Merge pull request #563 from MikaylaFischler/devel

2024.10.18 Release
This commit is contained in:
Mikayla
2024-10-18 20:34:14 -04:00
committed by GitHub
153 changed files with 7179 additions and 5826 deletions

View File

@@ -2,7 +2,6 @@ import base64
import json
import os
import subprocess
import sys
path_prefix = "./_minified/"

192
ccmsi.lua
View File

@@ -15,7 +15,7 @@ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]--
local CCMSI_VERSION = "v1.17"
local CCMSI_VERSION = "v1.19"
local install_dir = "/.install-cache"
local manifest_path = "https://mikaylafischler.github.io/cc-mek-scada/manifests/"
@@ -169,6 +169,7 @@ local function write_install_manifest(manifest, dependencies)
end
-- try at most 3 times to download a file from the repository and write into w_path base directory
---@return 0|1|2|3 success 0: ok, 1: download fail, 2: file open fail, 3: out of space
local function http_get_file(file, w_path)
local dl, err
for i = 1, 3 do
@@ -176,17 +177,28 @@ local function http_get_file(file, w_path)
if dl then
if i > 1 then green();println("success!");lgray() end
local f = fs.open(w_path..file, "w")
f.write(dl.readAll())
if not f then return 2 end
local ok, msg = pcall(function() f.write(dl.readAll()) end)
f.close()
if not ok then
if string.find(msg or "", "Out of space") ~= nil then
red();println("[out of space]");lgray()
return 3
else return 2 end
end
break
else
red();println("HTTP Error: "..err)
if i < 3 then lgray();print("> retrying...") end
if i < 3 then
lgray();print("> retrying...")
---@diagnostic disable-next-line: undefined-field
os.sleep(i/3.0)
else
return 1
end
end
return dl ~= nil
end
return 0
end
-- recursively build a tree out of the file manifest
@@ -407,7 +419,7 @@ elseif mode == "install" or mode == "update" then
if manifest.versions.installer ~= CCMSI_VERSION then
if not update_installer then yellow();println("A different version of the installer is available, it is recommended to update to it.");white() end
if update_installer or ask_y_n("Would you like to update now") then
if update_installer or ask_y_n("Would you like to update now", true) then
lgray();println("GET ccmsi.lua")
local dl, err = http.get(repo_path.."ccmsi.lua")
@@ -492,19 +504,119 @@ elseif mode == "install" or mode == "update" then
-- check space constraints
if space_available < space_required then
single_file_mode = true
yellow();println("NOTICE: Insufficient space available for a full cached download!");white()
lgray();println("Files can instead be downloaded one by one. If you are replacing a current install this may corrupt your install ONLY if it fails (such as a sudden network issue). If that occurs, you can still try again.")
if mode == "update" then println("If installation still fails, delete this device's log file and/or any unrelated files you have on this computer then try again.") end
white();
if not ask_y_n("Do you wish to continue", false) then
println("Operation cancelled.")
return
end
end
local success = true
if not single_file_mode then
-- delete a file if the capitalization changes so that things work on Windows
---@param path string
local function mitigate_case(path)
local dir, file = fs.getDir(path), fs.getName(path)
if not fs.isDir(dir) then return end
for _, p in ipairs(fs.list(dir)) do
if string.lower(p) == string.lower(file) then
if p ~= file then fs.delete(path) end
return
end
end
end
---@param dl_stat 1|2|3 download status
---@param file string file name
---@param attempt integer recursive attempt #
---@param sf_install function installer function for recursion
local function handle_dl_fail(dl_stat, file, attempt, sf_install)
red()
if dl_stat == 1 then
println("failed to download "..file)
elseif dl_stat > 1 then
if dl_stat == 2 then println("filesystem error with "..file) else println("no space for "..file) end
if attempt == 1 then
orange();println("re-attempting operation...");white()
sf_install(2)
elseif attempt == 2 then
yellow()
if dl_stat == 2 then println("There was an error writing to a file.") else println("Insufficient space available.") end
lgray()
if dl_stat == 2 then
println("This may be due to insufficent space available or file permission issues. The installer can now attempt to delete files not used by the SCADA system.")
else
println("The installer can now attempt to delete files not used by the SCADA system.")
end
white()
if not ask_y_n("Continue", false) then
success = false
return
end
clean(manifest)
sf_install(3)
elseif attempt == 3 then
yellow()
if dl_stat == 2 then println("There again was an error writing to a file.") else println("Insufficient space available.") end
lgray()
if dl_stat == 2 then
println("This may be due to insufficent space available or file permission issues. Please delete any unused files you have on this computer then try again. Do not delete the "..app..".settings file unless you want to re-configure.")
else
println("Please delete any unused files you have on this computer then try again. Do not delete the "..app..".settings file unless you want to re-configure.")
end
white()
success = false
end
end
end
-- single file update routine: go through all files and replace one by one
---@param attempt integer recursive attempt #
local function sf_install(attempt)
---@diagnostic disable-next-line: undefined-field
if attempt > 1 then os.sleep(2.0) end
local abort_attempt = false
success = true
for _, dependency in pairs(dependencies) do
if mode == "update" and unchanged(dependency) then
pkg_message("skipping install of unchanged package", dependency)
else
pkg_message("installing package", dependency)
lgray()
-- beginning on the second try, delete the directory before starting
if attempt >= 2 then
if dependency == "system" then
elseif dependency == "common" then
if fs.exists("/scada-common") then
fs.delete("/scada-common")
println("deleted /scada-common")
end
else
if fs.exists("/"..dependency) then
fs.delete("/"..dependency)
println("deleted /"..dependency)
end
end
end
local files = file_list[dependency]
for _, file in pairs(files) do
println("GET "..file)
mitigate_case(file)
local dl_stat = http_get_file(file, "/")
if dl_stat ~= 0 then
abort_attempt = true
---@diagnostic disable-next-line: param-type-mismatch
handle_dl_fail(dl_stat, file, attempt, sf_install)
break
end
end
end
if abort_attempt or not success then break end
end
end
-- handle update/install
if single_file_mode then sf_install(1)
else
if fs.exists(install_dir) then fs.delete(install_dir);fs.makeDir(install_dir) end
-- download all dependencies
@@ -518,9 +630,17 @@ elseif mode == "install" or mode == "update" then
local files = file_list[dependency]
for _, file in pairs(files) do
println("GET "..file)
if not http_get_file(file, install_dir.."/") then
local dl_stat = http_get_file(file, install_dir.."/")
success = dl_stat == 0
if dl_stat == 1 then
red();println("failed to download "..file)
success = false
break
elseif dl_stat == 2 then
red();println("filesystem error with "..file)
break
elseif dl_stat == 3 then
-- this shouldn't occur in this mode
red();println("no space for "..file)
break
end
end
@@ -548,41 +668,6 @@ elseif mode == "install" or mode == "update" then
end
fs.delete(install_dir)
if success then
write_install_manifest(manifest, dependencies)
green()
if mode == "install" then
println("Installation completed successfully.")
else println("Update completed successfully.") end
white();println("Ready to clean up unused files, press any key to continue...")
any_key();clean(manifest)
white();println("Done.")
else
if mode == "install" then
red();println("Installation failed.")
else orange();println("Update failed, existing files unmodified.") end
end
else
-- go through all files and replace one by one
for _, dependency in pairs(dependencies) do
if mode == "update" and unchanged(dependency) then
pkg_message("skipping install of unchanged package", dependency)
else
pkg_message("installing package", dependency)
lgray()
local files = file_list[dependency]
for _, file in pairs(files) do
println("GET "..file)
if not http_get_file(file, "/") then
red();println("failed to download "..file)
success = false
break
end
end
end
if not success then break end
end
if success then
@@ -596,9 +681,14 @@ elseif mode == "install" or mode == "update" then
white();println("Done.")
else
red()
if single_file_mode then
if mode == "install" then
println("Installation failed, files may have been skipped.")
else println("Update failed, files may have been skipped.") end
else
if mode == "install" then
println("Installation failed.")
else orange();println("Update failed, existing files unmodified.") end
end
end
elseif mode == "uninstall" then

View File

@@ -0,0 +1,318 @@
local comms = require("scada-common.comms")
local network = require("scada-common.network")
local ppm = require("scada-common.ppm")
local tcd = require("scada-common.tcd")
local util = require("scada-common.util")
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 PushButton = require("graphics.elements.controls.PushButton")
local NumberField = require("graphics.elements.form.NumberField")
local tri = util.trinary
local cpair = core.cpair
local PROTOCOL = comms.PROTOCOL
local DEVICE_TYPE = comms.DEVICE_TYPE
local ESTABLISH_ACK = comms.ESTABLISH_ACK
local MGMT_TYPE = comms.MGMT_TYPE
local self = {
nic = nil, ---@type nic
net_listen = false,
sv_addr = comms.BROADCAST,
sv_seq_num = util.time_ms() * 10,
show_sv_cfg = nil, ---@type function
sv_conn_button = nil, ---@type PushButton
sv_conn_status = nil, ---@type TextBox
sv_conn_detail = nil, ---@type TextBox
sv_next = nil, ---@type PushButton
sv_skip = nil, ---@type PushButton
tool_ctl = nil, ---@type _crd_cfg_tool_ctl
tmp_cfg = nil ---@type crd_config
}
-- check if a value is an integer within a range (inclusive)
---@param x any
---@param min integer
---@param max integer
local function is_int_min_max(x, min, max) return util.is_int(x) and x >= min and x <= max end
-- send a management packet to the supervisor
---@param msg_type MGMT_TYPE
---@param msg table
local function send_sv(msg_type, msg)
local s_pkt = comms.scada_packet()
local pkt = comms.mgmt_packet()
pkt.make(msg_type, msg)
s_pkt.make(self.sv_addr, self.sv_seq_num, PROTOCOL.SCADA_MGMT, pkt.raw_sendable())
self.nic.transmit(self.tmp_cfg.SVR_Channel, self.tmp_cfg.CRD_Channel, s_pkt)
self.sv_seq_num = self.sv_seq_num + 1
end
-- handle an establish message from the supervisor
---@param packet mgmt_frame
local function handle_packet(packet)
local error_msg = nil
if packet.scada_frame.local_channel() ~= self.tmp_cfg.CRD_Channel then
error_msg = "Error: unknown receive channel."
elseif packet.scada_frame.remote_channel() == self.tmp_cfg.SVR_Channel and packet.scada_frame.protocol() == PROTOCOL.SCADA_MGMT then
if packet.type == MGMT_TYPE.ESTABLISH then
if packet.length == 2 then
local est_ack = packet.data[1]
local config = packet.data[2]
if est_ack == ESTABLISH_ACK.ALLOW then
if type(config) == "table" and #config == 2 then
local count_ok = is_int_min_max(config[1], 1, 4)
local cool_ok = type(config[2]) == "table" and type(config[2].r_cool) == "table" and #config[2].r_cool == config[1]
if count_ok and cool_ok then
self.tmp_cfg.UnitCount = config[1]
self.tool_ctl.sv_cool_conf = {}
for i = 1, self.tmp_cfg.UnitCount do
local num_b = config[2].r_cool[i].BoilerCount
local num_t = config[2].r_cool[i].TurbineCount
self.tool_ctl.sv_cool_conf[i] = { num_b, num_t }
cool_ok = cool_ok and is_int_min_max(num_b, 0, 2) and is_int_min_max(num_t, 1, 3)
end
end
if not count_ok then
error_msg = "Error: supervisor unit count out of range."
elseif not cool_ok then
error_msg = "Error: supervisor cooling configuration malformed."
self.tool_ctl.sv_cool_conf = nil
end
self.sv_addr = packet.scada_frame.src_addr()
send_sv(MGMT_TYPE.CLOSE, {})
else
error_msg = "Error: invalid cooling configuration supervisor."
end
else
error_msg = "Error: invalid allow reply length from supervisor."
end
elseif packet.length == 1 then
local est_ack = packet.data[1]
if est_ack == ESTABLISH_ACK.DENY then
error_msg = "Error: supervisor connection denied."
elseif est_ack == ESTABLISH_ACK.COLLISION then
error_msg = "Error: a coordinator is already/still connected. Please try again."
elseif est_ack == ESTABLISH_ACK.BAD_VERSION then
error_msg = "Error: coordinator comms version does not match supervisor comms version."
else
error_msg = "Error: invalid reply from supervisor."
end
else
error_msg = "Error: invalid reply length from supervisor."
end
else
error_msg = "Error: didn't get an establish reply from supervisor."
end
end
self.net_listen = false
if error_msg then
self.sv_conn_status.set_value("")
self.sv_conn_detail.set_value(error_msg)
self.sv_conn_button.enable()
else
self.sv_conn_status.set_value("Connected!")
self.sv_conn_detail.set_value("Data received successfully, press 'Next' to continue.")
self.sv_skip.hide()
self.sv_next.show()
end
end
-- handle supervisor connection failure
local function handle_timeout()
self.net_listen = false
self.sv_conn_button.enable()
self.sv_conn_status.set_value("Timed out.")
self.sv_conn_detail.set_value("Supervisor did not reply. Ensure startup app is running on the supervisor.")
end
-- attempt a connection to the supervisor to get cooling info
local function sv_connect()
self.sv_conn_button.disable()
self.sv_conn_detail.set_value("")
local modem = ppm.get_wireless_modem()
if modem == nil then
self.sv_conn_status.set_value("Please connect an ender/wireless modem.")
else
self.sv_conn_status.set_value("Modem found, connecting...")
if self.nic == nil then self.nic = network.nic(modem) end
self.nic.closeAll()
self.nic.open(self.tmp_cfg.CRD_Channel)
self.sv_addr = comms.BROADCAST
self.net_listen = true
send_sv(MGMT_TYPE.ESTABLISH, { comms.version, "0.0.0", DEVICE_TYPE.CRD })
tcd.dispatch_unique(8, handle_timeout)
end
end
local facility = {}
-- create the facility configuration view
---@param tool_ctl _crd_cfg_tool_ctl
---@param main_pane MultiPane
---@param cfg_sys [ crd_config, crd_config, crd_config, { [1]: string, [2]: string, [3]: any }[], function ]
---@param fac_cfg Div
---@param style { [string]: cpair }
---@return MultiPane fac_pane
function facility.create(tool_ctl, main_pane, cfg_sys, fac_cfg, style)
local _, ini_cfg, tmp_cfg, _, _ = cfg_sys[1], cfg_sys[2], cfg_sys[3], cfg_sys[4], cfg_sys[5]
self.tmp_cfg = tmp_cfg
self.tool_ctl = tool_ctl
local bw_fg_bg = style.bw_fg_bg
local g_lg_fg_bg = style.g_lg_fg_bg
local nav_fg_bg = style.nav_fg_bg
local btn_act_fg_bg = style.btn_act_fg_bg
local btn_dis_fg_bg = style.btn_dis_fg_bg
--#region Facility
local fac_c_1 = Div{parent=fac_cfg,x=2,y=4,width=49}
local fac_c_2 = Div{parent=fac_cfg,x=2,y=4,width=49}
local fac_c_3 = Div{parent=fac_cfg,x=2,y=4,width=49}
local fac_pane = MultiPane{parent=fac_cfg,x=1,y=4,panes={fac_c_1,fac_c_2,fac_c_3}}
TextBox{parent=fac_cfg,x=1,y=2,text=" Facility Configuration",fg_bg=cpair(colors.black,colors.yellow)}
TextBox{parent=fac_c_1,x=1,y=1,height=4,text="This tool can attempt to connect to your supervisor computer. This would load facility information in order to get the unit count and aid monitor setup."}
TextBox{parent=fac_c_1,x=1,y=6,height=2,text="The supervisor startup app must be running and fully configured on your supervisor computer."}
self.sv_conn_status = TextBox{parent=fac_c_1,x=11,y=9,text=""}
self.sv_conn_detail = TextBox{parent=fac_c_1,x=1,y=11,height=2,text=""}
self.sv_conn_button = PushButton{parent=fac_c_1,x=1,y=9,text="Connect",min_width=9,callback=function()sv_connect()end,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
local function sv_skip()
tcd.abort(handle_timeout)
tool_ctl.sv_cool_conf = nil
self.net_listen = false
fac_pane.set_value(2)
end
local function sv_next()
self.show_sv_cfg()
tool_ctl.update_mon_reqs()
fac_pane.set_value(3)
end
PushButton{parent=fac_c_1,x=1,y=14,text="\x1b Back",callback=function()main_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
self.sv_skip = PushButton{parent=fac_c_1,x=44,y=14,text="Skip \x1a",callback=sv_skip,fg_bg=cpair(colors.black,colors.red),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
self.sv_next = PushButton{parent=fac_c_1,x=44,y=14,text="Next \x1a",callback=sv_next,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg,hidden=true}
TextBox{parent=fac_c_2,x=1,y=1,height=3,text="Please enter the number of reactors you have, also referred to as reactor units or 'units' for short. A maximum of 4 is currently supported."}
tool_ctl.num_units = NumberField{parent=fac_c_2,x=1,y=5,width=5,max_chars=2,default=ini_cfg.UnitCount,min=1,max=4,fg_bg=bw_fg_bg}
TextBox{parent=fac_c_2,x=7,y=5,text="reactors"}
TextBox{parent=fac_c_2,x=1,y=7,height=3,text="This will decide how many monitors you need. If this does not match the supervisor's number of reactor units, the coordinator will not connect.",fg_bg=g_lg_fg_bg}
TextBox{parent=fac_c_2,x=1,y=10,height=3,text="Since you skipped supervisor sync, the main monitor minimum height can't be determined precisely. It is marked with * on the next page.",fg_bg=g_lg_fg_bg}
local nu_error = TextBox{parent=fac_c_2,x=8,y=14,width=35,text="Please set the number of reactors.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
local function submit_num_units()
local count = tonumber(tool_ctl.num_units.get_value())
if count ~= nil and count > 0 and count < 5 then
nu_error.hide(true)
tmp_cfg.UnitCount = count
tool_ctl.update_mon_reqs()
main_pane.set_value(4)
else nu_error.show() end
end
PushButton{parent=fac_c_2,x=1,y=14,text="\x1b Back",callback=function()fac_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
PushButton{parent=fac_c_2,x=44,y=14,text="Next \x1a",callback=submit_num_units,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
TextBox{parent=fac_c_3,x=1,y=1,height=2,text="The following facility configuration was fetched from your supervisor computer."}
local fac_config_list = ListBox{parent=fac_c_3,x=1,y=4,height=9,width=49,scroll_height=100,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
PushButton{parent=fac_c_3,x=1,y=14,text="\x1b Back",callback=function()fac_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
PushButton{parent=fac_c_3,x=44,y=14,text="Next \x1a",callback=function()main_pane.set_value(4)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
--#endregion
--#region Tool and Helper Functions
tool_ctl.is_int_min_max = is_int_min_max
-- reset the connection display for a new attempt
function tool_ctl.init_sv_connect_ui()
self.sv_next.hide()
self.sv_skip.disable()
self.sv_skip.show()
self.sv_conn_button.enable()
self.sv_conn_status.set_value("")
self.sv_conn_detail.set_value("")
-- the user needs to wait a few seconds, encouraging the to connect
tcd.dispatch_unique(2, function () self.sv_skip.enable() end)
end
-- show the facility's unit count and cooling configuration data
function self.show_sv_cfg()
local conf = tool_ctl.sv_cool_conf
fac_config_list.remove_all()
local str = util.sprintf("Facility has %d reactor unit%s:", #conf, tri(#conf==1,"","s"))
TextBox{parent=fac_config_list,text=str,fg_bg=cpair(colors.gray,colors.white)}
for i = 1, #conf do
local num_b, num_t = conf[i][1], conf[i][2]
str = util.sprintf("\x07 Unit %d has %d boiler%s and %d turbine%s", i, num_b, tri(num_b == 1, "", "s"), num_t, tri(num_t == 1, "", "s"))
TextBox{parent=fac_config_list,text=str,fg_bg=cpair(colors.gray,colors.white)}
end
end
--#endregion
return fac_pane
end
-- handle incoming modem messages
---@param side string
---@param sender integer
---@param reply_to integer
---@param message any
---@param distance integer
function facility.receive_sv(side, sender, reply_to, message, distance)
if self.nic ~= nil and self.net_listen then
local s_pkt = self.nic.receive(side, sender, reply_to, message, distance)
if s_pkt and s_pkt.protocol() == PROTOCOL.SCADA_MGMT then
local mgmt_pkt = comms.mgmt_packet()
if mgmt_pkt.decode(s_pkt) then
tcd.abort(handle_timeout)
handle_packet(mgmt_pkt.get())
end
end
end
end
return facility

450
coordinator/config/hmi.lua Normal file
View File

@@ -0,0 +1,450 @@
local ppm = require("scada-common.ppm")
local types = require("scada-common.types")
local util = require("scada-common.util")
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 Checkbox = require("graphics.elements.controls.Checkbox")
local PushButton = require("graphics.elements.controls.PushButton")
local RadioButton = require("graphics.elements.controls.RadioButton")
local NumberField = require("graphics.elements.form.NumberField")
local cpair = core.cpair
local self = {
apply_mon = nil, ---@type PushButton
edit_monitor = nil, ---@type function
mon_iface = "",
mon_expect = {} ---@type integer[]
}
local hmi = {}
-- create the HMI (human machine interface) configuration view
---@param tool_ctl _crd_cfg_tool_ctl
---@param main_pane MultiPane
---@param cfg_sys [ crd_config, crd_config, crd_config, { [1]: string, [2]: string, [3]: any }[], function ]
---@param divs Div[]
---@param style { [string]: cpair }
---@return MultiPane mon_pane
function hmi.create(tool_ctl, main_pane, cfg_sys, divs, style)
local _, ini_cfg, tmp_cfg, _, _ = cfg_sys[1], cfg_sys[2], cfg_sys[3], cfg_sys[4], cfg_sys[5]
local mon_cfg, spkr_cfg, crd_cfg = divs[1], divs[2], divs[3]
local bw_fg_bg = style.bw_fg_bg
local g_lg_fg_bg = style.g_lg_fg_bg
local nav_fg_bg = style.nav_fg_bg
local btn_act_fg_bg = style.btn_act_fg_bg
local btn_dis_fg_bg = style.btn_dis_fg_bg
--#region Monitors
local mon_c_1 = Div{parent=mon_cfg,x=2,y=4,width=49}
local mon_c_2 = Div{parent=mon_cfg,x=2,y=4,width=49}
local mon_c_3 = Div{parent=mon_cfg,x=2,y=4,width=49}
local mon_c_4 = Div{parent=mon_cfg,x=2,y=4,width=49}
local mon_pane = MultiPane{parent=mon_cfg,x=1,y=4,panes={mon_c_1,mon_c_2,mon_c_3,mon_c_4}}
TextBox{parent=mon_cfg,x=1,y=2,text=" Monitor Configuration",fg_bg=cpair(colors.black,colors.blue)}
TextBox{parent=mon_c_1,x=1,y=1,height=5,text="Your configuration requires the following monitors. The main and flow monitors' heights are dependent on your unit count and cooling setup. If you manually entered the unit count, a * will be shown on potentially inaccurate calculations."}
local mon_reqs = ListBox{parent=mon_c_1,x=1,y=7,height=6,width=49,scroll_height=100,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
local function next_from_reqs()
-- unassign unit monitors above the unit count
for i = tmp_cfg.UnitCount + 1, 4 do tmp_cfg.UnitDisplays[i] = nil end
tool_ctl.gen_mon_list()
mon_pane.set_value(2)
end
PushButton{parent=mon_c_1,x=1,y=14,text="\x1b Back",callback=function()main_pane.set_value(3)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
PushButton{parent=mon_c_1,x=8,y=14,text="Legacy Options",min_width=16,callback=function()mon_pane.set_value(4)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
PushButton{parent=mon_c_1,x=44,y=14,text="Next \x1a",callback=next_from_reqs,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
TextBox{parent=mon_c_2,x=1,y=1,height=5,text="Please configure your monitors below. You can go back to the prior page without losing progress to double check what you need. All of those monitors must be assigned before you can proceed."}
local mon_list = ListBox{parent=mon_c_2,x=1,y=6,height=7,width=49,scroll_height=100,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
local assign_err = TextBox{parent=mon_c_2,x=8,y=14,width=35,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
local function submit_monitors()
if tmp_cfg.MainDisplay == nil then
assign_err.set_value("Please assign the main monitor.")
elseif tmp_cfg.FlowDisplay == nil and not tmp_cfg.DisableFlowView then
assign_err.set_value("Please assign the flow monitor.")
elseif util.table_len(tmp_cfg.UnitDisplays) ~= tmp_cfg.UnitCount then
for i = 1, tmp_cfg.UnitCount do
if tmp_cfg.UnitDisplays[i] == nil then
assign_err.set_value("Please assign the unit " .. i .. " monitor.")
break
end
end
else
assign_err.hide(true)
main_pane.set_value(5)
return
end
assign_err.show()
end
PushButton{parent=mon_c_2,x=1,y=14,text="\x1b Back",callback=function()mon_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
PushButton{parent=mon_c_2,x=44,y=14,text="Next \x1a",callback=submit_monitors,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
local mon_desc = TextBox{parent=mon_c_3,x=1,y=1,height=4,text=""}
local mon_unit_l, mon_unit = nil, nil ---@type TextBox, NumberField
local mon_warn = TextBox{parent=mon_c_3,x=1,y=11,height=2,text="",fg_bg=cpair(colors.red,colors.lightGray)}
---@param val integer assignment type
local function on_assign_mon(val)
if val == 2 and tmp_cfg.DisableFlowView then
self.apply_mon.disable()
mon_warn.set_value("You disabled having a flow view monitor. It can't be set unless you go back and enable it.")
mon_warn.show()
elseif not util.table_contains(self.mon_expect, val) then
self.apply_mon.disable()
mon_warn.set_value("That assignment doesn't fit monitor dimensions. You'll need to resize the monitor for it to work.")
mon_warn.show()
else
self.apply_mon.enable()
mon_warn.hide(true)
end
if val == 3 then
mon_unit_l.show()
mon_unit.show()
else
mon_unit_l.hide(true)
mon_unit.hide(true)
end
local value = mon_unit.get_value()
mon_unit.set_max(tmp_cfg.UnitCount)
if value == "0" or value == nil then mon_unit.set_value(0) end
end
TextBox{parent=mon_c_3,x=1,y=6,width=10,text="Assignment"}
local mon_assign = RadioButton{parent=mon_c_3,x=1,y=7,default=1,options={"Main Monitor","Flow Monitor","Unit Monitor"},callback=on_assign_mon,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.blue}
mon_unit_l = TextBox{parent=mon_c_3,x=18,y=6,width=7,text="Unit ID"}
mon_unit = NumberField{parent=mon_c_3,x=18,y=7,width=10,max_chars=2,min=1,max=4,fg_bg=bw_fg_bg}
local mon_u_err = TextBox{parent=mon_c_3,x=8,y=14,width=35,text="Please provide a unit ID.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
-- purge all assignments for a given monitor
---@param iface string
local function purge_assignments(iface)
if tmp_cfg.MainDisplay == iface then
tmp_cfg.MainDisplay = nil
elseif tmp_cfg.FlowDisplay == iface then
tmp_cfg.FlowDisplay = nil
else
for i = 1, tmp_cfg.UnitCount do
if tmp_cfg.UnitDisplays[i] == iface then tmp_cfg.UnitDisplays[i] = nil end
end
end
end
local function apply_monitor()
local iface = self.mon_iface
local type = mon_assign.get_value()
local u_id = tonumber(mon_unit.get_value())
if type == 1 then
purge_assignments(iface)
tmp_cfg.MainDisplay = iface
elseif type == 2 then
purge_assignments(iface)
tmp_cfg.FlowDisplay = iface
elseif u_id and u_id > 0 then
purge_assignments(iface)
tmp_cfg.UnitDisplays[u_id] = iface
else
mon_u_err.show()
return
end
tool_ctl.gen_mon_list()
mon_u_err.hide(true)
mon_pane.set_value(2)
end
PushButton{parent=mon_c_3,x=1,y=14,text="\x1b Back",callback=function()mon_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
self.apply_mon = PushButton{parent=mon_c_3,x=43,y=14,min_width=7,text="Apply",callback=apply_monitor,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
TextBox{parent=mon_c_4,x=1,y=1,height=3,text="For legacy compatibility with facilities built without space for a flow monitor, you can disable the flow monitor requirement here."}
TextBox{parent=mon_c_4,x=1,y=5,height=3,text="Please be aware that THIS OPTION WILL BE REMOVED ON RELEASE. Disabling it will only be available for the remainder of the beta."}
tool_ctl.dis_flow_view = Checkbox{parent=mon_c_4,x=1,y=9,default=ini_cfg.DisableFlowView,label="Disable Flow View Monitor",box_fg_bg=cpair(colors.blue,colors.black)}
local function back_from_legacy()
tmp_cfg.DisableFlowView = tool_ctl.dis_flow_view.get_value()
tool_ctl.update_mon_reqs()
mon_pane.set_value(1)
end
PushButton{parent=mon_c_4,x=44,y=14,min_width=6,text="Done",callback=back_from_legacy,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
--#endregion
--#region Speaker
local spkr_c = Div{parent=spkr_cfg,x=2,y=4,width=49}
TextBox{parent=spkr_cfg,x=1,y=2,text=" Speaker Configuration",fg_bg=cpair(colors.black,colors.cyan)}
TextBox{parent=spkr_c,x=1,y=1,height=2,text="The coordinator uses a speaker to play alarm sounds."}
TextBox{parent=spkr_c,x=1,y=4,height=3,text="You can change the speaker audio volume from the default. The range is 0.0 to 3.0, where 1.0 is standard volume."}
tool_ctl.s_vol = NumberField{parent=spkr_c,x=1,y=8,width=9,max_chars=7,allow_decimal=true,default=ini_cfg.SpeakerVolume,min=0,max=3,fg_bg=bw_fg_bg}
TextBox{parent=spkr_c,x=1,y=10,height=3,text="Note: alarm sine waves are at half scale so that multiple will be required to reach full scale.",fg_bg=g_lg_fg_bg}
local s_vol_err = TextBox{parent=spkr_c,x=8,y=14,width=35,text="Please set a volume.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
local function submit_vol()
local vol = tonumber(tool_ctl.s_vol.get_value())
if vol ~= nil then
s_vol_err.hide(true)
tmp_cfg.SpeakerVolume = vol
main_pane.set_value(6)
else s_vol_err.show() end
end
PushButton{parent=spkr_c,x=1,y=14,text="\x1b Back",callback=function()main_pane.set_value(4)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
PushButton{parent=spkr_c,x=44,y=14,text="Next \x1a",callback=submit_vol,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
--#endregion
--#region Coordinator UI
local crd_c_1 = Div{parent=crd_cfg,x=2,y=4,width=49}
TextBox{parent=crd_cfg,x=1,y=2,text=" Coordinator UI Configuration",fg_bg=cpair(colors.black,colors.lime)}
TextBox{parent=crd_c_1,x=1,y=1,height=3,text="Configure the UI interface options below if you wish to customize formats."}
TextBox{parent=crd_c_1,x=1,y=4,text="Clock Time Format"}
tool_ctl.clock_fmt = RadioButton{parent=crd_c_1,x=1,y=5,default=util.trinary(ini_cfg.Time24Hour,1,2),options={"24-Hour","12-Hour"},callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime}
TextBox{parent=crd_c_1,x=1,y=8,text="Temperature Scale"}
tool_ctl.temp_scale = RadioButton{parent=crd_c_1,x=1,y=9,default=ini_cfg.TempScale,options=types.TEMP_SCALE_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime}
TextBox{parent=crd_c_1,x=24,y=8,text="Energy Scale"}
tool_ctl.energy_scale = RadioButton{parent=crd_c_1,x=24,y=9,default=ini_cfg.EnergyScale,options=types.ENERGY_SCALE_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime}
local function submit_ui_opts()
tmp_cfg.Time24Hour = tool_ctl.clock_fmt.get_value() == 1
tmp_cfg.TempScale = tool_ctl.temp_scale.get_value()
tmp_cfg.EnergyScale = tool_ctl.energy_scale.get_value()
main_pane.set_value(7)
end
PushButton{parent=crd_c_1,x=1,y=14,text="\x1b Back",callback=function()main_pane.set_value(5)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
PushButton{parent=crd_c_1,x=44,y=14,text="Next \x1a",callback=submit_ui_opts,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
--#endregion
--#region Tool and Helper Functions
-- update list of monitor requirements
function tool_ctl.update_mon_reqs()
local plural = tmp_cfg.UnitCount > 1
if tool_ctl.sv_cool_conf ~= nil then
local cnf = tool_ctl.sv_cool_conf
local row1_tall = cnf[1][1] > 1 or cnf[1][2] > 2 or (cnf[2] and (cnf[2][1] > 1 or cnf[2][2] > 2))
local row1_short = (cnf[1][1] == 0 and cnf[1][2] == 1) and (cnf[2] == nil or (cnf[2][1] == 0 and cnf[2][2] == 1))
local row2_tall = (cnf[3] and (cnf[3][1] > 1 or cnf[3][2] > 2)) or (cnf[4] and (cnf[4][1] > 1 or cnf[4][2] > 2))
local row2_short = (cnf[3] == nil or (cnf[3][1] == 0 and cnf[3][2] == 1)) and (cnf[4] == nil or (cnf[4][1] == 0 and cnf[4][2] == 1))
if tmp_cfg.UnitCount <= 2 then
tool_ctl.main_mon_h = util.trinary(row1_tall, 5, 4)
else
-- is only one tall and the other short, or are both tall? -> 5 or 6; are neither tall? -> 5
if row1_tall or row2_tall then
tool_ctl.main_mon_h = util.trinary((row1_short and row2_tall) or (row1_tall and row2_short), 5, 6)
else tool_ctl.main_mon_h = 5 end
end
else
tool_ctl.main_mon_h = util.trinary(tmp_cfg.UnitCount <= 2, 4, 5)
end
tool_ctl.flow_mon_h = 2 + tmp_cfg.UnitCount
local asterisk = util.trinary(tool_ctl.sv_cool_conf == nil, "*", "")
local m_at_least = util.trinary(tool_ctl.main_mon_h < 6, "at least ", "")
local f_at_least = util.trinary(tool_ctl.flow_mon_h < 6, "at least ", "")
mon_reqs.remove_all()
TextBox{parent=mon_reqs,x=1,y=1,text="\x1a "..tmp_cfg.UnitCount.." Unit View Monitor"..util.trinary(plural,"s","")}
TextBox{parent=mon_reqs,x=1,y=1,text=" "..util.trinary(plural,"each ","").."must be 4 blocks wide by 4 tall",fg_bg=cpair(colors.gray,colors.white)}
TextBox{parent=mon_reqs,x=1,y=1,text="\x1a 1 Main View Monitor"}
TextBox{parent=mon_reqs,x=1,y=1,text=" must be 8 blocks wide by "..m_at_least..tool_ctl.main_mon_h..asterisk.." tall",fg_bg=cpair(colors.gray,colors.white)}
if not tmp_cfg.DisableFlowView then
TextBox{parent=mon_reqs,x=1,y=1,text="\x1a 1 Flow View Monitor"}
TextBox{parent=mon_reqs,x=1,y=1,text=" must be 8 blocks wide by "..f_at_least..tool_ctl.flow_mon_h.." tall",fg_bg=cpair(colors.gray,colors.white)}
end
end
-- set/edit a monitor's assignment
---@param iface string
---@param device ppm_entry
function self.edit_monitor(iface, device)
self.mon_iface = iface
local dev = device.dev
local w, h = ppm.monitor_block_size(dev.getSize())
local msg = "This size doesn't match a required screen. Please go back and resize it, or configure below at the risk of it not working."
self.mon_expect = {}
mon_assign.set_value(1)
mon_unit.set_value(0)
if w == 4 and h == 4 then
msg = "This could work as a unit display. Please configure below."
self.mon_expect = { 3 }
mon_assign.set_value(3)
elseif w == 8 then
if h >= tool_ctl.main_mon_h and h >= tool_ctl.flow_mon_h then
msg = "This could work as either your main monitor or flow monitor. Please configure below."
self.mon_expect = { 1, 2 }
if tmp_cfg.MainDisplay then mon_assign.set_value(2) end
elseif h >= tool_ctl.main_mon_h then
msg = "This could work as your main monitor. Please configure below."
self.mon_expect = { 1 }
elseif h >= tool_ctl.flow_mon_h then
msg = "This could work as your flow monitor. Please configure below."
self.mon_expect = { 2 }
mon_assign.set_value(2)
end
end
-- override if a config exists
if tmp_cfg.MainDisplay == iface then
mon_assign.set_value(1)
elseif tmp_cfg.FlowDisplay == iface then
mon_assign.set_value(2)
else
for i = 1, tmp_cfg.UnitCount do
if tmp_cfg.UnitDisplays[i] == iface then
mon_assign.set_value(3)
mon_unit.set_value(i)
break
end
end
end
on_assign_mon(mon_assign.get_value())
mon_desc.set_value(util.c("You have selected '", iface, "', which has a block size of ", w, " wide by ", h, " tall. ", msg))
mon_pane.set_value(3)
end
-- generate the list of available monitors
function tool_ctl.gen_mon_list()
mon_list.remove_all()
local missing = { main = tmp_cfg.MainDisplay ~= nil, flow = tmp_cfg.FlowDisplay ~= nil, unit = {} }
for i = 1, tmp_cfg.UnitCount do missing.unit[i] = tmp_cfg.UnitDisplays[i] ~= nil end
-- list connected monitors
local monitors = ppm.get_monitor_list()
for iface, device in pairs(monitors) do
local dev = device.dev ---@type Monitor
dev.setTextScale(0.5)
dev.setTextColor(colors.white)
dev.setBackgroundColor(colors.black)
dev.clear()
dev.setCursorPos(1, 1)
dev.setTextColor(colors.magenta)
dev.write("This is monitor")
dev.setCursorPos(1, 2)
dev.setTextColor(colors.white)
dev.write(iface)
local assignment = "Unused"
if tmp_cfg.MainDisplay == iface then
assignment = "Main"
missing.main = false
elseif tmp_cfg.FlowDisplay == iface then
assignment = "Flow"
missing.flow = false
else
for i = 1, tmp_cfg.UnitCount do
if tmp_cfg.UnitDisplays[i] == iface then
missing.unit[i] = false
assignment = "Unit " .. i
break
end
end
end
local line = Div{parent=mon_list,x=1,y=1,height=1}
TextBox{parent=line,x=1,y=1,width=6,text=assignment,fg_bg=cpair(util.trinary(assignment=="Unused",colors.red,colors.blue),colors.white)}
TextBox{parent=line,x=8,y=1,text=iface}
local w, h = ppm.monitor_block_size(dev.getSize())
local function unset_mon()
purge_assignments(iface)
tool_ctl.gen_mon_list()
end
TextBox{parent=line,x=33,y=1,width=4,text=w.."x"..h,fg_bg=cpair(colors.black,colors.white)}
PushButton{parent=line,x=37,y=1,min_width=5,height=1,text="SET",callback=function()self.edit_monitor(iface,device)end,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg}
local unset = PushButton{parent=line,x=42,y=1,min_width=7,height=1,text="UNSET",callback=unset_mon,fg_bg=cpair(colors.black,colors.red),active_fg_bg=btn_act_fg_bg,dis_fg_bg=cpair(colors.black,colors.gray)}
if assignment == "Unused" then unset.disable() end
end
local dc_list = {} -- disconnected monitor list
if missing.main then table.insert(dc_list, { "Main", tmp_cfg.MainDisplay }) end
if missing.flow then table.insert(dc_list, { "Flow", tmp_cfg.FlowDisplay }) end
for i = 1, tmp_cfg.UnitCount do
if missing.unit[i] then table.insert(dc_list, { "Unit " .. i, tmp_cfg.UnitDisplays[i] }) end
end
-- add monitors that are assigned but not connected
for i = 1, #dc_list do
local line = Div{parent=mon_list,x=1,y=1,height=1}
TextBox{parent=line,x=1,y=1,width=6,text=dc_list[i][1],fg_bg=cpair(colors.blue,colors.white)}
TextBox{parent=line,x=8,y=1,text="disconnected",fg_bg=cpair(colors.red,colors.white)}
local function unset_mon()
purge_assignments(dc_list[i][2])
tool_ctl.gen_mon_list()
end
TextBox{parent=line,x=33,y=1,width=4,text="?x?",fg_bg=cpair(colors.black,colors.white)}
PushButton{parent=line,x=37,y=1,min_width=5,height=1,text="SET",callback=function()end,dis_fg_bg=cpair(colors.black,colors.gray)}.disable()
PushButton{parent=line,x=42,y=1,min_width=7,height=1,text="UNSET",callback=unset_mon,fg_bg=cpair(colors.black,colors.red),active_fg_bg=btn_act_fg_bg,dis_fg_bg=cpair(colors.black,colors.gray)}
end
end
--#endregion
return mon_pane
end
return hmi

View File

@@ -0,0 +1,577 @@
local comms = require("scada-common.comms")
local log = require("scada-common.log")
local network = require("scada-common.network")
local types = require("scada-common.types")
local util = require("scada-common.util")
local core = require("graphics.core")
local themes = require("graphics.themes")
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 Checkbox = require("graphics.elements.controls.Checkbox")
local PushButton = require("graphics.elements.controls.PushButton")
local RadioButton = require("graphics.elements.controls.RadioButton")
local NumberField = require("graphics.elements.form.NumberField")
local TextField = require("graphics.elements.form.TextField")
local IndLight = require("graphics.elements.indicators.IndicatorLight")
local tri = util.trinary
local cpair = core.cpair
local RIGHT = core.ALIGN.RIGHT
local self = {
importing_legacy = false,
show_auth_key = nil, ---@type function
show_key_btn = nil, ---@type PushButton
auth_key_textbox = nil, ---@type TextBox
auth_key_value = ""
}
local system = {}
-- create the system configuration view
---@param tool_ctl _crd_cfg_tool_ctl
---@param main_pane MultiPane
---@param cfg_sys [ crd_config, crd_config, crd_config, { [1]: string, [2]: string, [3]: any }[], function ]
---@param divs Div[]
---@param ext [ MultiPane, MultiPane, function, function ]
---@param style { [string]: cpair }
function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style)
local settings_cfg, ini_cfg, tmp_cfg, fields, load_settings = cfg_sys[1], cfg_sys[2], cfg_sys[3], cfg_sys[4], cfg_sys[5]
local net_cfg, log_cfg, clr_cfg, summary = divs[1], divs[2], divs[3], divs[4]
local fac_pane, mon_pane, preset_monitor_fields, exit = ext[1], ext[2], ext[3], ext[4]
local bw_fg_bg = style.bw_fg_bg
local g_lg_fg_bg = style.g_lg_fg_bg
local nav_fg_bg = style.nav_fg_bg
local btn_act_fg_bg = style.btn_act_fg_bg
local btn_dis_fg_bg = style.btn_dis_fg_bg
--#region Network
local net_c_1 = Div{parent=net_cfg,x=2,y=4,width=49}
local net_c_2 = Div{parent=net_cfg,x=2,y=4,width=49}
local net_c_3 = Div{parent=net_cfg,x=2,y=4,width=49}
local net_c_4 = Div{parent=net_cfg,x=2,y=4,width=49}
local net_pane = MultiPane{parent=net_cfg,x=1,y=4,panes={net_c_1,net_c_2,net_c_3,net_c_4}}
TextBox{parent=net_cfg,x=1,y=2,text=" Network Configuration",fg_bg=cpair(colors.black,colors.lightBlue)}
TextBox{parent=net_c_1,x=1,y=1,text="Please set the network channels below."}
TextBox{parent=net_c_1,x=1,y=3,height=4,text="Each of the 5 uniquely named channels, including the 3 below, must be the same for each device in this SCADA network. For multiplayer servers, it is recommended to not use the default channels.",fg_bg=g_lg_fg_bg}
TextBox{parent=net_c_1,x=1,y=8,width=18,text="Supervisor Channel"}
local svr_chan = NumberField{parent=net_c_1,x=21,y=8,width=7,default=ini_cfg.SVR_Channel,min=1,max=65535,fg_bg=bw_fg_bg}
TextBox{parent=net_c_1,x=29,y=8,height=4,text="[SVR_CHANNEL]",fg_bg=g_lg_fg_bg}
TextBox{parent=net_c_1,x=1,y=10,width=19,text="Coordinator Channel"}
local crd_chan = NumberField{parent=net_c_1,x=21,y=10,width=7,default=ini_cfg.CRD_Channel,min=1,max=65535,fg_bg=bw_fg_bg}
TextBox{parent=net_c_1,x=29,y=10,height=4,text="[CRD_CHANNEL]",fg_bg=g_lg_fg_bg}
TextBox{parent=net_c_1,x=1,y=12,width=14,text="Pocket Channel"}
local pkt_chan = NumberField{parent=net_c_1,x=21,y=12,width=7,default=ini_cfg.PKT_Channel,min=1,max=65535,fg_bg=bw_fg_bg}
TextBox{parent=net_c_1,x=29,y=12,height=4,text="[PKT_CHANNEL]",fg_bg=g_lg_fg_bg}
local chan_err = TextBox{parent=net_c_1,x=8,y=14,width=35,text="Please set all channels.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
local function submit_channels()
local svr_c, crd_c, pkt_c = tonumber(svr_chan.get_value()), tonumber(crd_chan.get_value()), tonumber(pkt_chan.get_value())
if svr_c ~= nil and crd_c ~= nil and pkt_c ~= nil then
tmp_cfg.SVR_Channel, tmp_cfg.CRD_Channel, tmp_cfg.PKT_Channel = svr_c, crd_c, pkt_c
net_pane.set_value(2)
chan_err.hide(true)
else chan_err.show() end
end
PushButton{parent=net_c_1,x=1,y=14,text="\x1b Back",callback=function()main_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
PushButton{parent=net_c_1,x=44,y=14,text="Next \x1a",callback=submit_channels,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
TextBox{parent=net_c_2,x=1,y=1,text="Please set the connection timeouts below."}
TextBox{parent=net_c_2,x=1,y=3,height=4,text="You generally should not need to modify these. On slow servers, you can try to increase this to make the system wait longer before assuming a disconnection. The default for all is 5 seconds.",fg_bg=g_lg_fg_bg}
TextBox{parent=net_c_2,x=1,y=8,width=19,text="Supervisor Timeout"}
local svr_timeout = NumberField{parent=net_c_2,x=20,y=8,width=7,default=ini_cfg.SVR_Timeout,min=2,max=25,max_chars=6,max_frac_digits=2,allow_decimal=true,fg_bg=bw_fg_bg}
TextBox{parent=net_c_2,x=1,y=10,width=14,text="Pocket Timeout"}
local api_timeout = NumberField{parent=net_c_2,x=20,y=10,width=7,default=ini_cfg.API_Timeout,min=2,max=25,max_chars=6,max_frac_digits=2,allow_decimal=true,fg_bg=bw_fg_bg}
TextBox{parent=net_c_2,x=28,y=8,height=4,width=7,text="seconds\n\nseconds",fg_bg=g_lg_fg_bg}
local ct_err = TextBox{parent=net_c_2,x=8,y=14,width=35,text="Please set all connection timeouts.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
local function submit_timeouts()
local svr_cto, api_cto = tonumber(svr_timeout.get_value()), tonumber(api_timeout.get_value())
if svr_cto ~= nil and api_cto ~= nil then
tmp_cfg.SVR_Timeout, tmp_cfg.API_Timeout = svr_cto, api_cto
net_pane.set_value(3)
ct_err.hide(true)
else ct_err.show() end
end
PushButton{parent=net_c_2,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
PushButton{parent=net_c_2,x=44,y=14,text="Next \x1a",callback=submit_timeouts,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
TextBox{parent=net_c_3,x=1,y=1,text="Please set the trusted range below."}
TextBox{parent=net_c_3,x=1,y=3,height=3,text="Setting this to a value larger than 0 prevents connections with devices that many meters (blocks) away in any direction.",fg_bg=g_lg_fg_bg}
TextBox{parent=net_c_3,x=1,y=7,height=2,text="This is optional. You can disable this functionality by setting the value to 0.",fg_bg=g_lg_fg_bg}
local range = NumberField{parent=net_c_3,x=1,y=10,width=10,default=ini_cfg.TrustedRange,min=0,max_chars=20,allow_decimal=true,fg_bg=bw_fg_bg}
local tr_err = TextBox{parent=net_c_3,x=8,y=14,width=35,text="Please set the trusted range.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
local function submit_tr()
local range_val = tonumber(range.get_value())
if range_val ~= nil then
tmp_cfg.TrustedRange = range_val
comms.set_trusted_range(range_val)
net_pane.set_value(4)
tr_err.hide(true)
else tr_err.show() end
end
PushButton{parent=net_c_3,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
PushButton{parent=net_c_3,x=44,y=14,text="Next \x1a",callback=submit_tr,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
TextBox{parent=net_c_4,x=1,y=1,height=2,text="Optionally, set the facility authentication key below. Do NOT use one of your passwords."}
TextBox{parent=net_c_4,x=1,y=4,height=6,text="This enables verifying that messages are authentic, so it is intended for security on multiplayer servers. All devices on the same network MUST use the same key if any device has a key. This does result in some extra computation (can slow things down).",fg_bg=g_lg_fg_bg}
TextBox{parent=net_c_4,x=1,y=11,text="Facility Auth Key"}
local key, _ = TextField{parent=net_c_4,x=1,y=12,max_len=64,value=ini_cfg.AuthKey,width=32,height=1,fg_bg=bw_fg_bg}
local function censor_key(enable) key.censor(tri(enable, "*", nil)) end
local hide_key = Checkbox{parent=net_c_4,x=34,y=12,label="Hide",box_fg_bg=cpair(colors.lightBlue,colors.black),callback=censor_key}
hide_key.set_value(true)
censor_key(true)
local key_err = TextBox{parent=net_c_4,x=8,y=14,width=35,text="Key must be at least 8 characters.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
local function submit_auth()
local v = key.get_value()
if string.len(v) == 0 or string.len(v) >= 8 then
tmp_cfg.AuthKey = key.get_value()
key_err.hide(true)
-- init mac for supervisor connection
if string.len(v) >= 8 then network.init_mac(tmp_cfg.AuthKey) else network.deinit_mac() end
-- prep supervisor connection screen
tool_ctl.init_sv_connect_ui()
main_pane.set_value(3)
else key_err.show() end
end
PushButton{parent=net_c_4,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(3)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
PushButton{parent=net_c_4,x=44,y=14,text="Next \x1a",callback=submit_auth,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
--#endregion
--#region Logging
local log_c_1 = Div{parent=log_cfg,x=2,y=4,width=49}
TextBox{parent=log_cfg,x=1,y=2,text=" Logging Configuration",fg_bg=cpair(colors.black,colors.pink)}
TextBox{parent=log_c_1,x=1,y=1,text="Please configure logging below."}
TextBox{parent=log_c_1,x=1,y=3,text="Log File Mode"}
local mode = RadioButton{parent=log_c_1,x=1,y=4,default=ini_cfg.LogMode+1,options={"Append on Startup","Replace on Startup"},callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.pink}
TextBox{parent=log_c_1,x=1,y=7,text="Log File Path"}
local path = TextField{parent=log_c_1,x=1,y=8,width=49,height=1,value=ini_cfg.LogPath,max_len=128,fg_bg=bw_fg_bg}
local en_dbg = Checkbox{parent=log_c_1,x=1,y=10,default=ini_cfg.LogDebug,label="Enable Logging Debug Messages",box_fg_bg=cpair(colors.pink,colors.black)}
TextBox{parent=log_c_1,x=3,y=11,height=2,text="This results in much larger log files. It is best to only use this when there is a problem.",fg_bg=g_lg_fg_bg}
local path_err = TextBox{parent=log_c_1,x=8,y=14,width=35,text="Please provide a log file path.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
local function submit_log()
if path.get_value() ~= "" then
path_err.hide(true)
tmp_cfg.LogMode = mode.get_value() - 1
tmp_cfg.LogPath = path.get_value()
tmp_cfg.LogDebug = en_dbg.get_value()
tool_ctl.color_apply.hide(true)
tool_ctl.color_next.show()
main_pane.set_value(8)
else path_err.show() end
end
PushButton{parent=log_c_1,x=1,y=14,text="\x1b Back",callback=function()main_pane.set_value(6)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
PushButton{parent=log_c_1,x=44,y=14,text="Next \x1a",callback=submit_log,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
--#endregion
--#region Color Options
local clr_c_1 = Div{parent=clr_cfg,x=2,y=4,width=49}
local clr_c_2 = Div{parent=clr_cfg,x=2,y=4,width=49}
local clr_c_3 = Div{parent=clr_cfg,x=2,y=4,width=49}
local clr_c_4 = Div{parent=clr_cfg,x=2,y=4,width=49}
local clr_pane = MultiPane{parent=clr_cfg,x=1,y=4,panes={clr_c_1,clr_c_2,clr_c_3,clr_c_4}}
TextBox{parent=clr_cfg,x=1,y=2,text=" Color Configuration",fg_bg=cpair(colors.black,colors.magenta)}
TextBox{parent=clr_c_1,x=1,y=1,height=2,text="Here you can select the color themes for the different UI displays."}
TextBox{parent=clr_c_1,x=1,y=4,height=2,text="Click 'Accessibility' below to access colorblind assistive options.",fg_bg=g_lg_fg_bg}
TextBox{parent=clr_c_1,x=1,y=7,text="Main UI Theme"}
local main_theme = RadioButton{parent=clr_c_1,x=1,y=8,default=ini_cfg.MainTheme,options=themes.UI_THEME_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.magenta}
TextBox{parent=clr_c_1,x=18,y=7,text="Front Panel Theme"}
local fp_theme = RadioButton{parent=clr_c_1,x=18,y=8,default=ini_cfg.FrontPanelTheme,options=themes.FP_THEME_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.magenta}
TextBox{parent=clr_c_2,x=1,y=1,height=6,text="This system uses color heavily to distinguish ok and not, with some indicators using many colors. By selecting a mode below, indicators will change as shown. For non-standard modes, indicators with more than two colors will usually be split up."}
TextBox{parent=clr_c_2,x=21,y=7,text="Preview"}
local _ = IndLight{parent=clr_c_2,x=21,y=8,label="Good",colors=cpair(colors.black,colors.green)}
_ = IndLight{parent=clr_c_2,x=21,y=9,label="Warning",colors=cpair(colors.black,colors.yellow)}
_ = IndLight{parent=clr_c_2,x=21,y=10,label="Bad",colors=cpair(colors.black,colors.red)}
local b_off = IndLight{parent=clr_c_2,x=21,y=11,label="Off",colors=cpair(colors.black,colors.black),hidden=true}
local g_off = IndLight{parent=clr_c_2,x=21,y=11,label="Off",colors=cpair(colors.gray,colors.gray),hidden=true}
local function recolor(value)
local c = themes.smooth_stone.color_modes[value]
if value == themes.COLOR_MODE.STANDARD or value == themes.COLOR_MODE.BLUE_IND then
b_off.hide()
g_off.show()
else
g_off.hide()
b_off.show()
end
if #c == 0 then
for i = 1, #style.colors do term.setPaletteColor(style.colors[i].c, style.colors[i].hex) end
else
term.setPaletteColor(colors.green, c[1].hex)
term.setPaletteColor(colors.yellow, c[2].hex)
term.setPaletteColor(colors.red, c[3].hex)
end
end
TextBox{parent=clr_c_2,x=1,y=7,width=10,text="Color Mode"}
local c_mode = RadioButton{parent=clr_c_2,x=1,y=8,default=ini_cfg.ColorMode,options=themes.COLOR_MODE_NAMES,callback=recolor,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.magenta}
TextBox{parent=clr_c_2,x=21,y=13,height=2,width=18,text="Note: exact color varies by theme.",fg_bg=g_lg_fg_bg}
PushButton{parent=clr_c_2,x=44,y=14,min_width=6,text="Done",callback=function()clr_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
local function back_from_colors()
main_pane.set_value(tri(tool_ctl.jumped_to_color, 1, 7))
tool_ctl.jumped_to_color = false
recolor(1)
end
local function show_access()
clr_pane.set_value(2)
recolor(c_mode.get_value())
end
local function submit_colors()
tmp_cfg.MainTheme = main_theme.get_value()
tmp_cfg.FrontPanelTheme = fp_theme.get_value()
tmp_cfg.ColorMode = c_mode.get_value()
if tool_ctl.jumped_to_color then
settings.set("MainTheme", tmp_cfg.MainTheme)
settings.set("FrontPanelTheme", tmp_cfg.FrontPanelTheme)
settings.set("ColorMode", tmp_cfg.ColorMode)
if settings.save("/coordinator.settings") then
load_settings(settings_cfg, true)
load_settings(ini_cfg)
clr_pane.set_value(3)
else
clr_pane.set_value(4)
end
else
tool_ctl.gen_summary(tmp_cfg)
tool_ctl.viewing_config = false
self.importing_legacy = false
tool_ctl.settings_apply.show()
main_pane.set_value(9)
end
end
PushButton{parent=clr_c_1,x=1,y=14,text="\x1b Back",callback=back_from_colors,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
PushButton{parent=clr_c_1,x=8,y=14,min_width=15,text="Accessibility",callback=show_access,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
tool_ctl.color_next = PushButton{parent=clr_c_1,x=44,y=14,text="Next \x1a",callback=submit_colors,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
tool_ctl.color_apply = PushButton{parent=clr_c_1,x=43,y=14,min_width=7,text="Apply",callback=submit_colors,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg}
tool_ctl.color_apply.hide(true)
local function c_go_home()
main_pane.set_value(1)
clr_pane.set_value(1)
end
TextBox{parent=clr_c_3,x=1,y=1,text="Settings saved!"}
PushButton{parent=clr_c_3,x=1,y=14,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray)}
PushButton{parent=clr_c_3,x=44,y=14,min_width=6,text="Home",callback=c_go_home,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
TextBox{parent=clr_c_4,x=1,y=1,height=5,text="Failed to save the settings file.\n\nThere may not be enough space for the modification or server file permissions may be denying writes."}
PushButton{parent=clr_c_4,x=1,y=14,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray)}
PushButton{parent=clr_c_4,x=44,y=14,min_width=6,text="Home",callback=c_go_home,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
--#endregion
--#region Summary and Saving
local sum_c_1 = Div{parent=summary,x=2,y=4,width=49}
local sum_c_2 = Div{parent=summary,x=2,y=4,width=49}
local sum_c_3 = Div{parent=summary,x=2,y=4,width=49}
local sum_c_4 = Div{parent=summary,x=2,y=4,width=49}
local sum_pane = MultiPane{parent=summary,x=1,y=4,panes={sum_c_1,sum_c_2,sum_c_3,sum_c_4}}
TextBox{parent=summary,x=1,y=2,text=" Summary",fg_bg=cpair(colors.black,colors.green)}
local setting_list = ListBox{parent=sum_c_1,x=1,y=1,height=12,width=49,scroll_height=100,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
local function back_from_summary()
if tool_ctl.viewing_config or self.importing_legacy then
main_pane.set_value(1)
tool_ctl.viewing_config = false
self.importing_legacy = false
tool_ctl.settings_apply.show()
else
main_pane.set_value(8)
end
end
---@param element graphics_element
---@param data any
local function try_set(element, data)
if data ~= nil then element.set_value(data) end
end
local function save_and_continue()
for _, field in ipairs(fields) do
local k, v = field[1], tmp_cfg[field[1]]
if v == nil then settings.unset(k) else settings.set(k, v) end
end
if settings.save("/coordinator.settings") then
load_settings(settings_cfg, true)
load_settings(ini_cfg)
try_set(svr_chan, ini_cfg.SVR_Channel)
try_set(crd_chan, ini_cfg.CRD_Channel)
try_set(pkt_chan, ini_cfg.PKT_Channel)
try_set(svr_timeout, ini_cfg.SVR_Timeout)
try_set(api_timeout, ini_cfg.API_Timeout)
try_set(range, ini_cfg.TrustedRange)
try_set(key, ini_cfg.AuthKey)
try_set(tool_ctl.num_units, ini_cfg.UnitCount)
try_set(tool_ctl.dis_flow_view, ini_cfg.DisableFlowView)
try_set(tool_ctl.s_vol, ini_cfg.SpeakerVolume)
try_set(tool_ctl.clock_fmt, tri(ini_cfg.Time24Hour, 1, 2))
try_set(tool_ctl.temp_scale, ini_cfg.TempScale)
try_set(tool_ctl.energy_scale, ini_cfg.EnergyScale)
try_set(mode, ini_cfg.LogMode)
try_set(path, ini_cfg.LogPath)
try_set(en_dbg, ini_cfg.LogDebug)
try_set(main_theme, ini_cfg.MainTheme)
try_set(fp_theme, ini_cfg.FrontPanelTheme)
try_set(c_mode, ini_cfg.ColorMode)
preset_monitor_fields()
tool_ctl.gen_mon_list()
tool_ctl.view_cfg.enable()
tool_ctl.color_cfg.enable()
if self.importing_legacy then
self.importing_legacy = false
sum_pane.set_value(3)
else
sum_pane.set_value(2)
end
else
sum_pane.set_value(4)
end
end
PushButton{parent=sum_c_1,x=1,y=14,text="\x1b Back",callback=back_from_summary,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
self.show_key_btn = PushButton{parent=sum_c_1,x=8,y=14,min_width=17,text="Unhide Auth Key",callback=function()self.show_auth_key()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
tool_ctl.settings_apply = PushButton{parent=sum_c_1,x=43,y=14,min_width=7,text="Apply",callback=save_and_continue,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg}
TextBox{parent=sum_c_2,x=1,y=1,text="Settings saved!"}
local function go_home()
main_pane.set_value(1)
net_pane.set_value(1)
fac_pane.set_value(1)
mon_pane.set_value(1)
clr_pane.set_value(1)
sum_pane.set_value(1)
end
PushButton{parent=sum_c_2,x=1,y=14,min_width=6,text="Home",callback=go_home,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
PushButton{parent=sum_c_2,x=44,y=14,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray)}
TextBox{parent=sum_c_3,x=1,y=1,height=2,text="The old config.lua and coord.settings files will now be deleted, then the configurator will exit."}
local function delete_legacy()
fs.delete("/coordinator/config.lua")
fs.delete("/coord.settings")
exit()
end
PushButton{parent=sum_c_3,x=1,y=14,min_width=8,text="Cancel",callback=go_home,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
PushButton{parent=sum_c_3,x=44,y=14,min_width=6,text="OK",callback=delete_legacy,fg_bg=cpair(colors.black,colors.green),active_fg_bg=cpair(colors.white,colors.gray)}
TextBox{parent=sum_c_4,x=1,y=1,height=5,text="Failed to save the settings file.\n\nThere may not be enough space for the modification or server file permissions may be denying writes."}
PushButton{parent=sum_c_4,x=1,y=14,min_width=6,text="Home",callback=go_home,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
PushButton{parent=sum_c_4,x=44,y=14,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray)}
--#endregion
--#region Tool Functions
-- load a legacy config file
function tool_ctl.load_legacy()
local config = require("coordinator.config")
tmp_cfg.SVR_Channel = config.SVR_CHANNEL
tmp_cfg.CRD_Channel = config.CRD_CHANNEL
tmp_cfg.PKT_Channel = config.PKT_CHANNEL
tmp_cfg.SVR_Timeout = config.SV_TIMEOUT
tmp_cfg.API_Timeout = config.API_TIMEOUT
tmp_cfg.TrustedRange = config.TRUSTED_RANGE
tmp_cfg.AuthKey = config.AUTH_KEY or ""
tmp_cfg.UnitCount = config.NUM_UNITS
tmp_cfg.DisableFlowView = config.DISABLE_FLOW_VIEW
tmp_cfg.SpeakerVolume = config.SOUNDER_VOLUME
tmp_cfg.Time24Hour = config.TIME_24_HOUR
tmp_cfg.LogMode = config.LOG_MODE
tmp_cfg.LogPath = config.LOG_PATH
tmp_cfg.LogDebug = config.LOG_DEBUG or false
settings.load("/coord.settings")
tmp_cfg.MainDisplay = settings.get("PRIMARY_DISPLAY")
tmp_cfg.FlowDisplay = settings.get("FLOW_DISPLAY")
tmp_cfg.UnitDisplays = settings.get("UNIT_DISPLAYS", {})
-- if there are extra monitor entries, delete them now
-- not doing so will cause the app to fail to start
if tool_ctl.is_int_min_max(tmp_cfg.UnitCount, 1, 4) then
for i = tmp_cfg.UnitCount + 1, 4 do tmp_cfg.UnitDisplays[i] = nil end
end
if settings.get("ControlStates") == nil then
local ctrl_states = {
process = settings.get("PROCESS"),
waste_modes = settings.get("WASTE_MODES"),
priority_groups = settings.get("PRIORITY_GROUPS"),
}
settings.set("ControlStates", ctrl_states)
end
settings.unset("PRIMARY_DISPLAY")
settings.unset("FLOW_DISPLAY")
settings.unset("UNIT_DISPLAYS")
settings.unset("PROCESS")
settings.unset("WASTE_MODES")
settings.unset("PRIORITY_GROUPS")
tool_ctl.gen_summary(tmp_cfg)
sum_pane.set_value(1)
main_pane.set_value(9)
self.importing_legacy = true
end
-- expose the auth key on the summary page
function self.show_auth_key()
self.show_key_btn.disable()
self.auth_key_textbox.set_value(self.auth_key_value)
end
-- generate the summary list
---@param cfg crd_config
function tool_ctl.gen_summary(cfg)
setting_list.remove_all()
local alternate = false
local inner_width = setting_list.get_width() - 1
self.show_key_btn.enable()
self.auth_key_value = cfg.AuthKey or "" -- to show auth key
for i = 1, #fields do
local f = fields[i]
local height = 1
local label_w = string.len(f[2])
local val_max_w = (inner_width - label_w) + 1
local raw = cfg[f[1]]
local val = util.strval(raw)
if f[1] == "AuthKey" then val = string.rep("*", string.len(val))
elseif f[1] == "LogMode" then val = util.trinary(raw == log.MODE.APPEND, "append", "replace")
elseif f[1] == "TempScale" then
val = util.strval(types.TEMP_SCALE_NAMES[raw])
elseif f[1] == "EnergyScale" then
val = util.strval(types.ENERGY_SCALE_NAMES[raw])
elseif f[1] == "MainTheme" then
val = util.strval(themes.ui_theme_name(raw))
elseif f[1] == "FrontPanelTheme" then
val = util.strval(themes.fp_theme_name(raw))
elseif f[1] == "ColorMode" then
val = util.strval(themes.color_mode_name(raw))
elseif f[1] == "UnitDisplays" and type(cfg.UnitDisplays) == "table" then
val = ""
for idx = 1, #cfg.UnitDisplays do
val = val .. util.trinary(idx == 1, "", "\n") .. util.sprintf(" \x07 Unit %d - %s", idx, cfg.UnitDisplays[idx])
end
end
if val == "nil" then val = "<not set>" end
local c = util.trinary(alternate, g_lg_fg_bg, cpair(colors.gray,colors.white))
alternate = not alternate
if string.len(val) > val_max_w then
local lines = util.strwrap(val, inner_width)
height = #lines + 1
end
if (f[1] == "UnitDisplays") and (height == 1) and (val ~= "<not set>") then height = 2 end
local line = Div{parent=setting_list,height=height,fg_bg=c}
TextBox{parent=line,text=f[2],width=string.len(f[2]),fg_bg=cpair(colors.black,line.get_fg_bg().bkg)}
local textbox
if height > 1 then
textbox = TextBox{parent=line,x=1,y=2,text=val,height=height-1}
else
textbox = TextBox{parent=line,x=label_w+1,y=1,text=val,alignment=RIGHT}
end
if f[1] == "AuthKey" then self.auth_key_textbox = textbox end
end
end
--#endregion
end
return system

File diff suppressed because it is too large Load Diff

View File

@@ -111,12 +111,12 @@ function coordinator.load_config()
---@class monitors_struct
local monitors = {
main = nil, ---@type table|nil
main = nil, ---@type Monitor|nil
main_name = "",
flow = nil, ---@type table|nil
flow = nil, ---@type Monitor|nil
flow_name = "",
unit_displays = {},
unit_name_map = {}
unit_displays = {}, ---@type Monitor[]
unit_name_map = {} ---@type string[]
}
local mon_cfv = util.new_validator()
@@ -387,10 +387,14 @@ function coordinator.comms(version, nic, sv_watchdog)
end
-- send the auto process control configuration with a start command
---@param auto_cfg sys_auto_config configuration
function public.send_auto_start(auto_cfg)
---@param mode PROCESS process control mode
---@param burn_target number burn rate target
---@param charge_target number charge level target
---@param gen_target number generation rate target
---@param limits number[] unit burn rate limits
function public.send_auto_start(mode, burn_target, charge_target, gen_target, limits)
_send_sv(PROTOCOL.SCADA_CRDN, CRDN_TYPE.FAC_CMD, {
FAC_COMMAND.START, auto_cfg.mode, auto_cfg.burn_target, auto_cfg.charge_target, auto_cfg.gen_target, auto_cfg.limits
FAC_COMMAND.START, mode, burn_target, charge_target, gen_target, limits
})
end
@@ -578,7 +582,7 @@ function coordinator.comms(version, nic, sv_watchdog)
if cmd == FAC_COMMAND.SCRAM_ALL then
process.fac_ack(cmd, ack)
elseif cmd == FAC_COMMAND.STOP then
iocontrol.get_db().facility.stop_ack(ack)
process.fac_ack(cmd, ack)
elseif cmd == FAC_COMMAND.START then
if packet.length == 7 then
process.start_ack_handle({ table.unpack(packet.data, 2) })
@@ -623,7 +627,7 @@ function coordinator.comms(version, nic, sv_watchdog)
local unit_id = packet.data[2]
local ack = packet.data[3] == true
local unit = iocontrol.get_db().units[unit_id] ---@type ioctl_unit
local unit = iocontrol.get_db().units[unit_id]
if unit ~= nil then
if cmd == UNIT_COMMAND.SCRAM then

View File

@@ -29,15 +29,6 @@ local iocontrol = {}
---@class ioctl
local io = {}
-- luacheck: no unused args
-- placeholder acknowledge function for type hinting
---@param success boolean
---@diagnostic disable-next-line: unused-local
local function __generic_ack(success) end
-- luacheck: unused args
-- initialize front panel PSIL
---@param firmware_v string coordinator version
---@param comms_v string comms version
@@ -93,6 +84,8 @@ function iocontrol.init(conf, comms, temp_scale, energy_scale)
all_sys_ok = false,
rtu_count = 0,
status_lines = { "", "" },
auto_ready = false,
auto_active = false,
auto_ramping = false,
@@ -115,25 +108,21 @@ function iocontrol.init(conf, comms, temp_scale, energy_scale)
radiation = types.new_zero_radiation_reading(),
save_cfg_ack = __generic_ack,
start_ack = __generic_ack,
stop_ack = __generic_ack,
save_cfg_ack = nil, ---@type fun(success: boolean)
---@type { [TONE]: boolean }
alarm_tones = { false, false, false, false, false, false, false, false },
ps = psil.create(),
induction_ps_tbl = {},
induction_data_tbl = {},
induction_ps_tbl = {}, ---@type psil[]
induction_data_tbl = {}, ---@type imatrix_session_db[]
sps_ps_tbl = {},
sps_data_tbl = {},
sps_ps_tbl = {}, ---@type psil[]
sps_data_tbl = {}, ---@type sps_session_db[]
tank_ps_tbl = {},
tank_data_tbl = {},
env_d_ps = psil.create(),
env_d_data = {}
tank_ps_tbl = {}, ---@type psil[]
tank_data_tbl = {} ---@type dynamicv_session_db[]
}
-- create induction and SPS tables (currently only 1 of each is supported)
@@ -151,7 +140,7 @@ function iocontrol.init(conf, comms, temp_scale, energy_scale)
end
-- create unit data structures
io.units = {}
io.units = {} ---@type ioctl_unit[]
for i = 1, conf.num_units do
local function ack(alarm) process.ack_alarm(i, alarm) end
local function reset(alarm) process.reset_alarm(i, alarm) end
@@ -160,13 +149,21 @@ function iocontrol.init(conf, comms, temp_scale, energy_scale)
local entry = {
unit_id = i,
connected = false,
rtu_hw = { boilers = {}, turbines = {} },
rtu_hw = {
boilers = {}, ---@type { connected: boolean, faulted: boolean }[]
turbines = {} ---@type { connected: boolean, faulted: boolean }[]
},
num_boilers = 0,
num_turbines = 0,
num_snas = 0,
has_tank = conf.cooling.r_cool[i].TankConnection,
status_lines = { "", "" },
auto_ready = false,
auto_degraded = false,
control_state = false,
burn_rate_cmd = 0.0,
radiation = types.new_zero_radiation_reading(),
@@ -208,7 +205,7 @@ function iocontrol.init(conf, comms, temp_scale, energy_scale)
t_trip = { ack = function () ack(12) end, reset = function () reset(12) end }
},
---@type alarms
---@type { [ALARM]: ALARM_STATE }
alarms = {
ALARM_STATE.INACTIVE, -- containment breach
ALARM_STATE.INACTIVE, -- containment radiation
@@ -227,16 +224,16 @@ function iocontrol.init(conf, comms, temp_scale, energy_scale)
annunciator = {}, ---@type annunciator
unit_ps = psil.create(),
reactor_data = {}, ---@type reactor_db
reactor_data = types.new_reactor_db(),
boiler_ps_tbl = {},
boiler_data_tbl = {},
boiler_ps_tbl = {}, ---@type psil[]
boiler_data_tbl = {}, ---@type boilerv_session_db[]
turbine_ps_tbl = {},
turbine_data_tbl = {},
turbine_ps_tbl = {}, ---@type psil[]
turbine_data_tbl = {}, ---@type turbinev_session_db[]
tank_ps_tbl = {},
tank_data_tbl = {}
tank_ps_tbl = {}, ---@type psil[]
tank_data_tbl = {} ---@type dynamicv_session_db[]
}
-- on other facility modes, overwrite unit TANK option with facility tank defs
@@ -357,8 +354,8 @@ end
-- record and publish multiblock RTU build data
---@param id integer
---@param entry table
---@param data_tbl table
---@param ps_tbl table
---@param data_tbl (imatrix_session_db|sps_session_db|dynamicv_session_db|turbinev_session_db|boilerv_session_db)[]
---@param ps_tbl psil[]
---@param create boolean? true to create an entry if non exists, false to fail on missing
---@return boolean ok true if data saved, false if invalid ID
local function _record_multiblock_build(id, entry, data_tbl, ps_tbl, create)
@@ -369,8 +366,8 @@ local function _record_multiblock_build(id, entry, data_tbl, ps_tbl, create)
data_tbl[id] = {}
end
data_tbl[id].formed = entry[1] ---@type boolean
data_tbl[id].build = entry[2] ---@type table
data_tbl[id].formed = entry[1]
data_tbl[id].build = entry[2]
ps_tbl[id].publish("formed", entry[1])
@@ -444,7 +441,7 @@ function iocontrol.record_unit_builds(builds)
else
-- reactor build
if type(build.reactor) == "table" then
unit.reactor_data.mek_struct = build.reactor ---@type mek_struct
unit.reactor_data.mek_struct = build.reactor
for key, val in pairs(unit.reactor_data.mek_struct) do
unit.unit_ps.publish(key, val)
end
@@ -497,10 +494,10 @@ end
---@param ps psil
---@return boolean is_faulted
local function _record_multiblock_status(entry, data, ps)
local is_faulted = entry[1] ---@type boolean
data.formed = entry[2] ---@type boolean
data.state = entry[3] ---@type table
data.tanks = entry[4] ---@type table
local is_faulted = entry[1]
data.formed = entry[2]
data.state = entry[3]
data.tanks = entry[4]
ps.publish("formed", data.formed)
ps.publish("faulted", is_faulted)
@@ -549,8 +546,8 @@ function iocontrol.update_facility_status(status)
fac.ascram_status.radiation = ctl_status[10]
fac.ascram_status.gen_fault = ctl_status[11]
fac.status_line_1 = ctl_status[12]
fac.status_line_2 = ctl_status[13]
fac.status_lines[1] = ctl_status[12]
fac.status_lines[2] = ctl_status[13]
fac.ps.publish("all_sys_ok", fac.all_sys_ok)
fac.ps.publish("auto_ready", fac.auto_ready)
@@ -563,8 +560,8 @@ function iocontrol.update_facility_status(status)
fac.ps.publish("as_crit_alarm", fac.ascram_status.crit_alarm)
fac.ps.publish("as_radiation", fac.ascram_status.radiation)
fac.ps.publish("as_gen_fault", fac.ascram_status.gen_fault)
fac.ps.publish("status_line_1", fac.status_line_1)
fac.ps.publish("status_line_2", fac.status_line_2)
fac.ps.publish("status_line_1", fac.status_lines[1])
fac.ps.publish("status_line_2", fac.status_lines[2])
local group_map = ctl_status[14]
@@ -600,8 +597,8 @@ function iocontrol.update_facility_status(status)
-- power statistics
if type(rtu_statuses.power) == "table" and #rtu_statuses.power == 4 then
local data = fac.induction_data_tbl[1] ---@type imatrix_session_db
local ps = fac.induction_ps_tbl[1] ---@type psil
local data = fac.induction_data_tbl[1]
local ps = fac.induction_ps_tbl[1]
local chg = tonumber(rtu_statuses.power[1])
local in_f = tonumber(rtu_statuses.power[2])
@@ -636,8 +633,8 @@ function iocontrol.update_facility_status(status)
for id, matrix in pairs(rtu_statuses.induction) do
if type(fac.induction_data_tbl[id]) == "table" then
local data = fac.induction_data_tbl[id] ---@type imatrix_session_db
local ps = fac.induction_ps_tbl[id] ---@type psil
local data = fac.induction_data_tbl[id]
local ps = fac.induction_ps_tbl[id]
local rtu_faulted = _record_multiblock_status(matrix, data, ps)
@@ -674,8 +671,8 @@ function iocontrol.update_facility_status(status)
for id, sps in pairs(rtu_statuses.sps) do
if type(fac.sps_data_tbl[id]) == "table" then
local data = fac.sps_data_tbl[id] ---@type sps_session_db
local ps = fac.sps_ps_tbl[id] ---@type psil
local data = fac.sps_data_tbl[id]
local ps = fac.sps_ps_tbl[id]
local rtu_faulted = _record_multiblock_status(sps, data, ps)
@@ -712,8 +709,8 @@ function iocontrol.update_facility_status(status)
for id, tank in pairs(rtu_statuses.tanks) do
if type(fac.tank_data_tbl[id]) == "table" then
local data = fac.tank_data_tbl[id] ---@type dynamicv_session_db
local ps = fac.tank_ps_tbl[id] ---@type psil
local data = fac.tank_data_tbl[id]
local ps = fac.tank_ps_tbl[id]
local rtu_faulted = _record_multiblock_status(tank, data, ps)
@@ -812,7 +809,7 @@ function iocontrol.update_unit_statuses(statuses)
for i = 1, #statuses do
local log_header = util.c("iocontrol.update_unit_statuses[unit ", i, "]: ")
local unit = io.units[i] ---@type ioctl_unit
local unit = io.units[i]
local status = statuses[i]
local burn_rate = 0.0
@@ -848,14 +845,26 @@ function iocontrol.update_unit_statuses(statuses)
log.debug(log_header .. "reactor general status length mismatch")
end
unit.reactor_data.rps_status = rps_status ---@type rps_status
unit.reactor_data.mek_status = mek_status ---@type mek_status
for key, val in pairs(unit.reactor_data) do
if key ~= "rps_status" and key ~= "mek_struct" and key ~= "mek_status" then
unit.unit_ps.publish(key, val)
end
end
unit.reactor_data.rps_status = rps_status
for key, val in pairs(rps_status) do
unit.unit_ps.publish(key, val)
end
if next(mek_status) then
unit.reactor_data.mek_status = mek_status
for key, val in pairs(mek_status) do
unit.unit_ps.publish(key, val)
end
end
-- if status hasn't been received, mek_status = {}
if type(unit.reactor_data.mek_status.act_burn_rate) == "number" then
burn_rate = unit.reactor_data.mek_status.act_burn_rate
burn_rate_sum = burn_rate_sum + burn_rate
end
if unit.reactor_data.mek_status.status then
unit.unit_ps.publish("computed_status", 5) -- running
@@ -873,24 +882,6 @@ function iocontrol.update_unit_statuses(statuses)
end
end
for key, val in pairs(unit.reactor_data) do
if key ~= "rps_status" and key ~= "mek_struct" and key ~= "mek_status" then
unit.unit_ps.publish(key, val)
end
end
if type(unit.reactor_data.rps_status) == "table" then
for key, val in pairs(unit.reactor_data.rps_status) do
unit.unit_ps.publish(key, val)
end
end
if type(unit.reactor_data.mek_status) == "table" then
for key, val in pairs(unit.reactor_data.mek_status) do
unit.unit_ps.publish(key, val)
end
end
unit.connected = true
else
log.debug(log_header .. "reactor status length mismatch")
@@ -917,8 +908,8 @@ function iocontrol.update_unit_statuses(statuses)
for id, boiler in pairs(rtu_statuses.boilers) do
if type(unit.boiler_data_tbl[id]) == "table" then
local data = unit.boiler_data_tbl[id] ---@type boilerv_session_db
local ps = unit.boiler_ps_tbl[id] ---@type psil
local data = unit.boiler_data_tbl[id]
local ps = unit.boiler_ps_tbl[id]
local rtu_faulted = _record_multiblock_status(boiler, data, ps)
unit.rtu_hw.boilers[id].faulted = rtu_faulted
@@ -964,8 +955,8 @@ function iocontrol.update_unit_statuses(statuses)
for id, turbine in pairs(rtu_statuses.turbines) do
if type(unit.turbine_data_tbl[id]) == "table" then
local data = unit.turbine_data_tbl[id] ---@type turbinev_session_db
local ps = unit.turbine_ps_tbl[id] ---@type psil
local data = unit.turbine_data_tbl[id]
local ps = unit.turbine_ps_tbl[id]
local rtu_faulted = _record_multiblock_status(turbine, data, ps)
unit.rtu_hw.turbines[id].faulted = rtu_faulted
@@ -1008,8 +999,8 @@ function iocontrol.update_unit_statuses(statuses)
for id, tank in pairs(rtu_statuses.tanks) do
if type(unit.tank_data_tbl[id]) == "table" then
local data = unit.tank_data_tbl[id] ---@type dynamicv_session_db
local ps = unit.tank_ps_tbl[id] ---@type psil
local data = unit.tank_data_tbl[id]
local ps = unit.tank_ps_tbl[id]
local rtu_faulted = _record_multiblock_status(tank, data, ps)
@@ -1144,15 +1135,19 @@ function iocontrol.update_unit_statuses(statuses)
if type(unit_state) == "table" then
if #unit_state == 8 then
unit.status_lines[1] = unit_state[1]
unit.status_lines[2] = unit_state[2]
unit.auto_ready = unit_state[3]
unit.auto_degraded = unit_state[4]
unit.waste_mode = unit_state[5]
unit.waste_product = unit_state[6]
unit.last_rate_change_ms = unit_state[7]
unit.turbine_flow_stable = unit_state[8]
unit.unit_ps.publish("U_StatusLine1", unit_state[1])
unit.unit_ps.publish("U_StatusLine2", unit_state[2])
unit.unit_ps.publish("U_AutoReady", unit_state[3])
unit.unit_ps.publish("U_AutoDegraded", unit_state[4])
unit.unit_ps.publish("U_StatusLine1", unit.status_lines[1])
unit.unit_ps.publish("U_StatusLine2", unit.status_lines[2])
unit.unit_ps.publish("U_AutoReady", unit.auto_ready)
unit.unit_ps.publish("U_AutoDegraded", unit.auto_degraded)
unit.unit_ps.publish("U_AutoWaste", unit.waste_mode == types.WASTE_MODE.AUTO)
unit.unit_ps.publish("U_WasteMode", unit.waste_mode)
unit.unit_ps.publish("U_WasteProduct", unit.waste_product)

View File

@@ -25,17 +25,17 @@ local pctl = {
control_states = {
---@class sys_auto_config
process = {
mode = PROCESS.INACTIVE,
mode = PROCESS.INACTIVE, ---@type PROCESS
burn_target = 0.0,
charge_target = 0.0,
gen_target = 0.0,
limits = {},
waste_product = PRODUCT.PLUTONIUM,
limits = {}, ---@type number[]
waste_product = PRODUCT.PLUTONIUM, ---@type WASTE_PRODUCT
pu_fallback = false,
sps_low_power = false
},
waste_modes = {},
priority_groups = {}
waste_modes = {}, ---@type WASTE_MODE[]
priority_groups = {} ---@type AUTO_GROUP[]
},
commands = {
unit = {}, ---@type process_command_state[][]
@@ -46,9 +46,10 @@ local pctl = {
---@class process_command_state
---@field active boolean if this command is live
---@field timeout integer expiration time of this command request
---@field requestors table list of callbacks from the requestors
---@field requestors function[] list of callbacks from the requestors
-- write auto process control to config file
---@return boolean saved
local function _write_auto_config()
-- save config
settings.set("ControlStates", pctl.control_states)
@@ -60,6 +61,8 @@ local function _write_auto_config()
return saved
end
--#region Core
-- initialize the process controller
---@param iocontrol ioctl iocontrl system
---@param coord_comms coord_comms coordinator communications
@@ -80,8 +83,8 @@ function process.init(iocontrol, coord_comms)
ctl_proc.limits[i] = 0.1
end
local ctrl_states = settings.get("ControlStates", {})
local config = ctrl_states.process ---@type sys_auto_config
local ctrl_states = settings.get("ControlStates", {}) ---@type sys_control_states
local config = ctrl_states.process
-- facility auto control configuration
if type(config) == "table" then
@@ -103,7 +106,7 @@ function process.init(iocontrol, coord_comms)
pctl.io.facility.ps.publish("process_sps_low_power", ctl_proc.sps_low_power)
for id = 1, math.min(#ctl_proc.limits, pctl.io.facility.num_units) do
local unit = pctl.io.units[id] ---@type ioctl_unit
local unit = pctl.io.units[id]
unit.unit_ps.publish("burn_limit", ctl_proc.limits[id])
end
@@ -116,7 +119,7 @@ function process.init(iocontrol, coord_comms)
end
-- unit waste states
local waste_modes = ctrl_states.waste_modes ---@type table|nil
local waste_modes = ctrl_states.waste_modes
if type(waste_modes) == "table" then
for id, mode in pairs(waste_modes) do
pctl.control_states.waste_modes[id] = mode
@@ -127,7 +130,7 @@ function process.init(iocontrol, coord_comms)
end
-- unit priority groups
local prio_groups = ctrl_states.priority_groups ---@type table|nil
local prio_groups = ctrl_states.priority_groups
if type(prio_groups) == "table" then
for id, group in pairs(prio_groups) do
pctl.control_states.priority_groups[id] = group
@@ -180,6 +183,36 @@ function process.create_handle()
end
end
-- start automatic process control with current settings
function handle.process_start()
if f_request(F_CMD.START, handle.fac_ack.on_start) then
local p = pctl.control_states.process
pctl.comms.send_auto_start(p.mode, p.burn_target, p.charge_target, p.gen_target, p.limits)
log.debug("PROCESS: START AUTO CTRL")
end
end
-- start automatic process control with remote settings that haven't been set on the coordinator
---@param mode PROCESS process control mode
---@param burn_target number burn rate target
---@param charge_target number charge level target
---@param gen_target number generation rate target
---@param limits number[] unit burn rate limits
function handle.process_start_remote(mode, burn_target, charge_target, gen_target, limits)
if f_request(F_CMD.START, handle.fac_ack.on_start) then
pctl.comms.send_auto_start(mode, burn_target, charge_target, gen_target, limits)
log.debug("PROCESS: START AUTO CTRL")
end
end
-- stop process control
function handle.process_stop()
if f_request(F_CMD.STOP, handle.fac_ack.on_stop) then
pctl.comms.send_fac_command(F_CMD.STOP)
log.debug("PROCESS: STOP AUTO CTRL")
end
end
handle.fac_ack = {}
-- luacheck: no unused args
@@ -194,6 +227,16 @@ function process.create_handle()
---@diagnostic disable-next-line: unused-local
function handle.fac_ack.on_ack_alarms(success) end
-- facility auto control start ack, override to implement
---@param success boolean
---@diagnostic disable-next-line: unused-local
function handle.fac_ack.on_start(success) end
-- facility auto control stop ack, override to implement
---@param success boolean
---@diagnostic disable-next-line: unused-local
function handle.fac_ack.on_stop(success) end
-- luacheck: unused args
--#endregion
@@ -294,6 +337,14 @@ function process.clear_timed_out()
end
end
-- get the control states table
---@nodiscard
function process.get_control_states() return pctl.control_states end
--#endregion
--#region Command Handling
-- handle a command acknowledgement
---@param cmd_state process_command_state
---@param success boolean if the command was successful
@@ -335,6 +386,21 @@ function process.set_rate(id, rate)
log.debug(util.c("PROCESS: UNIT[", id, "] SET BURN ", rate))
end
-- assign a unit to a group
---@param unit_id integer unit ID
---@param group_id integer|0 group ID or 0 for independent
function process.set_group(unit_id, group_id)
pctl.comms.send_unit_command(U_CMD.SET_GROUP, unit_id, group_id)
log.debug(util.c("PROCESS: UNIT[", unit_id, "] SET GROUP ", group_id))
pctl.control_states.priority_groups[unit_id] = group_id
settings.set("ControlStates", pctl.control_states)
if not settings.save("/coordinator.settings") then
log.error("process.set_group(): failed to save coordinator settings file")
end
end
-- set waste mode
---@param id integer unit ID
---@param mode integer waste mode
@@ -369,39 +435,12 @@ function process.reset_alarm(id, alarm)
log.debug(util.c("PROCESS: UNIT[", id, "] RESET ALARM ", alarm))
end
-- assign a unit to a group
---@param unit_id integer unit ID
---@param group_id integer|0 group ID or 0 for independent
function process.set_group(unit_id, group_id)
pctl.comms.send_unit_command(U_CMD.SET_GROUP, unit_id, group_id)
log.debug(util.c("PROCESS: UNIT[", unit_id, "] SET GROUP ", group_id))
pctl.control_states.priority_groups[unit_id] = group_id
settings.set("ControlStates", pctl.control_states)
if not settings.save("/coordinator.settings") then
log.error("process.set_group(): failed to save coordinator settings file")
end
end
--#endregion
--------------------------
-- AUTO PROCESS CONTROL --
--------------------------
-- start automatic process control
function process.start_auto()
pctl.comms.send_auto_start(pctl.control_states.process)
log.debug("PROCESS: START AUTO CTL")
end
-- stop automatic process control
function process.stop_auto()
pctl.comms.send_fac_command(F_CMD.STOP)
log.debug("PROCESS: STOP AUTO CTL")
end
-- set automatic process control waste mode
---@param product WASTE_PRODUCT waste product for auto control
function process.set_process_waste(product)
@@ -439,11 +478,11 @@ function process.set_sps_low_power(enabled)
end
-- save process control settings
---@param mode PROCESS control mode
---@param mode PROCESS process control mode
---@param burn_target number burn rate target
---@param charge_target number charge target
---@param charge_target number charge level target
---@param gen_target number generation rate target
---@param limits table unit burn rate limits
---@param limits number[] unit burn rate limits
function process.save(mode, burn_target, charge_target, gen_target, limits)
log.debug("PROCESS: SAVE")
@@ -472,9 +511,7 @@ function process.start_ack_handle(response)
for i = 1, math.min(#response[6], pctl.io.facility.num_units) do
ctl_proc.limits[i] = response[6][i]
local unit = pctl.io.units[i] ---@type ioctl_unit
unit.unit_ps.publish("burn_limit", ctl_proc.limits[i])
pctl.io.units[i].unit_ps.publish("burn_limit", ctl_proc.limits[i])
end
pctl.io.facility.ps.publish("process_mode", ctl_proc.mode)
@@ -482,7 +519,9 @@ function process.start_ack_handle(response)
pctl.io.facility.ps.publish("process_charge_target", pctl.io.energy_convert_from_fe(ctl_proc.charge_target))
pctl.io.facility.ps.publish("process_gen_target", pctl.io.energy_convert_from_fe(ctl_proc.gen_target))
pctl.io.facility.start_ack(ack)
_write_auto_config()
process.fac_ack(F_CMD.START, ack)
end
-- record waste product settting after attempting to change it
@@ -506,4 +545,6 @@ function process.sps_lp_ack_handle(response)
pctl.io.facility.ps.publish("process_sps_low_power", response)
end
--#endregion
return process

View File

@@ -19,7 +19,7 @@ local unit_view = require("coordinator.ui.layout.unit_view")
local core = require("graphics.core")
local flasher = require("graphics.flasher")
local DisplayBox = require("graphics.elements.displaybox")
local DisplayBox = require("graphics.elements.DisplayBox")
local log_render = coordinator.log_render
@@ -30,20 +30,20 @@ local renderer = {}
local engine = {
color_mode = 1, ---@type COLOR_MODE
monitors = nil, ---@type monitors_struct|nil
dmesg_window = nil, ---@type table|nil
dmesg_window = nil, ---@type Window|nil
ui_ready = false,
fp_ready = false,
ui = {
front_panel = nil, ---@type graphics_element|nil
main_display = nil, ---@type graphics_element|nil
flow_display = nil, ---@type graphics_element|nil
unit_displays = {}
front_panel = nil, ---@type DisplayBox|nil
main_display = nil, ---@type DisplayBox|nil
flow_display = nil, ---@type DisplayBox|nil
unit_displays = {} ---@type (DisplayBox|nil)[]
},
disable_flow_view = false
}
-- init a display to the "default", but set text scale to 0.5
---@param monitor table monitor
---@param monitor Monitor monitor
local function _init_display(monitor)
monitor.setTextScale(0.5)
monitor.setTextColor(colors.white)
@@ -64,7 +64,7 @@ local function _init_display(monitor)
end
-- print out that the monitor is too small
---@param monitor table monitor
---@param monitor Monitor monitor
local function _print_too_small(monitor)
monitor.setCursorPos(1, 1)
monitor.setBackgroundColor(colors.black)
@@ -275,7 +275,7 @@ function renderer.fp_ready() return engine.fp_ready end
function renderer.ui_ready() return engine.ui_ready end
-- handle a monitor peripheral being disconnected
---@param device table monitor
---@param device Monitor monitor
---@return boolean is_used if the monitor is one of the configured monitors
function renderer.handle_disconnect(device)
local is_used = false
@@ -326,7 +326,7 @@ end
-- handle a monitor peripheral being reconnected
---@param name string monitor name
---@param device table monitor
---@param device Monitor monitor
---@return boolean is_used if the monitor is one of the configured monitors
function renderer.handle_reconnect(name, device)
local is_used = false
@@ -373,7 +373,7 @@ function renderer.handle_resize(name)
if not engine.monitors then return false, false end
if engine.monitors.main_name == name and engine.monitors.main then
local device = engine.monitors.main ---@type table
local device = engine.monitors.main ---@type Monitor
-- this is necessary if the bottom left block was broken and on reconnect
_init_display(device)
@@ -416,7 +416,7 @@ function renderer.handle_resize(name)
end
else engine.dmesg_window.redraw() end
elseif engine.monitors.flow_name == name and engine.monitors.flow then
local device = engine.monitors.flow ---@type table
local device = engine.monitors.flow ---@type Monitor
-- this is necessary if the bottom left block was broken and on reconnect
_init_display(device)

View File

@@ -13,7 +13,7 @@ local self = {
nic = nil, ---@type nic
config = nil, ---@type crd_config
next_id = 0,
sessions = {}
sessions = {} ---@type pkt_session_struct[]
}
-- PRIVATE FUNCTIONS --
@@ -129,7 +129,7 @@ end
---@param timer_event number
function apisessions.check_all_watchdogs(timer_event)
for i = 1, #self.sessions do
local session = self.sessions[i] ---@type pkt_session_struct
local session = self.sessions[i]
if session.open then
local triggered = session.instance.check_wd(timer_event)
if triggered then
@@ -143,7 +143,7 @@ end
-- iterate all the API sessions
function apisessions.iterate_all()
for i = 1, #self.sessions do
local session = self.sessions[i] ---@type pkt_session_struct
local session = self.sessions[i]
if session.open and session.instance.iterate() then
_api_handle_outq(session)
@@ -168,7 +168,7 @@ end
-- close all open connections
function apisessions.close_all()
for i = 1, #self.sessions do
local session = self.sessions[i] ---@type pkt_session_struct
local session = self.sessions[i]
if session.open then _shutdown(session) end
end

View File

@@ -108,14 +108,20 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout)
-- link callback transmissions
self.proc_handle.fac_ack.on_scram = function (success) _send(CRDN_TYPE.FAC_CMD, { FAC_COMMAND.SCRAM_ALL, success }) end
self.proc_handle.fac_ack.on_ack_alarms = function (success) _send(CRDN_TYPE.FAC_CMD, { FAC_COMMAND.ACK_ALL_ALARMS, success }) end
local f_ack = self.proc_handle.fac_ack
f_ack.on_scram = function (success) _send(CRDN_TYPE.FAC_CMD, { FAC_COMMAND.SCRAM_ALL, success }) end
f_ack.on_ack_alarms = function (success) _send(CRDN_TYPE.FAC_CMD, { FAC_COMMAND.ACK_ALL_ALARMS, success }) end
f_ack.on_start = function (success) _send(CRDN_TYPE.FAC_CMD, { FAC_COMMAND.START, success }) end
f_ack.on_stop = function (success) _send(CRDN_TYPE.FAC_CMD, { FAC_COMMAND.STOP, success }) end
for u = 1, iocontrol.get_db().facility.num_units do
self.proc_handle.unit_ack[u].on_start = function (success) _send(CRDN_TYPE.UNIT_CMD, { UNIT_COMMAND.START, u, success }) end
self.proc_handle.unit_ack[u].on_scram = function (success) _send(CRDN_TYPE.UNIT_CMD, { UNIT_COMMAND.SCRAM, u, success }) end
self.proc_handle.unit_ack[u].on_rps_reset = function (success) _send(CRDN_TYPE.UNIT_CMD, { UNIT_COMMAND.RESET_RPS, u, success }) end
self.proc_handle.unit_ack[u].on_ack_alarms = function (success) _send(CRDN_TYPE.UNIT_CMD, { UNIT_COMMAND.ACK_ALL_ALARMS, u, success }) end
local u_ack = self.proc_handle.unit_ack[u]
u_ack.on_start = function (success) _send(CRDN_TYPE.UNIT_CMD, { UNIT_COMMAND.START, u, success }) end
u_ack.on_scram = function (success) _send(CRDN_TYPE.UNIT_CMD, { UNIT_COMMAND.SCRAM, u, success }) end
u_ack.on_rps_reset = function (success) _send(CRDN_TYPE.UNIT_CMD, { UNIT_COMMAND.RESET_RPS, u, success }) end
u_ack.on_ack_alarms = function (success) _send(CRDN_TYPE.UNIT_CMD, { UNIT_COMMAND.ACK_ALL_ALARMS, u, success }) end
end
-- handle a packet
@@ -147,7 +153,15 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout)
log.info(log_tag .. "FAC SCRAM ALL")
self.proc_handle.fac_scram()
elseif cmd == FAC_COMMAND.STOP then
log.info(log_tag .. "STOP PROCESS CTRL")
self.proc_handle.process_stop()
elseif cmd == FAC_COMMAND.START then
if pkt.length == 6 then
log.info(log_tag .. "START PROCESS CTRL")
self.proc_handle.process_start_remote(pkt.data[2], pkt.data[3], pkt.data[4], pkt.data[5], pkt.data[6])
else
log.debug(log_tag .. "CRDN auto start (with configuration) packet length mismatch")
end
elseif cmd == FAC_COMMAND.ACK_ALL_ALARMS then
log.info(log_tag .. "FAC ACK ALL ALARMS")
self.proc_handle.fac_ack_alarms()
@@ -191,6 +205,12 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout)
elseif cmd == UNIT_COMMAND.ACK_ALARM then
elseif cmd == UNIT_COMMAND.RESET_ALARM then
elseif cmd == UNIT_COMMAND.SET_GROUP then
if pkt.length == 3 then
log.info(util.c(log_tag, "UNIT[", uid, "] SET GROUP ", pkt.data[3]))
process.set_group(uid, pkt.data[3])
else
log.debug(log_tag .. "CRDN unit set group missing option")
end
else
log.debug(log_tag .. "CRDN unit command unknown")
end
@@ -217,7 +237,7 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout)
_send(CRDN_TYPE.API_GET_FAC, data)
elseif pkt.type == CRDN_TYPE.API_GET_UNIT then
if pkt.length == 1 and type(pkt.data[1]) == "number" then
local u = db.units[pkt.data[1]] ---@type ioctl_unit
local u = db.units[pkt.data[1]]
if u then
local data = {
@@ -238,6 +258,58 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout)
_send(CRDN_TYPE.API_GET_UNIT, data)
end
end
elseif pkt.type == CRDN_TYPE.API_GET_CTRL then
local data = {}
for i = 1, #db.units do
local u = db.units[i]
data[i] = {
u.connected,
u.reactor_data.rps_tripped,
u.reactor_data.mek_status.status,
u.reactor_data.mek_status.temp,
u.reactor_data.mek_status.burn_rate,
u.reactor_data.mek_status.act_burn_rate,
u.reactor_data.mek_struct.max_burn,
u.annunciator.AutoControl,
u.a_group
}
end
_send(CRDN_TYPE.API_GET_CTRL, data)
elseif pkt.type == CRDN_TYPE.API_GET_PROC then
local data = {}
local fac = db.facility
local proc = process.get_control_states().process
-- unit data
for i = 1, #db.units do
local u = db.units[i]
data[i] = {
u.reactor_data.mek_status.status,
u.reactor_data.mek_struct.max_burn,
proc.limits[i],
u.auto_ready,
u.auto_degraded,
u.annunciator.AutoControl,
u.a_group
}
end
-- facility data
data[#db.units + 1] = {
fac.status_lines,
{ fac.auto_ready, fac.auto_active, fac.auto_ramping, fac.auto_saturated },
fac.auto_scram,
fac.ascram_status,
{ proc.mode, proc.burn_target, proc.charge_target, proc.gen_target }
}
_send(CRDN_TYPE.API_GET_PROC, data)
else
log.debug(log_tag .. "handler received unsupported CRDN packet type " .. pkt.type)
end

View File

@@ -9,7 +9,7 @@ local log = require("scada-common.log")
local sounder = {}
local alarm_ctl = {
speaker = nil,
speaker = nil, ---@type Speaker
volume = 0.5,
stream = audio.new_stream()
}
@@ -24,7 +24,7 @@ local function play()
end
-- initialize the annunciator alarm system
---@param speaker table speaker peripheral
---@param speaker Speaker speaker peripheral
---@param volume number speaker volume
function sounder.init(speaker, volume)
alarm_ctl.speaker = speaker
@@ -36,7 +36,7 @@ function sounder.init(speaker, volume)
end
-- reconnect the speaker peripheral
---@param speaker table speaker peripheral
---@param speaker Speaker speaker peripheral
function sounder.reconnect(speaker)
alarm_ctl.speaker = speaker
alarm_ctl.playing = false
@@ -44,7 +44,7 @@ function sounder.reconnect(speaker)
end
-- set alarm tones
---@param states table alarm tone commands from supervisor
---@param states { [TONE]: boolean } alarm tone commands from supervisor
function sounder.set(states)
-- set tone states
for id = 1, #states do alarm_ctl.stream.set_active(id, states[id]) end

View File

@@ -19,7 +19,7 @@ local renderer = require("coordinator.renderer")
local sounder = require("coordinator.sounder")
local threads = require("coordinator.threads")
local COORDINATOR_VERSION = "v1.5.8"
local COORDINATOR_VERSION = "v1.5.13"
local CHUNK_LOAD_DELAY_S = 30.0
@@ -152,7 +152,7 @@ local function main()
-- core coordinator devices
crd_dev = {
modem = ppm.get_wireless_modem(),
speaker = ppm.get_device("speaker")
speaker = ppm.get_device("speaker") ---@type Speaker|nil
},
-- system objects

View File

@@ -68,6 +68,7 @@ function threads.thread__main(smem)
if type ~= nil and device ~= nil then
if type == "modem" then
---@cast device Modem
-- we only really care if this is our wireless modem
-- if it is another modem, handle other peripheral losses separately
if nic.is_modem(device) then
@@ -91,8 +92,10 @@ function threads.thread__main(smem)
log_sys("non-comms modem disconnected")
end
elseif type == "monitor" then
---@cast device Monitor
smem.q.mq_render.push_data(MQ__RENDER_DATA.MON_DISCONNECT, device)
elseif type == "speaker" then
---@cast device Speaker
log_sys("lost alarm sounder speaker")
iocontrol.fp_has_speaker(false)
end
@@ -102,6 +105,7 @@ function threads.thread__main(smem)
if type ~= nil and device ~= nil then
if type == "modem" then
---@cast device Modem
if device.isWireless() and not nic.is_connected() then
-- reconnected modem
log_sys("comms modem reconnected")
@@ -113,8 +117,10 @@ function threads.thread__main(smem)
log_sys("wired modem reconnected")
end
elseif type == "monitor" then
---@cast device Monitor
smem.q.mq_render.push_data(MQ__RENDER_DATA.MON_CONNECT, { name = param1, device = device })
elseif type == "speaker" then
---@cast device Speaker
log_sys("alarm sounder speaker reconnected")
sounder.reconnect(device)
iocontrol.fp_has_speaker(true)

View File

@@ -4,18 +4,18 @@ local style = require("coordinator.ui.style")
local core = require("graphics.core")
local Rectangle = require("graphics.elements.rectangle")
local TextBox = require("graphics.elements.textbox")
local Rectangle = require("graphics.elements.Rectangle")
local TextBox = require("graphics.elements.TextBox")
local DataIndicator = require("graphics.elements.indicators.data")
local StateIndicator = require("graphics.elements.indicators.state")
local VerticalBar = require("graphics.elements.indicators.vbar")
local DataIndicator = require("graphics.elements.indicators.DataIndicator")
local StateIndicator = require("graphics.elements.indicators.StateIndicator")
local VerticalBar = require("graphics.elements.indicators.VerticalBar")
local cpair = core.cpair
local border = core.border
-- new boiler view
---@param root graphics_element parent
---@param root Container parent
---@param x integer top left x
---@param y integer top left y
---@param ps psil ps interface

View File

@@ -6,15 +6,15 @@ local style = require("coordinator.ui.style")
local core = require("graphics.core")
local Div = require("graphics.elements.div")
local Rectangle = require("graphics.elements.rectangle")
local TextBox = require("graphics.elements.textbox")
local Div = require("graphics.elements.Div")
local Rectangle = require("graphics.elements.Rectangle")
local TextBox = require("graphics.elements.TextBox")
local DataIndicator = require("graphics.elements.indicators.data")
local IndicatorLight = require("graphics.elements.indicators.light")
local PowerIndicator = require("graphics.elements.indicators.power")
local StateIndicator = require("graphics.elements.indicators.state")
local VerticalBar = require("graphics.elements.indicators.vbar")
local DataIndicator = require("graphics.elements.indicators.DataIndicator")
local IndicatorLight = require("graphics.elements.indicators.IndicatorLight")
local PowerIndicator = require("graphics.elements.indicators.PowerIndicator")
local StateIndicator = require("graphics.elements.indicators.StateIndicator")
local VerticalBar = require("graphics.elements.indicators.VerticalBar")
local cpair = core.cpair
local border = core.border
@@ -22,7 +22,7 @@ local border = core.border
local ALIGN = core.ALIGN
-- new induction matrix view
---@param root graphics_element parent
---@param root Container parent
---@param x integer top left x
---@param y integer top left y
---@param data imatrix_session_db matrix data

View File

@@ -8,17 +8,17 @@ local style = require("coordinator.ui.style")
local core = require("graphics.core")
local Div = require("graphics.elements.div")
local TextBox = require("graphics.elements.textbox")
local Div = require("graphics.elements.Div")
local TextBox = require("graphics.elements.TextBox")
local DataIndicator = require("graphics.elements.indicators.data")
local DataIndicator = require("graphics.elements.indicators.DataIndicator")
local ALIGN = core.ALIGN
local cpair = core.cpair
-- create a pocket list entry
---@param parent graphics_element parent
---@param parent ListBox parent
---@param id integer PKT session ID
local function init(parent, id)
local s_hi_box = style.fp_theme.highlight_box
@@ -29,7 +29,7 @@ local function init(parent, id)
local ps = iocontrol.get_db().fp.ps
-- root div
local root = Div{parent=parent,x=2,y=2,height=4,width=parent.get_width()-2,hidden=true}
local root = Div{parent=parent,x=2,y=2,height=4,width=parent.get_width()-2}
local entry = Div{parent=root,x=2,y=1,height=3,fg_bg=s_hi_bright}
local ps_prefix = "pkt_" .. id .. "_"

View File

@@ -8,20 +8,20 @@ local style = require("coordinator.ui.style")
local core = require("graphics.core")
local Div = require("graphics.elements.div")
local Rectangle = require("graphics.elements.rectangle")
local TextBox = require("graphics.elements.textbox")
local Div = require("graphics.elements.Div")
local Rectangle = require("graphics.elements.Rectangle")
local TextBox = require("graphics.elements.TextBox")
local DataIndicator = require("graphics.elements.indicators.data")
local IndicatorLight = require("graphics.elements.indicators.light")
local RadIndicator = require("graphics.elements.indicators.rad")
local StateIndicator = require("graphics.elements.indicators.state")
local TriIndicatorLight = require("graphics.elements.indicators.trilight")
local DataIndicator = require("graphics.elements.indicators.DataIndicator")
local IndicatorLight = require("graphics.elements.indicators.IndicatorLight")
local RadIndicator = require("graphics.elements.indicators.RadIndicator")
local StateIndicator = require("graphics.elements.indicators.StateIndicator")
local TriIndicatorLight = require("graphics.elements.indicators.TriIndicatorLight")
local Checkbox = require("graphics.elements.controls.checkbox")
local HazardButton = require("graphics.elements.controls.hazard_button")
local RadioButton = require("graphics.elements.controls.radio_button")
local SpinboxNumeric = require("graphics.elements.controls.spinbox_numeric")
local Checkbox = require("graphics.elements.controls.Checkbox")
local HazardButton = require("graphics.elements.controls.HazardButton")
local NumericSpinbox = require("graphics.elements.controls.NumericSpinbox")
local RadioButton = require("graphics.elements.controls.RadioButton")
local ALIGN = core.ALIGN
@@ -33,7 +33,7 @@ local bw_fg_bg = style.bw_fg_bg
local period = core.flasher.PERIOD
-- new process control view
---@param root graphics_element parent
---@param root Container parent
---@param x integer top left x
---@param y integer top left y
local function new_view(root, x, y)
@@ -131,7 +131,7 @@ local function new_view(root, x, y)
TextBox{parent=burn_tag,x=2,y=2,text="Burn Target",width=7,height=2}
local burn_target = Div{parent=targets,x=9,y=1,width=23,height=3,fg_bg=s_hi_box}
local b_target = SpinboxNumeric{parent=burn_target,x=11,y=1,whole_num_precision=4,fractional_precision=1,min=0.1,arrow_fg_bg=arrow_fg_bg,arrow_disable=style.theme.disabled}
local b_target = NumericSpinbox{parent=burn_target,x=11,y=1,whole_num_precision=4,fractional_precision=1,min=0.1,arrow_fg_bg=arrow_fg_bg,arrow_disable=style.theme.disabled}
TextBox{parent=burn_target,x=18,y=2,text="mB/t",fg_bg=style.theme.label_fg}
local burn_sum = DataIndicator{parent=targets,x=9,y=4,label="",format="%18.1f",value=0,unit="mB/t",commas=true,lu_colors=black,width=23,fg_bg=blk_brn}
@@ -142,7 +142,7 @@ local function new_view(root, x, y)
TextBox{parent=chg_tag,x=2,y=2,text="Charge Target",width=7,height=2}
local chg_target = Div{parent=targets,x=9,y=6,width=23,height=3,fg_bg=s_hi_box}
local c_target = SpinboxNumeric{parent=chg_target,x=2,y=1,whole_num_precision=15,fractional_precision=0,min=0,arrow_fg_bg=arrow_fg_bg,arrow_disable=style.theme.disabled}
local c_target = NumericSpinbox{parent=chg_target,x=2,y=1,whole_num_precision=15,fractional_precision=0,min=0,arrow_fg_bg=arrow_fg_bg,arrow_disable=style.theme.disabled}
TextBox{parent=chg_target,x=18,y=2,text="M"..db.energy_label,fg_bg=style.theme.label_fg}
local cur_charge = DataIndicator{parent=targets,x=9,y=9,label="",format="%19d",value=0,unit="M"..db.energy_label,commas=true,lu_colors=black,width=23,fg_bg=blk_brn}
@@ -153,7 +153,7 @@ local function new_view(root, x, y)
TextBox{parent=gen_tag,x=2,y=2,text="Gen. Target",width=7,height=2}
local gen_target = Div{parent=targets,x=9,y=11,width=23,height=3,fg_bg=s_hi_box}
local g_target = SpinboxNumeric{parent=gen_target,x=8,y=1,whole_num_precision=9,fractional_precision=0,min=0,arrow_fg_bg=arrow_fg_bg,arrow_disable=style.theme.disabled}
local g_target = NumericSpinbox{parent=gen_target,x=8,y=1,whole_num_precision=9,fractional_precision=0,min=0,arrow_fg_bg=arrow_fg_bg,arrow_disable=style.theme.disabled}
TextBox{parent=gen_target,x=18,y=2,text="k"..db.energy_label.."/t",fg_bg=style.theme.label_fg}
local cur_gen = DataIndicator{parent=targets,x=9,y=14,label="",format="%17d",value=0,unit="k"..db.energy_label.."/t",commas=true,lu_colors=black,width=23,fg_bg=blk_brn}
@@ -177,7 +177,7 @@ local function new_view(root, x, y)
local cur_lu = style.theme.disabled
if i <= facility.num_units then
unit = units[i] ---@type ioctl_unit
unit = units[i]
tag_fg_bg = cpair(colors.black, colors.lightBlue)
lim_fg_bg = s_hi_box
label_fg = style.theme.label_fg
@@ -191,7 +191,7 @@ local function new_view(root, x, y)
TextBox{parent=unit_tag,x=2,y=2,text="Unit "..i.." Limit",width=7,height=2}
local lim_ctl = Div{parent=limit_div,x=9,y=_y,width=14,height=3,fg_bg=s_hi_box}
local lim = SpinboxNumeric{parent=lim_ctl,x=2,y=1,whole_num_precision=4,fractional_precision=1,min=0.1,arrow_fg_bg=arrow_fg_bg,arrow_disable=style.theme.disabled,fg_bg=lim_fg_bg}
local lim = NumericSpinbox{parent=lim_ctl,x=2,y=1,whole_num_precision=4,fractional_precision=1,min=0.1,arrow_fg_bg=arrow_fg_bg,arrow_disable=style.theme.disabled,fg_bg=lim_fg_bg}
TextBox{parent=lim_ctl,x=9,y=2,text="mB/t",width=4,fg_bg=label_fg}
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(cur_lu,cur_lu),width=14,fg_bg=cur_fg_bg}
@@ -234,7 +234,7 @@ local function new_view(root, x, y)
local degraded = IndicatorLight{parent=lights,x=2,y=3,label="Degraded",colors=cpair(ind_red.fgd,ind_off),flash=true,period=period.BLINK_250_MS}
if i <= facility.num_units then
local unit = units[i] ---@type ioctl_unit
local unit = units[i]
ready.register(unit.unit_ps, "U_AutoReady", ready.update)
degraded.register(unit.unit_ps, "U_AutoDegraded", degraded.update)
@@ -264,24 +264,22 @@ local function new_view(root, x, y)
local limits = {}
for i = 1, #rate_limits do limits[i] = rate_limits[i].get_value() end
process.save(mode.get_value(), b_target.get_value(),
db.energy_convert_to_fe(c_target.get_value()),
db.energy_convert_to_fe(g_target.get_value()),
limits)
process.save(mode.get_value(), b_target.get_value(), db.energy_convert_to_fe(c_target.get_value()),
db.energy_convert_to_fe(g_target.get_value()), limits)
end
-- start automatic control after saving process control settings
local function _start_auto()
_save_cfg()
process.start_auto()
db.process.process_start()
end
local save = HazardButton{parent=auto_controls,x=2,y=2,text="SAVE",accent=colors.purple,dis_colors=dis_colors,callback=_save_cfg,fg_bg=hzd_fg_bg}
local start = HazardButton{parent=auto_controls,x=13,y=2,text="START",accent=colors.lightBlue,dis_colors=dis_colors,callback=_start_auto,fg_bg=hzd_fg_bg}
local stop = HazardButton{parent=auto_controls,x=23,y=2,text="STOP",accent=colors.red,dis_colors=dis_colors,callback=process.stop_auto,fg_bg=hzd_fg_bg}
local stop = HazardButton{parent=auto_controls,x=23,y=2,text="STOP",accent=colors.red,dis_colors=dis_colors,callback=db.process.process_stop,fg_bg=hzd_fg_bg}
facility.start_ack = start.on_response
facility.stop_ack = stop.on_response
db.process.fac_ack.on_start = start.on_response
db.process.fac_ack.on_stop = stop.on_response
function facility.save_cfg_ack(ack)
tcd.dispatch(0.2, function () save.on_response(ack) end)
@@ -323,7 +321,7 @@ local function new_view(root, x, y)
local waste_status = Div{parent=proc,width=24,height=4,x=57,y=1,}
for i = 1, facility.num_units do
local unit = units[i] ---@type ioctl_unit
local unit = units[i]
TextBox{parent=waste_status,y=i,text="U"..i.." Waste",width=8}
local a_waste = IndicatorLight{parent=waste_status,x=10,y=i,label="Auto",colors=ind_wht}

View File

@@ -6,18 +6,18 @@ local style = require("coordinator.ui.style")
local core = require("graphics.core")
local Rectangle = require("graphics.elements.rectangle")
local TextBox = require("graphics.elements.textbox")
local Rectangle = require("graphics.elements.Rectangle")
local TextBox = require("graphics.elements.TextBox")
local DataIndicator = require("graphics.elements.indicators.data")
local HorizontalBar = require("graphics.elements.indicators.hbar")
local StateIndicator = require("graphics.elements.indicators.state")
local DataIndicator = require("graphics.elements.indicators.DataIndicator")
local HorizontalBar = require("graphics.elements.indicators.HorizontalBar")
local StateIndicator = require("graphics.elements.indicators.StateIndicator")
local cpair = core.cpair
local border = core.border
-- create new reactor view
---@param root graphics_element parent
---@param root Container parent
---@param x integer top left x
---@param y integer top left y
---@param ps psil ps interface

View File

@@ -4,19 +4,19 @@ local style = require("coordinator.ui.style")
local core = require("graphics.core")
local Rectangle = require("graphics.elements.rectangle")
local TextBox = require("graphics.elements.textbox")
local Rectangle = require("graphics.elements.Rectangle")
local TextBox = require("graphics.elements.TextBox")
local DataIndicator = require("graphics.elements.indicators.data")
local PowerIndicator = require("graphics.elements.indicators.power")
local StateIndicator = require("graphics.elements.indicators.state")
local VerticalBar = require("graphics.elements.indicators.vbar")
local DataIndicator = require("graphics.elements.indicators.DataIndicator")
local PowerIndicator = require("graphics.elements.indicators.PowerIndicator")
local StateIndicator = require("graphics.elements.indicators.StateIndicator")
local VerticalBar = require("graphics.elements.indicators.VerticalBar")
local cpair = core.cpair
local border = core.border
-- new turbine view
---@param root graphics_element parent
---@param root Container parent
---@param x integer top left x
---@param y integer top left y
---@param ps psil ps interface

View File

@@ -11,23 +11,23 @@ local style = require("coordinator.ui.style")
local core = require("graphics.core")
local Div = require("graphics.elements.div")
local Rectangle = require("graphics.elements.rectangle")
local TextBox = require("graphics.elements.textbox")
local Div = require("graphics.elements.Div")
local Rectangle = require("graphics.elements.Rectangle")
local TextBox = require("graphics.elements.TextBox")
local AlarmLight = require("graphics.elements.indicators.alight")
local CoreMap = require("graphics.elements.indicators.coremap")
local DataIndicator = require("graphics.elements.indicators.data")
local IndicatorLight = require("graphics.elements.indicators.light")
local RadIndicator = require("graphics.elements.indicators.rad")
local TriIndicatorLight = require("graphics.elements.indicators.trilight")
local VerticalBar = require("graphics.elements.indicators.vbar")
local AlarmLight = require("graphics.elements.indicators.AlarmLight")
local CoreMap = require("graphics.elements.indicators.CoreMap")
local DataIndicator = require("graphics.elements.indicators.DataIndicator")
local IndicatorLight = require("graphics.elements.indicators.IndicatorLight")
local RadIndicator = require("graphics.elements.indicators.RadIndicator")
local TriIndicatorLight = require("graphics.elements.indicators.TriIndicatorLight")
local VerticalBar = require("graphics.elements.indicators.VerticalBar")
local HazardButton = require("graphics.elements.controls.hazard_button")
local MultiButton = require("graphics.elements.controls.multi_button")
local PushButton = require("graphics.elements.controls.push_button")
local RadioButton = require("graphics.elements.controls.radio_button")
local SpinboxNumeric = require("graphics.elements.controls.spinbox_numeric")
local HazardButton = require("graphics.elements.controls.HazardButton")
local MultiButton = require("graphics.elements.controls.MultiButton")
local NumericSpinbox = require("graphics.elements.controls.NumericSpinbox")
local PushButton = require("graphics.elements.controls.PushButton")
local RadioButton = require("graphics.elements.controls.RadioButton")
local AUTO_GROUP = types.AUTO_GROUP
@@ -42,7 +42,7 @@ local gry_wht = style.gray_white
local period = core.flasher.PERIOD
-- create a unit view
---@param parent graphics_element parent
---@param parent Container parent
---@param id integer
local function init(parent, id)
local s_hi_box = style.theme.highlight_box
@@ -62,7 +62,7 @@ local function init(parent, id)
local ind_wht = style.ind_wht
local db = iocontrol.get_db()
local unit = db.units[id] ---@type ioctl_unit
local unit = db.units[id]
local f_ps = db.facility.ps
local main = Div{parent=parent,x=1,y=1}
@@ -361,7 +361,7 @@ local function init(parent, id)
----------------------
local burn_control = Div{parent=main,x=12,y=28,width=19,height=3,fg_bg=s_hi_box}
local burn_rate = SpinboxNumeric{parent=burn_control,x=2,y=1,whole_num_precision=4,fractional_precision=1,min=0.1,arrow_fg_bg=arrow_fg_bg,arrow_disable=style.theme.disabled}
local burn_rate = NumericSpinbox{parent=burn_control,x=2,y=1,whole_num_precision=4,fractional_precision=1,min=0.1,arrow_fg_bg=arrow_fg_bg,arrow_disable=style.theme.disabled}
TextBox{parent=burn_control,x=9,y=2,text="mB/t",fg_bg=style.theme.label_fg}
local set_burn = function () unit.set_burn(burn_rate.get_value()) end
@@ -381,13 +381,11 @@ local function init(parent, id)
db.process.unit_ack[id].on_ack_alarms = ack_a.on_response
local function start_button_en_check()
if (unit.reactor_data ~= nil) and (unit.reactor_data.mek_status ~= nil) then
local can_start = (not unit.reactor_data.mek_status.status) and
(not unit.reactor_data.rps_tripped) and
(unit.a_group == AUTO_GROUP.MANUAL)
if can_start then start.enable() else start.disable() end
end
end
start.register(u_ps, "status", start_button_en_check)
start.register(u_ps, "rps_tripped", start_button_en_check)

View File

@@ -8,16 +8,16 @@ local style = require("coordinator.ui.style")
local core = require("graphics.core")
local Div = require("graphics.elements.div")
local PipeNetwork = require("graphics.elements.pipenet")
local TextBox = require("graphics.elements.textbox")
local Div = require("graphics.elements.Div")
local PipeNetwork = require("graphics.elements.PipeNetwork")
local TextBox = require("graphics.elements.TextBox")
local Rectangle = require("graphics.elements.rectangle")
local Rectangle = require("graphics.elements.Rectangle")
local DataIndicator = require("graphics.elements.indicators.data")
local DataIndicator = require("graphics.elements.indicators.DataIndicator")
local IndicatorLight = require("graphics.elements.indicators.light")
local TriIndicatorLight = require("graphics.elements.indicators.trilight")
local IndicatorLight = require("graphics.elements.indicators.IndicatorLight")
local TriIndicatorLight = require("graphics.elements.indicators.TriIndicatorLight")
local ALIGN = core.ALIGN
@@ -31,7 +31,7 @@ local wh_gray = style.wh_gray
local lg_gray = style.lg_gray
-- make a new unit flow window
---@param parent graphics_element parent
---@param parent Container parent
---@param x integer top left x
---@param y integer top left y
---@param wide boolean whether to render wide version

View File

@@ -10,16 +10,16 @@ local reactor_view = require("coordinator.ui.components.reactor")
local boiler_view = require("coordinator.ui.components.boiler")
local turbine_view = require("coordinator.ui.components.turbine")
local Div = require("graphics.elements.div")
local PipeNetwork = require("graphics.elements.pipenet")
local TextBox = require("graphics.elements.textbox")
local Div = require("graphics.elements.Div")
local PipeNetwork = require("graphics.elements.PipeNetwork")
local TextBox = require("graphics.elements.TextBox")
local ALIGN = core.ALIGN
local pipe = core.pipe
-- make a new unit overview window
---@param parent graphics_element parent
---@param parent Container parent
---@param x integer top left x
---@param y integer top left y
---@param unit ioctl_unit unit database entry

View File

@@ -13,15 +13,15 @@ local unit_flow = require("coordinator.ui.components.unit_flow")
local core = require("graphics.core")
local Div = require("graphics.elements.div")
local PipeNetwork = require("graphics.elements.pipenet")
local Rectangle = require("graphics.elements.rectangle")
local TextBox = require("graphics.elements.textbox")
local Div = require("graphics.elements.Div")
local PipeNetwork = require("graphics.elements.PipeNetwork")
local Rectangle = require("graphics.elements.Rectangle")
local TextBox = require("graphics.elements.TextBox")
local DataIndicator = require("graphics.elements.indicators.data")
local HorizontalBar = require("graphics.elements.indicators.hbar")
local IndicatorLight = require("graphics.elements.indicators.light")
local StateIndicator = require("graphics.elements.indicators.state")
local DataIndicator = require("graphics.elements.indicators.DataIndicator")
local HorizontalBar = require("graphics.elements.indicators.HorizontalBar")
local IndicatorLight = require("graphics.elements.indicators.IndicatorLight")
local StateIndicator = require("graphics.elements.indicators.StateIndicator")
local CONTAINER_MODE = types.CONTAINER_MODE
@@ -34,7 +34,7 @@ local pipe = core.pipe
local wh_gray = style.wh_gray
-- create new flow view
---@param main graphics_element main displaybox
---@param main DisplayBox main displaybox
local function init(main)
local s_hi_bright = style.theme.highlight_box_bright
local s_field = style.theme.field_box
@@ -84,8 +84,7 @@ local function init(main)
table.insert(water_pipes, pipe(2, y, 2, y + 3, colors.blue, true))
table.insert(water_pipes, pipe(2, y, 21, y, colors.blue, true))
local u = units[i] ---@type ioctl_unit
local x = util.trinary(u.num_boilers == 0, 45, 84)
local x = util.trinary(units[i].num_boilers == 0, 45, 84)
table.insert(water_pipes, pipe(21, y, x, y + 2, colors.blue, true, true))
end
end
@@ -102,8 +101,7 @@ local function init(main)
table.insert(water_pipes, pipe(2, y, 21, y, colors.blue, true))
end
local u = units[i] ---@type ioctl_unit
local x = util.trinary(u.num_boilers == 0, 45, 84)
local x = util.trinary(units[i].num_boilers == 0, 45, 84)
table.insert(water_pipes, pipe(21, y, x, y + 2, colors.blue, true, true))
end
end

View File

@@ -14,16 +14,16 @@ local pkt_entry = require("coordinator.ui.components.pkt_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 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 TabBar = require("graphics.elements.controls.TabBar")
local LED = require("graphics.elements.indicators.led")
local LEDPair = require("graphics.elements.indicators.ledpair")
local RGBLED = require("graphics.elements.indicators.ledrgb")
local LED = require("graphics.elements.indicators.LED")
local LEDPair = require("graphics.elements.indicators.LEDPair")
local RGBLED = require("graphics.elements.indicators.RGBLED")
local LINK_STATE = types.PANEL_LINK_STATE
@@ -34,7 +34,7 @@ local cpair = core.cpair
local led_grn = style.led_grn
-- create new front panel view
---@param panel graphics_element main displaybox
---@param panel DisplayBox main displaybox
---@param num_units integer number of units (number of unit monitors)
local function init(panel, num_units)
local ps = iocontrol.get_db().fp.ps
@@ -146,7 +146,7 @@ local function init(panel, num_units)
local api_page = Div{parent=page_div,x=1,y=1,hidden=true}
local api_list = ListBox{parent=api_page,x=1,y=1,height=17,width=51,scroll_height=1000,fg_bg=style.fp.text_fg,nav_fg_bg=cpair(colors.gray,colors.lightGray),nav_active=cpair(colors.black,colors.gray)}
local _ = Div{parent=api_list,height=1,hidden=true} -- padding
local _ = Div{parent=api_list,height=1} -- padding
-- assemble page panes

View File

@@ -14,14 +14,14 @@ local unit_overview = require("coordinator.ui.components.unit_overview")
local core = require("graphics.core")
local TextBox = require("graphics.elements.textbox")
local TextBox = require("graphics.elements.TextBox")
local DataIndicator = require("graphics.elements.indicators.data")
local DataIndicator = require("graphics.elements.indicators.DataIndicator")
local ALIGN = core.ALIGN
-- create new main view
---@param main graphics_element main displaybox
---@param main DisplayBox main displaybox
local function init(main)
local s_header = style.theme.header
@@ -37,7 +37,8 @@ local function init(main)
ping.register(facility.ps, "sv_ping", ping.update)
datetime.register(facility.ps, "date_time", datetime.set_value)
local uo_1, uo_2, uo_3, uo_4 ---@type graphics_element
---@type Div, Div, Div, Div
local uo_1, uo_2, uo_3, uo_4
local cnc_y_start = 3
local row_1_height = 0

View File

@@ -5,7 +5,7 @@
local unit_detail = require("coordinator.ui.components.unit_detail")
-- create a unit view
---@param main graphics_element main displaybox
---@param main DisplayBox main displaybox
---@param id integer
local function init(main, id)
unit_detail(main, id)

View File

@@ -8,15 +8,17 @@ local util = require("scada-common.util")
local pgi = {}
local data = {
pkt_list = nil, ---@type nil|graphics_element
pkt_list = nil, ---@type ListBox|nil
pkt_entry = nil, ---@type function
-- session entries
s_entries = { pkt = {} }
s_entries = {
pkt = {} ---@type Div[]
}
}
-- link list boxes
---@param pkt_list graphics_element pocket list element
---@param pkt_entry function pocket entry constructor
---@param pkt_list ListBox pocket list element
---@param pkt_entry fun(parent: ListBox, id: integer) : Div pocket entry constructor
function pgi.link_elements(pkt_list, pkt_entry)
data.pkt_list = pkt_list
data.pkt_entry = pkt_entry

View File

@@ -7,7 +7,7 @@ local flasher = require("graphics.flasher")
local core = {}
core.version = "2.3.4"
core.version = "2.4.5"
core.flasher = flasher
core.events = events
@@ -17,6 +17,8 @@ core.events = events
---@enum ALIGN
core.ALIGN = { LEFT = 1, CENTER = 2, RIGHT = 3 }
---@alias Container DisplayBox|Div|ListBox|MultiPane|AppMultiPane|Rectangle
---@class graphics_border
---@field width integer
---@field color color
@@ -124,10 +126,10 @@ end
-- Interactive Field Manager
---@param e graphics_base element
---@param max_len any max value length
---@param fg_bg any enabled fg/bg
---@param dis_fg_bg any disabled fg/bg
---@param align_right any true to align content right while unfocused
---@param max_len integer max value length
---@param fg_bg cpair enabled fg/bg
---@param dis_fg_bg? cpair disabled fg/bg
---@param align_right? boolean true to align content right while unfocused
function core.new_ifield(e, max_len, fg_bg, dis_fg_bg, align_right)
local self = {
frame_start = 1,

View File

@@ -11,9 +11,9 @@ local events = core.events
local element = {}
---@class graphics_args_generic
---@field window? table
---@field parent? graphics_element
---@class graphics_args
---@field window? Window base window to use, only root elements should use this
---@field parent? graphics_element parent element, if not a root element
---@field id? string element id
---@field x? integer 1 if omitted
---@field y? integer next line if omitted
@@ -24,47 +24,6 @@ local element = {}
---@field hidden? boolean true to hide on initial draw
---@field can_focus? boolean true if this element can be focused, false by default
---@alias graphics_args graphics_args_generic
---|waiting_args
---|app_button_args
---|checkbox_args
---|hazard_button_args
---|multi_button_args
---|push_button_args
---|radio_2d_args
---|radio_button_args
---|sidebar_args
---|spinbox_args
---|switch_button_args
---|tabbar_args
---|number_field_args
---|text_field_args
---|alarm_indicator_light
---|core_map_args
---|data_indicator_args
---|hbar_args
---|icon_indicator_args
---|indicator_led_args
---|indicator_led_pair_args
---|indicator_led_rgb_args
---|indicator_light_args
---|power_indicator_args
---|rad_indicator_args
---|signal_bar_args
---|state_indicator_args
---|tristate_indicator_light_args
---|vbar_args
---|app_multipane_args
---|colormap_args
---|displaybox_args
---|div_args
---|listbox_args
---|multipane_args
---|pipenet_args
---|rectangle_args
---|textbox_args
---|tiling_args
---@class element_subscription
---@field ps psil ps used
---@field key string data key
@@ -92,14 +51,14 @@ function element.new(args, constraint, child_offset_x, child_offset_y)
is_root = args.parent == nil,
elem_type = debug.getinfo(2).name,
define_completed = false,
p_window = nil, ---@type table
p_window = nil, ---@type Window
position = events.new_coord_2d(1, 1),
bounds = { x1 = 1, y1 = 1, x2 = 1, y2 = 1 }, ---@class element_bounds
offset_x = 0,
offset_y = 0,
next_y = 1, -- next child y coordinate
next_id = 0, -- next child ID
subscriptions = {},
next_id = 1, -- next child ID
subscriptions = {}, ---@type { ps: psil, key: string, func: function }[]
button_down = { events.new_coord_2d(-1, -1), events.new_coord_2d(-1, -1), events.new_coord_2d(-1, -1) },
focused = false,
mt = {}
@@ -109,13 +68,13 @@ function element.new(args, constraint, child_offset_x, child_offset_y)
local protected = {
enabled = true,
value = nil, ---@type any
window = nil, ---@type table
content_window = nil, ---@type table|nil
window = nil, ---@type Window
content_window = nil, ---@type Window|nil
mouse_window_shift = { x = 0, y = 0 },
fg_bg = core.cpair(colors.white, colors.black),
frame = core.gframe(1, 1, 1, 1),
children = {},
child_id_map = {}
children = {}, ---@type graphics_base[]
child_id_map = {} ---@type { [element_id]: integer }
}
-- element as string
@@ -128,9 +87,9 @@ function element.new(args, constraint, child_offset_x, child_offset_y)
setmetatable(public, self.mt)
-----------------------
-- PRIVATE FUNCTIONS --
-----------------------
------------------------------
--#region PRIVATE FUNCTIONS --
------------------------------
-- use tab to jump to the next focusable field
---@param reverse boolean
@@ -168,10 +127,10 @@ function element.new(args, constraint, child_offset_x, child_offset_y)
end
end
---@param children table
---@param children graphics_base[]
local function traverse(children)
for i = 1, #children do
local child = children[i] ---@type graphics_base
local child = children[i]
handle_element(child.get())
if child.get().is_visible() then traverse(child.children) end
end
@@ -191,9 +150,11 @@ function element.new(args, constraint, child_offset_x, child_offset_y)
end
end
-------------------------
-- PROTECTED FUNCTIONS --
-------------------------
--#endregion
--------------------------------
--#region PROTECTED FUNCTIONS --
--------------------------------
-- prepare the template
---@param offset_x integer x offset for mouse events
@@ -286,24 +247,29 @@ function element.new(args, constraint, child_offset_x, child_offset_y)
-- alias functions
-- window set cursor position
-- window set cursor position<br>
---@see Window.setCursorPos
---@param x integer
---@param y integer
function protected.w_set_cur(x, y) protected.window.setCursorPos(x, y) end
-- set background color
-- set background color<br>
---@see Window.setBackgroundColor
---@param c color
function protected.w_set_bkg(c) protected.window.setBackgroundColor(c) end
-- set foreground (text) color
-- set foreground (text) color<br>
---@see Window.setTextColor
---@param c color
function protected.w_set_fgd(c) protected.window.setTextColor(c) end
-- write text
-- write text<br>
---@see Window.write
---@param str string
function protected.w_write(str) protected.window.write(str) end
-- blit text
-- blit text<br>
---@see Window.blit
---@param str string
---@param fg string
---@param bg string
@@ -335,8 +301,10 @@ function element.new(args, constraint, child_offset_x, child_offset_y)
-- report completion of element instantiation and get the public interface
---@nodiscard
---@param redraw? boolean true to call redraw as part of completing this element
---@return graphics_element element, element_id id
function protected.complete()
function protected.complete(redraw)
if redraw then protected.redraw() end
if args.parent ~= nil then args.parent.__child_ready(self.id, public) end
return public, self.id
end
@@ -352,7 +320,7 @@ function element.new(args, constraint, child_offset_x, child_offset_y)
-- focus this element and take away focus from all other elements
function protected.take_focus() args.parent.__focus_child(public) end
-- action handlers --
--#region Action Handlers
-- luacheck: push ignore
---@diagnostic disable: unused-local, unused-vararg
@@ -401,14 +369,12 @@ function element.new(args, constraint, child_offset_x, child_offset_y)
function protected.handle_paste(text) end
-- handle data value changes
---@vararg any value(s)
---@param ... any value(s)
function protected.on_update(...) end
-- callback on control press responses
---@param result any
function protected.response_callback(result) end
--#endregion
-- accessors and control --
--#region Accessors and Control
-- get value
---@nodiscard
@@ -427,11 +393,11 @@ function element.new(args, constraint, child_offset_x, child_offset_y)
function protected.set_max(max) end
-- custom recolor command, varies by element if implemented
---@vararg cpair|color color(s)
---@param ... cpair|color color(s)
function protected.recolor(...) end
-- custom resize command, varies by element if implemented
---@vararg integer sizing
---@param ... integer sizing
function protected.resize(...) end
-- luacheck: pop
@@ -446,9 +412,13 @@ function element.new(args, constraint, child_offset_x, child_offset_y)
-- stop animations
function protected.stop_anim() end
-----------
-- SETUP --
-----------
--#endregion
--#endregion
------------------
--#region SETUP --
------------------
-- get the parent window
self.p_window = args.window
@@ -467,9 +437,11 @@ function element.new(args, constraint, child_offset_x, child_offset_y)
self.id = args.parent.__add_child(args.id, protected)
end
----------------------
-- PUBLIC FUNCTIONS --
----------------------
--#endregion
-----------------------------
--#region PUBLIC FUNCTIONS --
-----------------------------
-- get the window object
---@nodiscard
@@ -511,9 +483,10 @@ function element.new(args, constraint, child_offset_x, child_offset_y)
end
end
-- ELEMENT TREE --
--#region ELEMENT TREE
-- add a child element
---@package
---@nodiscard
---@param key string|nil id
---@param child graphics_base
@@ -523,7 +496,7 @@ function element.new(args, constraint, child_offset_x, child_offset_y)
self.next_y = child.frame.y + child.frame.h
local id = key ---@type string|integer|nil
local id = key ---@type element_id|nil
if id == nil then
id = self.next_id
self.next_id = self.next_id + 1
@@ -537,6 +510,7 @@ function element.new(args, constraint, child_offset_x, child_offset_y)
end
-- remove a child element
---@package
---@param id element_id id
function public.__remove_child(id)
local index = protected.child_id_map[id]
@@ -548,11 +522,13 @@ function element.new(args, constraint, child_offset_x, child_offset_y)
end
-- actions to take upon a child element becoming ready (initial draw/construction completed)
---@package
---@param key element_id id
---@param child graphics_element
function public.__child_ready(key, child) protected.on_added(key, child) end
-- focus solely on this child
---@package
---@param child graphics_element
function public.__focus_child(child)
if self.is_root then
@@ -562,6 +538,7 @@ function element.new(args, constraint, child_offset_x, child_offset_y)
end
-- a child was focused, used to make sure it is actually visible to the user in the content frame
---@package
---@param child graphics_element
function public.__child_focused(child)
protected.on_child_focused(child)
@@ -571,8 +548,8 @@ function element.new(args, constraint, child_offset_x, child_offset_y)
-- get a child element
---@nodiscard
---@param id element_id
---@return graphics_element
function public.get_child(id) return protected.children[protected.child_id_map[id]].get() end
---@return graphics_element element
function public.get_child(id) return ({ protected.children[protected.child_id_map[id]].get() })[1] end
-- get all children
---@nodiscard
@@ -619,29 +596,33 @@ function element.new(args, constraint, child_offset_x, child_offset_y)
local elem = child.get().get_element_by_id(id)
if elem ~= nil then return elem end
end
else return protected.children[index].get() end
else return ({ protected.children[index].get() })[1] end
end
-- AUTO-PLACEMENT --
--#endregion
--#region AUTO-PLACEMENT
-- skip a line for automatically placed elements
function public.line_break()
self.next_y = self.next_y + 1
end
-- PROPERTIES --
--#endregion
-- get element id
--#region PROPERTIES
-- get element ID
---@nodiscard
---@return element_id
function public.get_id() return self.id end
-- get element x
-- get element relative x position
---@nodiscard
---@return integer x
function public.get_x() return protected.frame.x end
-- get element y
-- get element relative y position
---@nodiscard
---@return integer y
function public.get_y() return protected.frame.y end
@@ -661,12 +642,12 @@ function element.new(args, constraint, child_offset_x, child_offset_y)
---@return cpair fg_bg
function public.get_fg_bg() return protected.fg_bg end
-- get the element value
-- get the element's value
---@nodiscard
---@return any value
function public.get_value() return protected.get_value() end
-- set the element value
-- set the element's value
---@param value any new value
function public.set_value(value) protected.set_value(value) end
@@ -728,11 +709,11 @@ function element.new(args, constraint, child_offset_x, child_offset_y)
end
-- custom recolor command, varies by element if implemented
---@vararg cpair|color color(s)
---@param ... cpair|color color(s)
function public.recolor(...) protected.recolor(...) end
-- resize attributes of the element value if supported
---@vararg number dimensions (element specific)
---@param ... number dimensions (element specific)
function public.resize(...) protected.resize(...) end
-- reposition the element window<br>
@@ -756,7 +737,9 @@ function element.new(args, constraint, child_offset_x, child_offset_y)
self.bounds.y2 = self.position.y + protected.frame.h - 1
end
-- FUNCTION CALLBACKS --
--#endregion
--#region FUNCTION CALLBACKS
-- handle a monitor touch or mouse click if this element is visible
---@param event mouse_interaction mouse interaction event
@@ -818,13 +801,9 @@ function element.new(args, constraint, child_offset_x, child_offset_y)
end
-- draw the element given new data
---@vararg any new data
---@param ... any new data
function public.update(...) protected.on_update(...) end
-- on a control request response
---@param result any
function public.on_response(result) protected.response_callback(result) end
-- register a callback with a PSIL, allowing for automatic unregister on delete<br>
-- do not use graphics elements directly with PSIL subscribe()
---@param ps psil PSIL to subscribe to
@@ -835,7 +814,9 @@ function element.new(args, constraint, child_offset_x, child_offset_y)
ps.subscribe(key, func)
end
-- VISIBILITY & ANIMATIONS --
--#endregion
--#region VISIBILITY & ANIMATIONS
-- check if this element is visible
function public.is_visible() return protected.window.isVisible() end
@@ -849,6 +830,7 @@ function element.new(args, constraint, child_offset_x, child_offset_y)
-- 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 Window.redraw
---@see graphics_element.redraw
---@see graphics_element.content_redraw
---@param clear? boolean true to visibly hide this element (redraws the parent)
@@ -900,6 +882,10 @@ function element.new(args, constraint, child_offset_x, child_offset_y)
end
end
--#endregion
--#endregion
return protected
end

View File

@@ -24,15 +24,15 @@ local MOUSE_CLICK = core.events.MOUSE_CLICK
---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw
-- new app multipane element
-- Create a new app multipane container element.
---@nodiscard
---@param args app_multipane_args
---@return graphics_element element, element_id id
local function multipane(args)
---@return AppMultiPane element, element_id id
return function (args)
element.assert(type(args.panes) == "table", "panes is a required field")
-- create new graphics element base object
local e = element.new(args)
local e = element.new(args --[[@as graphics_args]])
e.value = 1
@@ -100,10 +100,8 @@ local function multipane(args)
end
end
-- initial draw
e.redraw()
---@class AppMultiPane:graphics_element
local AppMultiPane, id = e.complete(true)
return e.complete()
return AppMultiPane, id
end
return multipane

View File

@@ -9,10 +9,10 @@ local element = require("graphics.element")
---@field y? integer auto incremented if omitted
---@field hidden? boolean true to hide on initial draw
-- new color map
-- Create a horizontal reference color map. Primarily used for tuning custom colors.
---@param args colormap_args
---@return graphics_element element, element_id id
local function colormap(args)
---@return ColorMap element, element_id id
return function (args)
local bkg = "008877FFCCEE114455DD9933BBAA2266"
local spaces = string.rep(" ", 32)
@@ -20,7 +20,7 @@ local function colormap(args)
args.height = 1
-- create new graphics element base object
local e = element.new(args)
local e = element.new(args --[[@as graphics_args]])
-- draw color map
function e.redraw()
@@ -28,10 +28,8 @@ local function colormap(args)
e.w_blit(spaces, bkg, bkg)
end
-- initial draw
e.redraw()
---@class ColorMap:graphics_element
local ColorMap, id = e.complete(true)
return e.complete()
return ColorMap, id
end
return colormap

View File

@@ -13,13 +13,16 @@ local element = require("graphics.element")
---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw
-- new root display box
-- Create a root display box.
---@nodiscard
---@param args displaybox_args
---@return graphics_element element, element_id id
local function displaybox(args)
---@return DisplayBox element, element_id id
return function (args)
-- create new graphics element base object
return element.new(args).complete()
end
local e = element.new(args --[[@as graphics_args]])
return displaybox
---@class DisplayBox:graphics_element
local DisplayBox, id = e.complete()
return DisplayBox, id
end

View File

@@ -13,13 +13,16 @@ local element = require("graphics.element")
---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw
-- new div element
-- Create a new div container element.
---@nodiscard
---@param args div_args
---@return graphics_element element, element_id id
local function div(args)
---@return Div element, element_id id
return function (args)
-- create new graphics element base object
return element.new(args).complete()
end
local e = element.new(args --[[@as graphics_args]])
return div
---@class Div:graphics_element
local Div, id = e.complete()
return Div, id
end

View File

@@ -30,15 +30,15 @@ local MOUSE_CLICK = core.events.MOUSE_CLICK
---@field y integer y position
---@field h integer element height
-- new listbox element
-- Create a new scrollable listbox container element.
---@nodiscard
---@param args listbox_args
---@return graphics_element element, element_id id
local function listbox(args)
---@return ListBox element, element_id id
return function (args)
args.can_focus = true
-- create new graphics element base object
local e = element.new(args)
local e = element.new(args --[[@as graphics_args]])
-- create content window for child elements
local scroll_frame = window.create(e.window, 1, 1, e.frame.w - 1, args.scroll_height, false)
@@ -339,10 +339,8 @@ local function listbox(args)
draw_bar()
end
-- initial draw
e.redraw()
---@class ListBox:graphics_element
local ListBox, id = e.complete(true)
return e.complete()
return ListBox, id
end
return listbox

View File

@@ -14,15 +14,15 @@ local element = require("graphics.element")
---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw
-- new multipane element
-- Create a new multipane container element.
---@nodiscard
---@param args multipane_args
---@return graphics_element element, element_id id
local function multipane(args)
---@return MultiPane element, element_id id
return function (args)
element.assert(type(args.panes) == "table", "panes is a required field")
-- create new graphics element base object
local e = element.new(args)
local e = element.new(args --[[@as graphics_args]])
e.value = 1
@@ -41,10 +41,8 @@ local function multipane(args)
end
end
-- initial draw
e.redraw()
---@class MultiPane:graphics_element
local MultiPane, id = e.complete(true)
return e.complete()
return MultiPane, id
end
return multipane

View File

@@ -20,10 +20,10 @@ local element = require("graphics.element")
---@field fg string foreground blit
---@field bg string background blit
-- new pipe network
-- Create a pipe network diagram.
---@param args pipenet_args
---@return graphics_element element, element_id id
local function pipenet(args)
---@return PipeNetwork element, element_id id
return function (args)
element.assert(type(args.pipes) == "table", "pipes is a required field")
args.width = 0
@@ -47,7 +47,7 @@ local function pipenet(args)
end
-- create new graphics element base object
local e = element.new(args)
local e = element.new(args --[[@as graphics_args]])
-- determine if there are any thin pipes involved
local any_thin = false
@@ -322,10 +322,8 @@ local function pipenet(args)
if any_thin then map_draw() else vector_draw() end
end
-- initial draw
e.redraw()
---@class PipeNetwork:graphics_element
local PipeNetwork, id = e.complete(true)
return e.complete()
return PipeNetwork, id
end
return pipenet

View File

@@ -18,10 +18,10 @@ local element = require("graphics.element")
---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw
-- new rectangle
-- Create a new rectangle container element.
---@param args rectangle_args
---@return graphics_element element, element_id id
local function rectangle(args)
---@return Rectangle element, element_id id
return function (args)
element.assert(args.border ~= nil or args.thin ~= true, "thin requires border to be provided")
-- if thin, then width will always need to be 1
@@ -45,7 +45,7 @@ local function rectangle(args)
end
-- create new graphics element base object
local e = element.new(args, nil, offset_x, offset_y)
local e = element.new(args --[[@as graphics_args]], nil, offset_x, offset_y)
-- 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))
@@ -191,7 +191,8 @@ local function rectangle(args)
e.redraw()
end
return e.complete()
end
---@class Rectangle:graphics_element
local Rectangle, id = e.complete()
return rectangle
return Rectangle, id
end

View File

@@ -10,6 +10,7 @@ local ALIGN = core.ALIGN
---@class textbox_args
---@field text string text to show
---@field alignment? ALIGN text alignment, left by default
---@field trim_whitespace? boolean true to trim whitespace before/after lines of text
---@field anchor? boolean true to use this as an anchor, making it focusable
---@field parent graphics_element
---@field id? string element id
@@ -21,10 +22,10 @@ local ALIGN = core.ALIGN
---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw
-- new text box
-- Create a new text box element.
---@param args textbox_args
---@return graphics_element element, element_id id
local function textbox(args)
---@return TextBox element, element_id id
return function (args)
element.assert(type(args.text) == "string", "text is a required field")
if args.anchor == true then args.can_focus = true end
@@ -42,7 +43,7 @@ local function textbox(args)
end
-- create new graphics element base object
local e = element.new(args, constrain)
local e = element.new(args --[[@as graphics_args]], constrain)
e.value = args.text
@@ -57,8 +58,11 @@ local function textbox(args)
for i = 1, #lines do
if i > e.frame.h then break end
-- trim leading/trailing whitespace
-- trim leading/trailing whitespace, except on the first line
-- leading whitespace on the first line is usually intentional
if args.trim_whitespace == true then
lines[i] = util.trim(lines[i])
end
local len = string.len(lines[i])
@@ -82,10 +86,8 @@ local function textbox(args)
e.redraw()
end
-- initial draw
e.redraw()
---@class TextBox:graphics_element
local TextBox, id = e.complete(true)
return e.complete()
return TextBox, id
end
return textbox

View File

@@ -18,14 +18,14 @@ local element = require("graphics.element")
---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw
-- new tiling box
-- Create a new tiling box element.
---@param args tiling_args
---@return graphics_element element, element_id id
local function tiling(args)
---@return Tiling element, element_id id
return function (args)
element.assert(type(args.fill_c) == "table", "fill_c is a required field")
-- create new graphics element base object
local e = element.new(args)
local e = element.new(args --[[@as graphics_args]])
local fill_a = args.fill_c.blit_a
local fill_b = args.fill_c.blit_b
@@ -52,7 +52,7 @@ local function tiling(args)
element.assert(start_x <= inner_width, "start_x > inner_width")
element.assert(start_y <= inner_height, "start_y > inner_height")
-- draw tiling box
-- draw the tiling box
function e.redraw()
local alternator = true
@@ -86,10 +86,8 @@ local function tiling(args)
end
end
-- initial draw
e.redraw()
---@class Tiling:graphics_element
local Tiling, id = e.complete(true)
return e.complete()
return Tiling, id
end
return tiling

View File

@@ -12,10 +12,10 @@ local element = require("graphics.element")
---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw
-- new waiting animation element
-- Create a new waiting animation element.
---@param args waiting_args
---@return graphics_element element, element_id id
local function waiting(args)
---@return Waiting element, element_id id
return function (args)
local state = 0
local run_animation = false
@@ -23,7 +23,7 @@ local function waiting(args)
args.height = 3
-- create new graphics element base object
local e = element.new(args)
local e = element.new(args --[[@as graphics_args]])
local blit_fg = e.fg_bg.blit_fgd
local blit_bg = e.fg_bg.blit_bkg
@@ -103,7 +103,8 @@ local function waiting(args)
e.start_anim()
return e.complete()
end
---@class Waiting:graphics_element
local Waiting, id = e.complete()
return waiting
return Waiting, id
end

View File

@@ -20,10 +20,10 @@ local MOUSE_CLICK = core.events.MOUSE_CLICK
---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw
-- new app button
-- Create a new app icon style button control element, like on a mobile device.
---@param args app_button_args
---@return graphics_element element, element_id id
local function app_button(args)
---@return App element, element_id id
return function (args)
element.assert(type(args.text) == "string", "text is a required field")
element.assert(type(args.title) == "string", "title is a required field")
element.assert(type(args.callback) == "function", "callback is a required field")
@@ -33,7 +33,7 @@ local function app_button(args)
args.width = 7
-- create new graphics element base object
local e = element.new(args)
local e = element.new(args --[[@as graphics_args]])
-- draw the app button
local function draw()
@@ -123,10 +123,8 @@ local function app_button(args)
draw()
end
-- initial draw
e.redraw()
---@class App:graphics_element
local App, id = e.complete(true)
return e.complete()
return App, id
end
return app_button

View File

@@ -15,10 +15,10 @@ local element = require("graphics.element")
---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw
-- new checkbox control
-- Create a new checkbox control element.
---@param args checkbox_args
---@return graphics_element element, element_id id
local function checkbox(args)
---@return Checkbox element, element_id id
return function (args)
element.assert(type(args.label) == "string", "label is a required field")
element.assert(type(args.box_fg_bg) == "table", "box_fg_bg is a required field")
@@ -27,7 +27,7 @@ local function checkbox(args)
args.width = 2 + string.len(args.label)
-- create new graphics element base object
local e = element.new(args)
local e = element.new(args --[[@as graphics_args]])
e.value = args.default == true
@@ -112,10 +112,8 @@ local function checkbox(args)
draw_label()
end
-- initial draw
e.redraw()
---@class Checkbox:graphics_element
local Checkbox, id = e.complete(true)
return e.complete()
return Checkbox, id
end
return checkbox

View File

@@ -18,10 +18,10 @@ local element = require("graphics.element")
---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw
-- new hazard button
-- Create a new hazard button control element.
---@param args hazard_button_args
---@return graphics_element element, element_id id
local function hazard_button(args)
---@return HazardButton element, element_id id
return function (args)
element.assert(type(args.text) == "string", "text is a required field")
element.assert(type(args.accent) == "number", "accent is a required field")
element.assert(type(args.callback) == "function", "callback is a required field")
@@ -32,7 +32,7 @@ local function hazard_button(args)
local timeout = args.timeout or 1.5
-- create new graphics element base object
local e = element.new(args)
local e = element.new(args --[[@as graphics_args]])
-- draw border
---@param accent color accent color
@@ -159,13 +159,6 @@ local function hazard_button(args)
end
end
-- callback on request response
---@param result boolean true for success, false for failure
function e.response_callback(result)
tcd.abort(on_timeout)
if result then on_success() else on_failure(0) end
end
-- set the value (true simulates pressing the button)
---@param val boolean new value
function e.set_value(val)
@@ -198,10 +191,15 @@ local function hazard_button(args)
draw_border(args.accent)
end
-- initial draw
e.redraw()
---@class HazardButton:graphics_element
local HazardButton, id = e.complete(true)
return e.complete()
-- callback for request response
---@param success boolean
function HazardButton.on_response(success)
tcd.abort(on_timeout)
if success then on_success() else on_failure(0) end
end
return hazard_button
return HazardButton, id
end

View File

@@ -25,10 +25,10 @@ local element = require("graphics.element")
---@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)
-- Create a new multi button control element (latch selection, exclusively one button at a time).
---@param args multi_button_args
---@return graphics_element element, element_id id
local function multi_button(args)
---@return MultiButton element, element_id id
return function (args)
element.assert(type(args.options) == "table", "options is a required field")
element.assert(#args.options > 0, "at least one option is required")
element.assert(type(args.callback) == "function", "callback is a required field")
@@ -52,7 +52,7 @@ local function multi_button(args)
args.width = (button_width * #args.options) + #args.options + 1
-- create new graphics element base object
local e = element.new(args)
local e = element.new(args --[[@as graphics_args]])
-- button state (convert nil to 1 if missing)
e.value = args.default or 1
@@ -126,10 +126,8 @@ local function multi_button(args)
e.redraw()
end
-- initial draw
e.redraw()
---@class MultiButton:graphics_element
local MultiButton, id = e.complete(true)
return e.complete()
return MultiButton, id
end
return multi_button

View File

@@ -20,10 +20,10 @@ local element = require("graphics.element")
---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw
-- new spinbox control (minimum value is 0)
-- Create a new spinbox control element (minimum value is 0).
---@param args spinbox_args
---@return graphics_element element, element_id id
local function spinbox(args)
---@return NumericSpinbox element, element_id id
return function (args)
-- properties
local digits = {}
local wn_prec = args.whole_num_precision
@@ -51,7 +51,7 @@ local function spinbox(args)
args.height = 3
-- create new graphics element base object
local e = element.new(args)
local e = element.new(args --[[@as graphics_args]])
-- set initial value
e.value = args.default or 0
@@ -179,10 +179,8 @@ local function spinbox(args)
draw_arrows(util.trinary(e.enabled, args.arrow_fg_bg.fgd, args.arrow_disable or colors.lightGray))
end
-- initial draw
e.redraw()
---@class NumericSpinbox:graphics_element
local NumericSpinbox, id = e.complete(true)
return e.complete()
return NumericSpinbox, id
end
return spinbox

View File

@@ -25,10 +25,10 @@ local KEY_CLICK = core.events.KEY_CLICK
---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw
-- new push button
-- Create a new push button control element.
---@param args push_button_args
---@return graphics_element element, element_id id
local function push_button(args)
---@return PushButton element, element_id id
return function (args)
element.assert(type(args.text) == "string", "text is a required field")
element.assert(type(args.callback) == "function", "callback is a required field")
element.assert(type(args.min_width) == "nil" or (type(args.min_width) == "number" and args.min_width > 0), "min_width must be nil or a number > 0")
@@ -48,7 +48,7 @@ local function push_button(args)
end
-- create new graphics element base object
local e = element.new(args, constrain)
local e = element.new(args --[[@as graphics_args]], constrain)
local text_lines = util.strwrap(args.text, e.frame.w)
@@ -157,10 +157,8 @@ local function push_button(args)
e.on_focused = show_pressed
e.on_unfocused = show_unpressed
-- initial draw
e.redraw()
---@class PushButton:graphics_element
local PushButton, id = e.complete(true)
return e.complete()
return PushButton, id
end
return push_button

View File

@@ -23,10 +23,10 @@ local element = require("graphics.element")
---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw
-- new 2D radio button list (latch selection, exclusively one color at a time)
-- Create a new 2-dimensional (rows and columns of options) radio button list control element (latch selection, exclusively one color at a time).
---@param args radio_2d_args
---@return graphics_element element, element_id id
local function radio_2d_button(args)
---@return Radio2D element, element_id id
return function (args)
element.assert(type(args.options) == "table" and #args.options > 0, "options should be a table with length >= 1")
element.assert(util.is_int(args.rows) and util.is_int(args.columns), "rows/columns must be integers")
element.assert((args.rows * args.columns) >= #args.options, "rows x columns size insufficient for provided number of options")
@@ -70,7 +70,7 @@ local function radio_2d_button(args)
args.height = max_rows
-- create new graphics element base object
local e = element.new(args)
local e = element.new(args --[[@as graphics_args]])
-- selected option (convert nil to 1 if missing)
e.value = args.default or 1
@@ -194,10 +194,8 @@ local function radio_2d_button(args)
e.on_enabled = e.redraw
e.on_disabled = e.redraw
-- initial draw
e.redraw()
---@class Radio2D:graphics_element
local Radio2D, id = e.complete(true)
return e.complete()
return Radio2D, id
end
return radio_2d_button

View File

@@ -11,6 +11,7 @@ local KEY_CLICK = core.events.KEY_CLICK
---@field options table button options
---@field radio_colors cpair radio button colors (inner & outer)
---@field select_color color color for radio button border when selected
---@field dis_fg_bg? cpair foreground/background colors when disabled
---@field default? integer default state, defaults to options[1]
---@field min_width? integer text length + 2 if omitted
---@field callback? function function to call on touch
@@ -21,10 +22,10 @@ local KEY_CLICK = core.events.KEY_CLICK
---@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)
-- Create a new radio button list control element (latch selection, exclusively one button at a time).
---@param args radio_button_args
---@return graphics_element element, element_id id
local function radio_button(args)
---@return RadioButton element, element_id id
return function (args)
element.assert(type(args.options) == "table", "options is a required field")
element.assert(#args.options > 0, "at least one option is required")
element.assert(type(args.radio_colors) == "table", "radio_colors is a required field")
@@ -49,7 +50,7 @@ local function radio_button(args)
args.height = #args.options -- one line per option
-- create new graphics element base object
local e = element.new(args)
local e = element.new(args --[[@as graphics_args]])
local focused_opt = 1
@@ -64,6 +65,10 @@ local function radio_button(args)
local inner_color = util.trinary(e.value == i, args.radio_colors.color_b, args.radio_colors.color_a)
local outer_color = util.trinary(e.value == i, args.select_color, args.radio_colors.color_b)
if e.value == i and args.dis_fg_bg and not e.enabled then
outer_color = args.radio_colors.color_a
end
e.w_set_cur(1, i)
e.w_set_fgd(inner_color)
@@ -75,9 +80,14 @@ local function radio_button(args)
e.w_write("\x95")
-- write button text
if i == focused_opt and e.is_focused() and e.enabled then
if args.dis_fg_bg and not e.enabled then
e.w_set_fgd(args.dis_fg_bg.fgd)
e.w_set_bkg(args.dis_fg_bg.bkg)
elseif i == focused_opt and e.is_focused() then
if e.enabled then
e.w_set_fgd(e.fg_bg.bkg)
e.w_set_bkg(e.fg_bg.fgd)
end
else
e.w_set_fgd(e.fg_bg.fgd)
e.w_set_bkg(e.fg_bg.bkg)
@@ -139,10 +149,8 @@ local function radio_button(args)
e.on_enabled = e.redraw
e.on_disabled = e.redraw
-- initial draw
e.redraw()
---@class RadioButton:graphics_element
local RadioButton, id = e.complete(true)
return e.complete()
return RadioButton, id
end
return radio_button

View File

@@ -17,14 +17,14 @@ local MOUSE_CLICK = core.events.MOUSE_CLICK
---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw
-- new sidebar tab selector
-- Create a new sidebar tab selector control element.
---@param args sidebar_args
---@return graphics_element element, element_id id
local function sidebar(args)
---@return Sidebar element, element_id id
return function (args)
args.width = 3
-- create new graphics element base object
local e = element.new(args)
local e = element.new(args --[[@as graphics_args]])
-- default to 1st tab
e.value = 1
@@ -129,8 +129,14 @@ local function sidebar(args)
end
-- update the sidebar navigation options
---@param items table sidebar entries
---@param items sidebar_entry[] sidebar entries
function e.on_update(items)
---@class sidebar_entry
---@field label string
---@field tall boolean
---@field color cpair
---@field callback function|nil
local next_y = 1
tabs = {}
@@ -160,9 +166,8 @@ local function sidebar(args)
-- element redraw
e.redraw = draw
e.redraw()
---@class Sidebar:graphics_element
local Sidebar, id = e.complete(true)
return e.complete()
return Sidebar, id
end
return sidebar

View File

@@ -17,10 +17,10 @@ local element = require("graphics.element")
---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw
-- new switch button (latch high/low)
-- Create a new latching switch button control element.
---@param args switch_button_args
---@return graphics_element element, element_id id
local function switch_button(args)
---@return SwitchButton element, element_id id
return function (args)
element.assert(type(args.text) == "string", "text is a required field")
element.assert(type(args.callback) == "function", "callback is a required field")
element.assert(type(args.active_fg_bg) == "table", "active_fg_bg is a required field")
@@ -33,7 +33,7 @@ local function switch_button(args)
args.width = math.max(text_width, args.min_width)
-- create new graphics element base object
local e = element.new(args)
local e = element.new(args --[[@as graphics_args]])
e.value = args.default or false
@@ -72,10 +72,8 @@ local function switch_button(args)
e.redraw()
end
-- initial draw
e.redraw()
---@class SwitchButton:graphics_element
local SwitchButton, id = e.complete(true)
return e.complete()
return SwitchButton, id
end
return switch_button

View File

@@ -23,10 +23,10 @@ local element = require("graphics.element")
---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw
-- new tab selector
-- Create a new tab selector control element.
---@param args tabbar_args
---@return graphics_element element, element_id id
local function tabbar(args)
---@return TabBar element, element_id id
return function (args)
element.assert(type(args.tabs) == "table", "tabs is a required field")
element.assert(#args.tabs > 0, "at least one tab is required")
element.assert(type(args.callback) == "function", "callback is a required field")
@@ -46,7 +46,7 @@ local function tabbar(args)
local button_width = math.max(max_width, args.min_width or 0)
-- create new graphics element base object
local e = element.new(args)
local e = element.new(args --[[@as graphics_args]])
element.assert(e.frame.w >= (button_width * #args.tabs), "width insufficent to display all tabs")
@@ -120,10 +120,8 @@ local function tabbar(args)
e.redraw()
end
-- initial draw
e.redraw()
---@class TabBar:graphics_element
local TabBar, id = e.complete(true)
return e.complete()
return TabBar, id
end
return tabbar

View File

@@ -27,10 +27,10 @@ local MOUSE_CLICK = core.events.MOUSE_CLICK
---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw
-- new numeric entry field
-- Create a new numeric entry field.
---@param args number_field_args
---@return graphics_element element, element_id id
local function number_field(args)
---@return NumberField element, element_id id
return function (args)
element.assert(args.max_int_digits == nil or (util.is_int(args.max_int_digits) and args.max_int_digits > 0), "max_int_digits must be an integer greater than zero if supplied")
element.assert(args.max_frac_digits == nil or (util.is_int(args.max_frac_digits) and args.max_frac_digits > 0), "max_frac_digits must be an integer greater than zero if supplied")
@@ -38,14 +38,53 @@ local function number_field(args)
args.can_focus = true
-- create new graphics element base object
local e = element.new(args)
local e = element.new(args --[[@as graphics_args]])
local has_decimal = false
args.max_chars = args.max_chars or e.frame.w
-- determine the format to convert the number to a string
local format = "%d"
if args.allow_decimal then
if args.max_frac_digits then
format = "%."..args.max_frac_digits.."f"
else format = "%f" end
end
-- set the value to a formatted numeric string<br>
-- trims trailing zeros from floating point numbers
---@param num number
local function _set_value(num)
local str = util.sprintf(format, num)
if args.allow_decimal then
local found_nonzero = false
local str_table = {}
for i = #str, 1, -1 do
local c = string.sub(str, i, i)
if found_nonzero then
str_table[i] = c
else
if c == "." then
found_nonzero = true
elseif c ~= "0" then
str_table[i] = c
found_nonzero = true
end
end
end
e.value = table.concat(str_table)
else
e.value = str
end
end
-- set initial value
e.value = "" .. (args.default or 0)
_set_value(args.default or 0)
-- make an interactive field manager
local ifield = core.new_ifield(e, args.max_chars, args.fg_bg, args.dis_fg_bg, args.align_right)
@@ -107,7 +146,17 @@ local function number_field(args)
-- set the value (must be a number)
---@param val number number to show
function e.set_value(val)
if tonumber(val) then ifield.set_value("" .. tonumber(val)) end
local num, max, min = tonumber(val), tonumber(args.max), tonumber(args.min)
if max and num > max then
_set_value(max)
elseif min and num < min then
_set_value(min)
elseif num then
_set_value(num)
end
ifield.set_value(e.value)
end
-- set minimum input value
@@ -136,11 +185,9 @@ local function number_field(args)
-- handle unfocused
function e.on_unfocused()
local val = tonumber(e.value)
local max = tonumber(args.max)
local min = tonumber(args.min)
local val, max, min = tonumber(e.value), tonumber(args.max), tonumber(args.min)
if type(val) == "number" then
if val then
if args.max_int_digits or args.max_frac_digits then
local str = e.value
local ceil = false
@@ -169,17 +216,17 @@ local function number_field(args)
if parts[2] then parts[2] = "." .. parts[2] else parts[2] = "" end
val = tonumber((parts[1] or "") .. parts[2])
val = tonumber((parts[1] or "") .. parts[2]) or 0
end
if type(args.max) == "number" and val > max then
e.value = "" .. max
if max and val > max then
_set_value(max)
ifield.nav_start()
elseif type(args.min) == "number" and val < min then
e.value = "" .. min
elseif min and val < min then
_set_value(min)
ifield.nav_start()
else
e.value = "" .. val
_set_value(val)
ifield.nav_end()
end
else
@@ -195,10 +242,14 @@ local function number_field(args)
e.on_disabled = ifield.show
e.redraw = ifield.show
-- initial draw
e.redraw()
---@class NumberField:graphics_element
local NumberField, id = e.complete(true)
return e.complete()
-- get the numeric value of this field
---@return number value the value, or 0 if not a valid number
function NumberField.get_numeric()
return tonumber(e.value) or 0
end
return number_field
return NumberField, id
end

View File

@@ -19,15 +19,15 @@ local MOUSE_CLICK = core.events.MOUSE_CLICK
---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw
-- new text entry field
-- Create a new text entry field.
---@param args text_field_args
---@return graphics_element element, element_id id, function censor_ctl
local function text_field(args)
---@return TextField element, element_id id
return function (args)
args.height = 1
args.can_focus = true
-- create new graphics element base object
local e = element.new(args)
local e = element.new(args --[[@as graphics_args]])
-- set initial value
e.value = args.value or ""
@@ -95,11 +95,10 @@ local function text_field(args)
e.on_disabled = ifield.show
e.redraw = ifield.show
-- initial draw
e.redraw()
---@class TextField:graphics_element
local TextField, id = e.complete(true)
local elem, id = e.complete()
return elem, id, ifield.censor
TextField.censor = ifield.censor
return TextField, id
end
return text_field

View File

@@ -20,11 +20,11 @@ local flasher = require("graphics.flasher")
---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw
-- new alarm indicator light
-- Create a new alarm indicator light element.
---@nodiscard
---@param args alarm_indicator_light
---@return graphics_element element, element_id id
local function alarm_indicator_light(args)
---@return AlarmLight element, element_id id
return function (args)
element.assert(type(args.label) == "string", "label is a required field")
element.assert(type(args.c1) == "number", "c1 is a required field")
element.assert(type(args.c2) == "number", "c2 is a required field")
@@ -49,7 +49,7 @@ local function alarm_indicator_light(args)
local c3 = colors.toBlit(args.c3)
-- create new graphics element base object
local e = element.new(args)
local e = element.new(args --[[@as graphics_args]])
e.value = 1
@@ -113,10 +113,8 @@ local function alarm_indicator_light(args)
e.w_write(args.label)
end
-- initial draw
e.redraw()
---@class AlarmLight:graphics_element
local AlarmLight, id = e.complete(true)
return e.complete()
return AlarmLight, id
end
return alarm_indicator_light

View File

@@ -13,11 +13,11 @@ local element = require("graphics.element")
---@field x? integer 1 if omitted
---@field y? integer auto incremented if omitted
-- new core map box
-- Create a new core map diagram indicator element.
---@nodiscard
---@param args core_map_args
---@return graphics_element element, element_id id
local function core_map(args)
---@return CoreMap element, element_id id
return function (args)
element.assert(util.is_int(args.reactor_l), "reactor_l is a required field")
element.assert(util.is_int(args.reactor_w), "reactor_w is a required field")
@@ -29,7 +29,7 @@ local function core_map(args)
args.fg_bg = core.cpair(args.parent.get_fg_bg().fgd, colors.gray)
-- create new graphics element base object
local e = element.new(args)
local e = element.new(args --[[@as graphics_args]])
e.value = 0
@@ -165,10 +165,8 @@ local function core_map(args)
draw_core(e.value)
end
-- initial draw
e.redraw()
---@class CoreMap:graphics_element
local CoreMap, id = e.complete(true)
return e.complete()
return CoreMap, id
end
return core_map

View File

@@ -19,11 +19,11 @@ local element = require("graphics.element")
---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw
-- new data indicator
-- Create new data indicator element.
---@nodiscard
---@param args data_indicator_args
---@return graphics_element element, element_id id
local function data(args)
---@return DataIndicator element, element_id id
return function (args)
element.assert(type(args.label) == "string", "label is a required field")
element.assert(type(args.format) == "string", "format is a required field")
element.assert(args.value ~= nil, "value is a required field")
@@ -32,7 +32,7 @@ local function data(args)
args.height = 1
-- create new graphics element base object
local e = element.new(args)
local e = element.new(args --[[@as graphics_args]])
e.value = args.value
@@ -94,10 +94,8 @@ local function data(args)
e.on_update(e.value)
end
-- initial draw
e.redraw()
---@class DataIndicator:graphics_element
local DataIndicator, id = e.complete(true)
return e.complete()
return DataIndicator, id
end
return data

View File

@@ -17,13 +17,13 @@ local element = require("graphics.element")
---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw
-- new horizontal bar
-- Create a new horizontal fill bar indicator element.
---@nodiscard
---@param args hbar_args
---@return graphics_element element, element_id id
local function hbar(args)
return function (args)
-- create new graphics element base object
local e = element.new(args)
local e = element.new(args --[[@as graphics_args]])
e.value = 0.0
@@ -119,10 +119,8 @@ local function hbar(args)
e.on_update(e.value)
end
-- initial draw
e.redraw()
---@class HorizontalBar:graphics_element
local HorizontalBar, id = e.complete(true)
return e.complete()
return HorizontalBar, id
end
return hbar

View File

@@ -18,11 +18,11 @@ local element = require("graphics.element")
---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw
-- new icon indicator
-- Create a new icon indicator element.
---@nodiscard
---@param args icon_indicator_args
---@return graphics_element element, element_id id
local function icon(args)
---@return IconIndicator element, element_id id
return function (args)
element.assert(type(args.label) == "string", "label is a required field")
element.assert(type(args.states) == "table", "states is a required field")
@@ -30,7 +30,7 @@ local function icon(args)
args.width = math.max(args.min_label_width or 1, string.len(args.label)) + 4
-- create new graphics element base object
local e = element.new(args)
local e = element.new(args --[[@as graphics_args]])
e.value = args.value or 1
if e.value == true then e.value = 2 end
@@ -71,10 +71,8 @@ local function icon(args)
e.on_update(e.value)
end
-- initial draw
e.redraw()
---@class IconIndicator:graphics_element
local IconIndicator, id = e.complete(true)
return e.complete()
return IconIndicator, id
end
return icon

View File

@@ -18,11 +18,11 @@ local flasher = require("graphics.flasher")
---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw
-- new indicator light
-- Create a new indicator light element.
---@nodiscard
---@param args indicator_light_args
---@return graphics_element element, element_id id
local function indicator_light(args)
---@return IndicatorLight element, element_id id
return function (args)
element.assert(type(args.label) == "string", "label is a required field")
element.assert(type(args.colors) == "table", "colors is a required field")
@@ -36,7 +36,7 @@ local function indicator_light(args)
local flash_on = true
-- create new graphics element base object
local e = element.new(args)
local e = element.new(args --[[@as graphics_args]])
e.value = false
@@ -93,10 +93,8 @@ local function indicator_light(args)
e.w_write(args.label)
end
-- initial draw
e.redraw()
---@class IndicatorLight:graphics_element
local IndicatorLight, id = e.complete(true)
return e.complete()
return IndicatorLight, id
end
return indicator_light

View File

@@ -18,11 +18,11 @@ local flasher = require("graphics.flasher")
---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw
-- new indicator LED
-- Create a new indicator LED element.
---@nodiscard
---@param args indicator_led_args
---@return graphics_element element, element_id id
local function indicator_led(args)
---@return LED element, element_id id
return function (args)
element.assert(type(args.label) == "string", "label is a required field")
element.assert(type(args.colors) == "table", "colors is a required field")
@@ -36,7 +36,7 @@ local function indicator_led(args)
local flash_on = true
-- create new graphics element base object
local e = element.new(args)
local e = element.new(args --[[@as graphics_args]])
e.value = false
@@ -95,10 +95,8 @@ local function indicator_led(args)
end
end
-- initial draw
e.redraw()
---@class LED:graphics_element
local LED, id = e.complete(true)
return e.complete()
return LED, id
end
return indicator_led

View File

@@ -20,11 +20,12 @@ local flasher = require("graphics.flasher")
---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw
-- new dual LED indicator light
-- Create a new three-state LED indicator light. Two "active" states (colors c1 and c2) and an inactive state (off).<br>
-- Values: 1 = off, 2 = c1, 3 = c2
---@nodiscard
---@param args indicator_led_pair_args
---@return graphics_element element, element_id id
local function indicator_led_pair(args)
---@return LEDPair element, element_id id
return function (args)
element.assert(type(args.label) == "string", "label is a required field")
element.assert(type(args.off) == "number", "off is a required field")
element.assert(type(args.c1) == "number", "c1 is a required field")
@@ -44,7 +45,7 @@ local function indicator_led_pair(args)
local c2 = colors.toBlit(args.c2)
-- create new graphics element base object
local e = element.new(args)
local e = element.new(args --[[@as graphics_args]])
e.value = 1
@@ -104,10 +105,8 @@ local function indicator_led_pair(args)
end
end
-- initial draw
e.redraw()
---@class LEDPair:graphics_element
local LEDPair, id = e.complete(true)
return e.complete()
return LEDPair, id
end
return indicator_led_pair

View File

@@ -19,11 +19,11 @@ local element = require("graphics.element")
---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw
-- new power indicator
-- Create a new power indicator. Variant of a data indicator with dynamic energy units.
---@nodiscard
---@param args power_indicator_args
---@return graphics_element element, element_id id
local function power(args)
---@return PowerIndicator element, element_id id
return function (args)
element.assert(type(args.label) == "string", "label is a required field")
element.assert(type(args.unit) == "string", "unit is a required field")
element.assert(type(args.value) == "number", "value is a required field")
@@ -32,7 +32,7 @@ local function power(args)
args.height = 1
-- create new graphics element base object
local e = element.new(args)
local e = element.new(args --[[@as graphics_args]])
e.value = args.value
@@ -82,10 +82,8 @@ local function power(args)
e.on_update(e.value)
end
-- initial draw
e.redraw()
---@class PowerIndicator:graphics_element
local PowerIndicator, id = e.complete(true)
return e.complete()
return PowerIndicator, id
end
return power

View File

@@ -13,11 +13,11 @@ local element = require("graphics.element")
---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw
-- new RGB LED indicator light
-- Create a new RGB LED indicator light element.
---@nodiscard
---@param args indicator_led_rgb_args
---@return graphics_element element, element_id id
local function indicator_led_rgb(args)
---@return RGBLED element, element_id id
return function (args)
element.assert(type(args.label) == "string", "label is a required field")
element.assert(type(args.colors) == "table", "colors is a required field")
@@ -25,7 +25,7 @@ local function indicator_led_rgb(args)
args.width = math.max(args.min_label_width or 0, string.len(args.label)) + 2
-- create new graphics element base object
local e = element.new(args)
local e = element.new(args --[[@as graphics_args]])
e.value = 1
@@ -52,10 +52,8 @@ local function indicator_led_rgb(args)
end
end
-- initial draw
e.redraw()
---@class RGBLED:graphics_element
local RGBLED, id = e.complete(true)
return e.complete()
return RGBLED, id
end
return indicator_led_rgb

View File

@@ -19,11 +19,11 @@ local element = require("graphics.element")
---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw
-- new radiation indicator
-- Create a new radiation indicator element. Variant of a data indicator using dynamic Sievert unit precision.
---@nodiscard
---@param args rad_indicator_args
---@return graphics_element element, element_id id
local function rad(args)
---@return RadIndicator element, element_id id
return function (args)
element.assert(type(args.label) == "string", "label is a required field")
element.assert(type(args.format) == "string", "format is a required field")
element.assert(util.is_int(args.width), "width is a required field")
@@ -31,7 +31,7 @@ local function rad(args)
args.height = 1
-- create new graphics element base object
local e = element.new(args)
local e = element.new(args --[[@as graphics_args]])
e.value = args.value or types.new_zero_radiation_reading()
@@ -83,10 +83,8 @@ local function rad(args)
e.on_update(e.value)
end
-- initial draw
e.redraw()
---@class RadIndicator:graphics_element
local RadIndicator, id = e.complete(true)
return e.complete()
return RadIndicator, id
end
return rad

View File

@@ -15,16 +15,16 @@ local element = require("graphics.element")
---@field fg_bg? cpair foreground/background colors (foreground is used for high signal quality)
---@field hidden? boolean true to hide on initial draw
-- new signal bar
-- Create a new signal bar indicator element.
---@nodiscard
---@param args signal_bar_args
---@return graphics_element element, element_id id
local function signal_bar(args)
---@return SignalBar element, element_id id
return function (args)
args.height = 1
args.width = util.trinary(args.compact, 1, 2)
-- create new graphics element base object
local e = element.new(args)
local e = element.new(args --[[@as graphics_args]])
e.value = 0
@@ -76,10 +76,8 @@ local function signal_bar(args)
end
end
-- initial draw
e.redraw()
---@class SignalBar:graphics_element
local SignalBar, id = e.complete(true)
return e.complete()
return SignalBar, id
end
return signal_bar

View File

@@ -20,11 +20,11 @@ local element = require("graphics.element")
---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw
-- new state indicator
-- Create a new state indicator element.
---@nodiscard
---@param args state_indicator_args
---@return graphics_element element, element_id id
local function state_indicator(args)
---@return StateIndicator element, element_id id
return function (args)
element.assert(type(args.states) == "table", "states is a required field")
if util.is_int(args.height) then
@@ -52,7 +52,7 @@ local function state_indicator(args)
end
-- create new graphics element base object
local e = element.new(args)
local e = element.new(args --[[@as graphics_args]])
e.value = args.value or 1
@@ -74,10 +74,8 @@ local function state_indicator(args)
---@param val integer indicator state
function e.set_value(val) e.on_update(val) end
-- initial draw
e.redraw()
---@class StateIndicator:graphics_element
local StateIndicator, id = e.complete(true)
return e.complete()
return StateIndicator, id
end
return state_indicator

View File

@@ -20,11 +20,11 @@ local flasher = require("graphics.flasher")
---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw
-- new tri-state indicator light
-- Create a new tri-state indicator light element.
---@nodiscard
---@param args tristate_indicator_light_args
---@return graphics_element element, element_id id
local function tristate_indicator_light(args)
---@return TriIndicatorLight element, element_id id
return function (args)
element.assert(type(args.label) == "string", "label is a required field")
element.assert(type(args.c1) == "number", "c1 is a required field")
element.assert(type(args.c2) == "number", "c2 is a required field")
@@ -38,7 +38,7 @@ local function tristate_indicator_light(args)
args.width = math.max(args.min_label_width or 1, string.len(args.label)) + 2
-- create new graphics element base object
local e = element.new(args)
local e = element.new(args --[[@as graphics_args]])
e.value = 1
@@ -102,10 +102,8 @@ local function tristate_indicator_light(args)
e.w_write(args.label)
end
-- initial draw
e.redraw()
---@class TriIndicatorLight:graphics_element
local TriIndicatorLight, id = e.complete(true)
return e.complete()
return TriIndicatorLight, id
end
return tristate_indicator_light

View File

@@ -15,13 +15,13 @@ local element = require("graphics.element")
---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw
-- new vertical bar
-- Create a new vertical fill bar indicator element.
---@nodiscard
---@param args vbar_args
---@return graphics_element element, element_id id
local function vbar(args)
---@return VerticalBar element, element_id id
return function (args)
-- create new graphics element base object
local e = element.new(args)
local e = element.new(args --[[@as graphics_args]])
e.value = 0.0
@@ -98,10 +98,8 @@ local function vbar(args)
e.redraw()
end
-- initial draw
e.redraw()
---@class VerticalBar:graphics_element
local VerticalBar, id = e.complete(true)
return e.complete()
return VerticalBar, id
end
return vbar

View File

@@ -18,7 +18,7 @@ local PERIOD = {
flasher.PERIOD = PERIOD
local active = false
local registry = { {}, {}, {} } -- one registry table per period
local registry = { {}, {}, {} } ---@type [ function[], function[], function [] ] one registry table per period
local callback_counter = 0
-- blink registered indicators<br>

410
pocket/config/system.lua Normal file
View File

@@ -0,0 +1,410 @@
local log = require("scada-common.log")
local types = require("scada-common.types")
local util = require("scada-common.util")
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 Checkbox = require("graphics.elements.controls.Checkbox")
local PushButton = require("graphics.elements.controls.PushButton")
local RadioButton = require("graphics.elements.controls.RadioButton")
local NumberField = require("graphics.elements.form.NumberField")
local TextField = require("graphics.elements.form.TextField")
local tri = util.trinary
local cpair = core.cpair
local RIGHT = core.ALIGN.RIGHT
local self = {
importing_legacy = false,
show_auth_key = nil, ---@type function
show_key_btn = nil, ---@type PushButton
auth_key_textbox = nil, ---@type TextBox
auth_key_value = ""
}
local system = {}
-- create the system configuration view
---@param tool_ctl _pkt_cfg_tool_ctl
---@param main_pane MultiPane
---@param cfg_sys [ pkt_config, pkt_config, pkt_config, { [1]: string, [2]: string, [3]: any }[], function ]
---@param divs Div[]
---@param style { [string]: cpair }
---@param exit function
function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit)
local settings_cfg, ini_cfg, tmp_cfg, fields, load_settings = cfg_sys[1], cfg_sys[2], cfg_sys[3], cfg_sys[4], cfg_sys[5]
local ui_cfg, net_cfg, log_cfg, summary = divs[1], divs[2], divs[3], divs[4]
local bw_fg_bg = style.bw_fg_bg
local g_lg_fg_bg = style.g_lg_fg_bg
local nav_fg_bg = style.nav_fg_bg
local btn_act_fg_bg = style.btn_act_fg_bg
local btn_dis_fg_bg = style.btn_dis_fg_bg
--#region Pocket UI
local ui_c_1 = Div{parent=ui_cfg,x=2,y=4,width=24}
TextBox{parent=ui_cfg,x=1,y=2,text=" Pocket UI",fg_bg=cpair(colors.black,colors.lime)}
TextBox{parent=ui_c_1,x=1,y=1,height=3,text="You may customize units below."}
TextBox{parent=ui_c_1,x=1,y=4,text="Temperature Scale"}
local temp_scale = RadioButton{parent=ui_c_1,x=1,y=5,default=ini_cfg.TempScale,options=types.TEMP_SCALE_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime}
TextBox{parent=ui_c_1,x=1,y=10,text="Energy Scale"}
local energy_scale = RadioButton{parent=ui_c_1,x=1,y=11,default=ini_cfg.EnergyScale,options=types.ENERGY_SCALE_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime}
local function submit_ui_opts()
tmp_cfg.TempScale = temp_scale.get_value()
tmp_cfg.EnergyScale = energy_scale.get_value()
main_pane.set_value(3)
end
PushButton{parent=ui_c_1,x=1,y=15,text="\x1b Back",callback=function()main_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
PushButton{parent=ui_c_1,x=19,y=15,text="Next \x1a",callback=submit_ui_opts,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
--#endregion
--#region Network
local net_c_1 = Div{parent=net_cfg,x=2,y=4,width=24}
local net_c_2 = Div{parent=net_cfg,x=2,y=4,width=24}
local net_c_3 = Div{parent=net_cfg,x=2,y=4,width=24}
local net_c_4 = Div{parent=net_cfg,x=2,y=4,width=24}
local net_pane = MultiPane{parent=net_cfg,x=1,y=4,panes={net_c_1,net_c_2,net_c_3,net_c_4}}
TextBox{parent=net_cfg,x=1,y=2,text=" Network Configuration",fg_bg=cpair(colors.black,colors.lightBlue)}
TextBox{parent=net_c_1,x=1,y=1,text="Set network channels."}
TextBox{parent=net_c_1,x=1,y=3,height=4,text="Each of the named channels must be the same within a particular SCADA network.",fg_bg=g_lg_fg_bg}
TextBox{parent=net_c_1,x=1,y=8,width=18,text="Supervisor Channel"}
local svr_chan = NumberField{parent=net_c_1,x=1,y=9,width=7,default=ini_cfg.SVR_Channel,min=1,max=65535,fg_bg=bw_fg_bg}
TextBox{parent=net_c_1,x=9,y=9,height=4,text="[SVR_CHANNEL]",fg_bg=g_lg_fg_bg}
TextBox{parent=net_c_1,x=1,y=10,width=19,text="Coordinator Channel"}
local crd_chan = NumberField{parent=net_c_1,x=1,y=11,width=7,default=ini_cfg.CRD_Channel,min=1,max=65535,fg_bg=bw_fg_bg}
TextBox{parent=net_c_1,x=9,y=11,height=4,text="[CRD_CHANNEL]",fg_bg=g_lg_fg_bg}
TextBox{parent=net_c_1,x=1,y=12,width=14,text="Pocket Channel"}
local pkt_chan = NumberField{parent=net_c_1,x=1,y=13,width=7,default=ini_cfg.PKT_Channel,min=1,max=65535,fg_bg=bw_fg_bg}
TextBox{parent=net_c_1,x=9,y=13,height=4,text="[PKT_CHANNEL]",fg_bg=g_lg_fg_bg}
local chan_err = TextBox{parent=net_c_1,x=1,y=14,width=24,text="Please set all channels.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
local function submit_channels()
local svr_c, crd_c, pkt_c = tonumber(svr_chan.get_value()), tonumber(crd_chan.get_value()), tonumber(pkt_chan.get_value())
if svr_c ~= nil and crd_c ~= nil and pkt_c ~= nil then
tmp_cfg.SVR_Channel, tmp_cfg.CRD_Channel, tmp_cfg.PKT_Channel = svr_c, crd_c, pkt_c
net_pane.set_value(2)
chan_err.hide(true)
else chan_err.show() end
end
PushButton{parent=net_c_1,x=1,y=15,text="\x1b Back",callback=function()main_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
PushButton{parent=net_c_1,x=19,y=15,text="Next \x1a",callback=submit_channels,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
TextBox{parent=net_c_2,x=1,y=1,text="Set connection timeout."}
TextBox{parent=net_c_2,x=1,y=3,height=7,text="You generally should not need to modify this. On slow servers, you can try to increase this to make the system wait longer before assuming a disconnection.",fg_bg=g_lg_fg_bg}
TextBox{parent=net_c_2,x=1,y=11,width=19,text="Connection Timeout"}
local timeout = NumberField{parent=net_c_2,x=1,y=12,width=7,default=ini_cfg.ConnTimeout,min=2,max=25,max_chars=6,max_frac_digits=2,allow_decimal=true,fg_bg=bw_fg_bg}
TextBox{parent=net_c_2,x=9,y=12,height=2,text="seconds\n(default 5)",fg_bg=g_lg_fg_bg}
local ct_err = TextBox{parent=net_c_2,x=1,y=14,width=24,text="Please set timeout.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
local function submit_timeouts()
local timeout_val = tonumber(timeout.get_value())
if timeout_val ~= nil then
tmp_cfg.ConnTimeout = timeout_val
net_pane.set_value(3)
ct_err.hide(true)
else ct_err.show() end
end
PushButton{parent=net_c_2,x=1,y=15,text="\x1b Back",callback=function()net_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
PushButton{parent=net_c_2,x=19,y=15,text="Next \x1a",callback=submit_timeouts,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
TextBox{parent=net_c_3,x=1,y=1,text="Set the trusted range."}
TextBox{parent=net_c_3,x=1,y=3,height=4,text="Setting this to a value larger than 0 prevents connections with devices that many blocks away.",fg_bg=g_lg_fg_bg}
TextBox{parent=net_c_3,x=1,y=8,height=4,text="This is optional. You can disable this functionality by setting the value to 0.",fg_bg=g_lg_fg_bg}
local range = NumberField{parent=net_c_3,x=1,y=13,width=10,default=ini_cfg.TrustedRange,min=0,max_chars=20,allow_decimal=true,fg_bg=bw_fg_bg}
local tr_err = TextBox{parent=net_c_3,x=1,y=14,width=24,text="Set the trusted range.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
local function submit_tr()
local range_val = tonumber(range.get_value())
if range_val ~= nil then
tmp_cfg.TrustedRange = range_val
net_pane.set_value(4)
tr_err.hide(true)
else tr_err.show() end
end
PushButton{parent=net_c_3,x=1,y=15,text="\x1b Back",callback=function()net_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
PushButton{parent=net_c_3,x=19,y=15,text="Next \x1a",callback=submit_tr,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
TextBox{parent=net_c_4,x=1,y=1,height=4,text="Optionally, set the facility authentication key. Do NOT use one of your passwords."}
TextBox{parent=net_c_4,x=1,y=6,height=6,text="This enables verifying that messages are authentic, so it is intended for security on multiplayer servers.",fg_bg=g_lg_fg_bg}
TextBox{parent=net_c_4,x=1,y=12,text="Facility Auth Key"}
local key, _ = TextField{parent=net_c_4,x=1,y=13,max_len=64,value=ini_cfg.AuthKey,width=24,height=1,fg_bg=bw_fg_bg}
local function censor_key(enable) key.censor(tri(enable, "*", nil)) end
-- declare back first so tabbing makes sense visually
PushButton{parent=net_c_4,x=1,y=15,text="\x1b Back",callback=function()net_pane.set_value(3)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
local hide_key = Checkbox{parent=net_c_4,x=8,y=15,label="Hide Key",box_fg_bg=cpair(colors.lightBlue,colors.black),callback=censor_key}
hide_key.set_value(true)
censor_key(true)
local key_err = TextBox{parent=net_c_4,x=1,y=14,width=24,text="Length must be > 7.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
local function submit_auth()
local v = key.get_value()
if string.len(v) == 0 or string.len(v) >= 8 then
tmp_cfg.AuthKey = key.get_value()
main_pane.set_value(4)
key_err.hide(true)
else key_err.show() end
end
PushButton{parent=net_c_4,x=19,y=15,text="Next \x1a",callback=submit_auth,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
--#endregion
--#region Logging
local log_c_1 = Div{parent=log_cfg,x=2,y=4,width=24}
TextBox{parent=log_cfg,x=1,y=2,text=" Logging Configuration",fg_bg=cpair(colors.black,colors.pink)}
TextBox{parent=log_c_1,x=1,y=1,text="Configure logging below."}
TextBox{parent=log_c_1,x=1,y=3,text="Log File Mode"}
local mode = RadioButton{parent=log_c_1,x=1,y=4,default=ini_cfg.LogMode+1,options={"Append on Startup","Replace on Startup"},callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.pink}
TextBox{parent=log_c_1,x=1,y=7,text="Log File Path"}
local path = TextField{parent=log_c_1,x=1,y=8,width=24,height=1,value=ini_cfg.LogPath,max_len=128,fg_bg=bw_fg_bg}
local en_dbg = Checkbox{parent=log_c_1,x=1,y=10,default=ini_cfg.LogDebug,label="Enable Debug Messages",box_fg_bg=cpair(colors.pink,colors.black)}
TextBox{parent=log_c_1,x=3,y=11,height=4,text="This results in much larger log files. Use only as needed.",fg_bg=g_lg_fg_bg}
local path_err = TextBox{parent=log_c_1,x=1,y=14,width=24,text="Provide a log file path.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
local function submit_log()
if path.get_value() ~= "" then
path_err.hide(true)
tmp_cfg.LogMode = mode.get_value() - 1
tmp_cfg.LogPath = path.get_value()
tmp_cfg.LogDebug = en_dbg.get_value()
tool_ctl.gen_summary(tmp_cfg)
tool_ctl.viewing_config = false
self.importing_legacy = false
tool_ctl.settings_apply.show()
main_pane.set_value(5)
else path_err.show() end
end
PushButton{parent=log_c_1,x=1,y=15,text="\x1b Back",callback=function()main_pane.set_value(3)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
PushButton{parent=log_c_1,x=19,y=15,text="Next \x1a",callback=submit_log,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
--#endregion
--#region Summary and Saving
local sum_c_1 = Div{parent=summary,x=2,y=4,width=24}
local sum_c_2 = Div{parent=summary,x=2,y=4,width=24}
local sum_c_3 = Div{parent=summary,x=2,y=4,width=24}
local sum_c_4 = Div{parent=summary,x=2,y=4,width=24}
local sum_pane = MultiPane{parent=summary,x=1,y=4,panes={sum_c_1,sum_c_2,sum_c_3,sum_c_4}}
TextBox{parent=summary,x=1,y=2,text=" Summary",fg_bg=cpair(colors.black,colors.green)}
local setting_list = ListBox{parent=sum_c_1,x=1,y=1,height=11,width=24,scroll_height=100,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
local function back_from_summary()
if tool_ctl.viewing_config or self.importing_legacy then
main_pane.set_value(1)
tool_ctl.viewing_config = false
self.importing_legacy = false
tool_ctl.settings_apply.show()
else
main_pane.set_value(4)
end
end
---@param element graphics_element
---@param data any
local function try_set(element, data)
if data ~= nil then element.set_value(data) end
end
local function save_and_continue()
for _, field in ipairs(fields) do
local k, v = field[1], tmp_cfg[field[1]]
if v == nil then settings.unset(k) else settings.set(k, v) end
end
if settings.save("/pocket.settings") then
load_settings(settings_cfg, true)
load_settings(ini_cfg)
try_set(temp_scale, ini_cfg.TempScale)
try_set(energy_scale, ini_cfg.EnergyScale)
try_set(svr_chan, ini_cfg.SVR_Channel)
try_set(crd_chan, ini_cfg.CRD_Channel)
try_set(pkt_chan, ini_cfg.PKT_Channel)
try_set(timeout, ini_cfg.ConnTimeout)
try_set(range, ini_cfg.TrustedRange)
try_set(key, ini_cfg.AuthKey)
try_set(mode, ini_cfg.LogMode)
try_set(path, ini_cfg.LogPath)
try_set(en_dbg, ini_cfg.LogDebug)
tool_ctl.view_cfg.enable()
if self.importing_legacy then
self.importing_legacy = false
sum_pane.set_value(3)
else
sum_pane.set_value(2)
end
else
sum_pane.set_value(4)
end
end
PushButton{parent=sum_c_1,x=1,y=15,text="\x1b Back",callback=back_from_summary,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
self.show_key_btn = PushButton{parent=sum_c_1,x=1,y=13,min_width=17,text="Unhide Auth Key",callback=function()self.show_auth_key()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
tool_ctl.settings_apply = PushButton{parent=sum_c_1,x=18,y=15,min_width=7,text="Apply",callback=save_and_continue,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg}
TextBox{parent=sum_c_2,x=1,y=1,text="Settings saved!"}
local function go_home()
main_pane.set_value(1)
net_pane.set_value(1)
sum_pane.set_value(1)
end
PushButton{parent=sum_c_2,x=1,y=15,min_width=6,text="Home",callback=go_home,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
PushButton{parent=sum_c_2,x=19,y=15,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray)}
TextBox{parent=sum_c_3,x=1,y=1,height=4,text="The old config.lua file will now be deleted, then the configurator will exit."}
local function delete_legacy()
fs.delete("/pocket/config.lua")
exit()
end
PushButton{parent=sum_c_3,x=1,y=15,min_width=8,text="Cancel",callback=go_home,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
PushButton{parent=sum_c_3,x=19,y=15,min_width=6,text="OK",callback=delete_legacy,fg_bg=cpair(colors.black,colors.green),active_fg_bg=cpair(colors.white,colors.gray)}
TextBox{parent=sum_c_4,x=1,y=1,height=8,text="Failed to save the settings file.\n\nThere may not be enough space for the modification or server file permissions may be denying writes."}
PushButton{parent=sum_c_4,x=1,y=15,min_width=6,text="Home",callback=go_home,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
PushButton{parent=sum_c_4,x=19,y=15,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray)}
--#endregion
--#region Tool Functions
-- load a legacy config file
function tool_ctl.load_legacy()
local config = require("pocket.config")
tmp_cfg.SVR_Channel = config.SVR_CHANNEL
tmp_cfg.CRD_Channel = config.CRD_CHANNEL
tmp_cfg.PKT_Channel = config.PKT_CHANNEL
tmp_cfg.ConnTimeout = config.COMMS_TIMEOUT
tmp_cfg.TrustedRange = config.TRUSTED_RANGE
tmp_cfg.AuthKey = config.AUTH_KEY or ""
tmp_cfg.LogMode = config.LOG_MODE
tmp_cfg.LogPath = config.LOG_PATH
tmp_cfg.LogDebug = config.LOG_DEBUG or false
tool_ctl.gen_summary(tmp_cfg)
sum_pane.set_value(1)
main_pane.set_value(5)
self.importing_legacy = true
end
-- expose the auth key on the summary page
function self.show_auth_key()
self.show_key_btn.disable()
self.auth_key_textbox.set_value(self.auth_key_value)
end
-- generate the summary list
---@param cfg pkt_config
function tool_ctl.gen_summary(cfg)
setting_list.remove_all()
local alternate = false
local inner_width = setting_list.get_width() - 1
self.show_key_btn.enable()
self.auth_key_value = cfg.AuthKey or "" -- to show auth key
for i = 1, #fields do
local f = fields[i]
local height = 1
local label_w = string.len(f[2])
local val_max_w = (inner_width - label_w) - 1
local raw = cfg[f[1]]
local val = util.strval(raw)
if f[1] == "AuthKey" then
val = string.rep("*", string.len(val))
elseif f[1] == "LogMode" then
val = tri(raw == log.MODE.APPEND, "append", "replace")
elseif f[1] == "TempScale" then
val = util.strval(types.TEMP_SCALE_NAMES[raw])
elseif f[1] == "EnergyScale" then
val = util.strval(types.ENERGY_SCALE_NAMES[raw])
end
if val == "nil" then val = "<not set>" end
local c = tri(alternate, g_lg_fg_bg, cpair(colors.gray,colors.white))
alternate = not alternate
if string.len(val) > val_max_w then
local lines = util.strwrap(val, inner_width)
height = #lines + 1
end
local line = Div{parent=setting_list,height=height,fg_bg=c}
TextBox{parent=line,text=f[2],width=string.len(f[2]),fg_bg=cpair(colors.black,line.get_fg_bg().bkg)}
local textbox
if height > 1 then
textbox = TextBox{parent=line,x=1,y=2,text=val,height=height-1}
else
textbox = TextBox{parent=line,x=label_w+1,y=1,text=val,alignment=RIGHT}
end
if f[1] == "AuthKey" then self.auth_key_textbox = textbox end
end
end
--#endregion
end
return system

View File

@@ -6,21 +6,18 @@ local log = require("scada-common.log")
local types = require("scada-common.types")
local util = require("scada-common.util")
local system = require("pocket.config.system")
local core = require("graphics.core")
local themes = require("graphics.themes")
local DisplayBox = require("graphics.elements.displaybox")
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 DisplayBox = require("graphics.elements.DisplayBox")
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 CheckBox = require("graphics.elements.controls.checkbox")
local PushButton = require("graphics.elements.controls.push_button")
local RadioButton = require("graphics.elements.controls.radio_button")
local NumberField = require("graphics.elements.form.number_field")
local TextField = require("graphics.elements.form.text_field")
local PushButton = require("graphics.elements.controls.PushButton")
local println = util.println
local tri = util.trinary
@@ -28,7 +25,6 @@ local tri = util.trinary
local cpair = core.cpair
local CENTER = core.ALIGN.CENTER
local RIGHT = core.ALIGN.RIGHT
-- changes to the config data/format to let the user know
local changes = {
@@ -46,44 +42,36 @@ style.header = cpair(colors.white, colors.gray)
style.colors = themes.smooth_stone.colors
local bw_fg_bg = cpair(colors.black, colors.white)
local g_lg_fg_bg = cpair(colors.gray, colors.lightGray)
local nav_fg_bg = bw_fg_bg
local btn_act_fg_bg = cpair(colors.white, colors.gray)
local dis_fg_bg = cpair(colors.lightGray,colors.white)
style.bw_fg_bg = cpair(colors.black, colors.white)
style.g_lg_fg_bg = cpair(colors.gray, colors.lightGray)
style.nav_fg_bg = style.bw_fg_bg
style.btn_act_fg_bg = cpair(colors.white, colors.gray)
style.btn_dis_fg_bg = cpair(colors.lightGray, colors.white)
---@class _pkt_cfg_tool_ctl
local tool_ctl = {
ask_config = false,
has_config = false,
viewing_config = false,
importing_legacy = false,
view_cfg = nil, ---@type graphics_element
settings_apply = nil, ---@type graphics_element
view_cfg = nil, ---@type PushButton
settings_apply = nil, ---@type PushButton
set_networked = nil, ---@type function
bundled_emcool = nil, ---@type function
gen_summary = nil, ---@type function
show_current_cfg = nil, ---@type function
load_legacy = nil, ---@type function
show_auth_key = nil, ---@type function
show_key_btn = nil, ---@type graphics_element
auth_key_textbox = nil, ---@type graphics_element
auth_key_value = ""
load_legacy = nil ---@type function
}
---@class pkt_config
local tmp_cfg = {
TempScale = 1,
EnergyScale = 1,
TempScale = 1, ---@type TEMP_SCALE
EnergyScale = 1, ---@type ENERGY_SCALE
SVR_Channel = nil, ---@type integer
CRD_Channel = nil, ---@type integer
PKT_Channel = nil, ---@type integer
ConnTimeout = nil, ---@type number
TrustedRange = nil, ---@type number
AuthKey = nil, ---@type string|nil
LogMode = 0,
LogMode = 0, ---@type LOG_MODE
LogPath = "",
LogDebug = false,
}
@@ -122,8 +110,14 @@ local function load_settings(target, raw)
end
-- create the config view
---@param display graphics_element
---@param display DisplayBox
local function config_view(display)
local bw_fg_bg = style.bw_fg_bg
local g_lg_fg_bg = style.g_lg_fg_bg
local nav_fg_bg = style.nav_fg_bg
local btn_act_fg_bg = style.btn_act_fg_bg
local btn_dis_fg_bg = style.btn_dis_fg_bg
---@diagnostic disable-next-line: undefined-field
local function exit() os.queueEvent("terminate") end
@@ -140,7 +134,7 @@ local function config_view(display)
local main_pane = MultiPane{parent=root_pane_div,x=1,y=1,panes={main_page,ui_cfg,net_cfg,log_cfg,summary,changelog}}
-- Main Page
--#region Main Page
local y_start = 7
@@ -164,286 +158,25 @@ local function config_view(display)
end
PushButton{parent=main_page,x=2,y=y_start,min_width=18,text="Configure Device",callback=function()main_pane.set_value(2)end,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg}
tool_ctl.view_cfg = PushButton{parent=main_page,x=2,y=y_start+2,min_width=20,text="View Configuration",callback=view_config,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg,dis_fg_bg=dis_fg_bg}
tool_ctl.view_cfg = PushButton{parent=main_page,x=2,y=y_start+2,min_width=20,text="View Configuration",callback=view_config,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
if not tool_ctl.has_config then tool_ctl.view_cfg.disable() end
PushButton{parent=main_page,x=2,y=18,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=btn_act_fg_bg}
PushButton{parent=main_page,x=14,y=18,min_width=12,text="Change Log",callback=function()main_pane.set_value(6)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
--#region Pocket UI
--#endregion
local ui_c_1 = Div{parent=ui_cfg,x=2,y=4,width=24}
--#region System Configuration
TextBox{parent=ui_cfg,x=1,y=2,text=" Pocket UI",fg_bg=cpair(colors.black,colors.lime)}
local settings = { settings_cfg, ini_cfg, tmp_cfg, fields, load_settings }
local divs = { ui_cfg, net_cfg, log_cfg, summary }
TextBox{parent=ui_c_1,x=1,y=1,height=3,text="You may customize units below."}
TextBox{parent=ui_c_1,x=1,y=4,text="Temperature Scale"}
local temp_scale = RadioButton{parent=ui_c_1,x=1,y=5,default=ini_cfg.TempScale,options=types.TEMP_SCALE_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime}
TextBox{parent=ui_c_1,x=1,y=10,text="Energy Scale"}
local energy_scale = RadioButton{parent=ui_c_1,x=1,y=11,default=ini_cfg.EnergyScale,options=types.ENERGY_SCALE_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime}
local function submit_ui_opts()
tmp_cfg.TempScale = temp_scale.get_value()
tmp_cfg.EnergyScale = energy_scale.get_value()
main_pane.set_value(3)
end
PushButton{parent=ui_c_1,x=1,y=15,text="\x1b Back",callback=function()main_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
PushButton{parent=ui_c_1,x=19,y=15,text="Next \x1a",callback=submit_ui_opts,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
system.create(tool_ctl, main_pane, settings, divs, style, exit)
--#endregion
--#region Network
local net_c_1 = Div{parent=net_cfg,x=2,y=4,width=24}
local net_c_2 = Div{parent=net_cfg,x=2,y=4,width=24}
local net_c_3 = Div{parent=net_cfg,x=2,y=4,width=24}
local net_c_4 = Div{parent=net_cfg,x=2,y=4,width=24}
local net_pane = MultiPane{parent=net_cfg,x=1,y=4,panes={net_c_1,net_c_2,net_c_3,net_c_4}}
TextBox{parent=net_cfg,x=1,y=2,text=" Network Configuration",fg_bg=cpair(colors.black,colors.lightBlue)}
TextBox{parent=net_c_1,x=1,y=1,text="Set network channels."}
TextBox{parent=net_c_1,x=1,y=3,height=4,text="Each of the named channels must be the same within a particular SCADA network.",fg_bg=g_lg_fg_bg}
TextBox{parent=net_c_1,x=1,y=8,width=18,text="Supervisor Channel"}
local svr_chan = NumberField{parent=net_c_1,x=1,y=9,width=7,default=ini_cfg.SVR_Channel,min=1,max=65535,fg_bg=bw_fg_bg}
TextBox{parent=net_c_1,x=9,y=9,height=4,text="[SVR_CHANNEL]",fg_bg=g_lg_fg_bg}
TextBox{parent=net_c_1,x=1,y=10,width=19,text="Coordinator Channel"}
local crd_chan = NumberField{parent=net_c_1,x=1,y=11,width=7,default=ini_cfg.CRD_Channel,min=1,max=65535,fg_bg=bw_fg_bg}
TextBox{parent=net_c_1,x=9,y=11,height=4,text="[CRD_CHANNEL]",fg_bg=g_lg_fg_bg}
TextBox{parent=net_c_1,x=1,y=12,width=14,text="Pocket Channel"}
local pkt_chan = NumberField{parent=net_c_1,x=1,y=13,width=7,default=ini_cfg.PKT_Channel,min=1,max=65535,fg_bg=bw_fg_bg}
TextBox{parent=net_c_1,x=9,y=13,height=4,text="[PKT_CHANNEL]",fg_bg=g_lg_fg_bg}
local chan_err = TextBox{parent=net_c_1,x=1,y=14,width=24,text="Please set all channels.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
local function submit_channels()
local svr_c, crd_c, pkt_c = tonumber(svr_chan.get_value()), tonumber(crd_chan.get_value()), tonumber(pkt_chan.get_value())
if svr_c ~= nil and crd_c ~= nil and pkt_c ~= nil then
tmp_cfg.SVR_Channel, tmp_cfg.CRD_Channel, tmp_cfg.PKT_Channel = svr_c, crd_c, pkt_c
net_pane.set_value(2)
chan_err.hide(true)
else chan_err.show() end
end
PushButton{parent=net_c_1,x=1,y=15,text="\x1b Back",callback=function()main_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
PushButton{parent=net_c_1,x=19,y=15,text="Next \x1a",callback=submit_channels,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
TextBox{parent=net_c_2,x=1,y=1,text="Set connection timeout."}
TextBox{parent=net_c_2,x=1,y=3,height=7,text="You generally should not need to modify this. On slow servers, you can try to increase this to make the system wait longer before assuming a disconnection.",fg_bg=g_lg_fg_bg}
TextBox{parent=net_c_2,x=1,y=11,width=19,text="Connection Timeout"}
local timeout = NumberField{parent=net_c_2,x=1,y=12,width=7,default=ini_cfg.ConnTimeout,min=2,max=25,max_chars=6,max_frac_digits=2,allow_decimal=true,fg_bg=bw_fg_bg}
TextBox{parent=net_c_2,x=9,y=12,height=2,text="seconds\n(default 5)",fg_bg=g_lg_fg_bg}
local ct_err = TextBox{parent=net_c_2,x=1,y=14,width=24,text="Please set timeout.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
local function submit_timeouts()
local timeout_val = tonumber(timeout.get_value())
if timeout_val ~= nil then
tmp_cfg.ConnTimeout = timeout_val
net_pane.set_value(3)
ct_err.hide(true)
else ct_err.show() end
end
PushButton{parent=net_c_2,x=1,y=15,text="\x1b Back",callback=function()net_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
PushButton{parent=net_c_2,x=19,y=15,text="Next \x1a",callback=submit_timeouts,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
TextBox{parent=net_c_3,x=1,y=1,text="Set the trusted range."}
TextBox{parent=net_c_3,x=1,y=3,height=4,text="Setting this to a value larger than 0 prevents connections with devices that many blocks away.",fg_bg=g_lg_fg_bg}
TextBox{parent=net_c_3,x=1,y=8,height=4,text="This is optional. You can disable this functionality by setting the value to 0.",fg_bg=g_lg_fg_bg}
local range = NumberField{parent=net_c_3,x=1,y=13,width=10,default=ini_cfg.TrustedRange,min=0,max_chars=20,allow_decimal=true,fg_bg=bw_fg_bg}
local tr_err = TextBox{parent=net_c_3,x=1,y=14,width=24,text="Set the trusted range.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
local function submit_tr()
local range_val = tonumber(range.get_value())
if range_val ~= nil then
tmp_cfg.TrustedRange = range_val
net_pane.set_value(4)
tr_err.hide(true)
else tr_err.show() end
end
PushButton{parent=net_c_3,x=1,y=15,text="\x1b Back",callback=function()net_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
PushButton{parent=net_c_3,x=19,y=15,text="Next \x1a",callback=submit_tr,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
TextBox{parent=net_c_4,x=1,y=1,height=4,text="Optionally, set the facility authentication key. Do NOT use one of your passwords."}
TextBox{parent=net_c_4,x=1,y=6,height=6,text="This enables verifying that messages are authentic, so it is intended for security on multiplayer servers.",fg_bg=g_lg_fg_bg}
TextBox{parent=net_c_4,x=1,y=12,text="Facility Auth Key"}
local key, _, censor = TextField{parent=net_c_4,x=1,y=13,max_len=64,value=ini_cfg.AuthKey,width=24,height=1,fg_bg=bw_fg_bg}
local function censor_key(enable) censor(util.trinary(enable, "*", nil)) end
-- declare back first so tabbing makes sense visually
PushButton{parent=net_c_4,x=1,y=15,text="\x1b Back",callback=function()net_pane.set_value(3)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
local hide_key = CheckBox{parent=net_c_4,x=8,y=15,label="Hide Key",box_fg_bg=cpair(colors.lightBlue,colors.black),callback=censor_key}
hide_key.set_value(true)
censor_key(true)
local key_err = TextBox{parent=net_c_4,x=1,y=14,width=24,text="Length must be > 7.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
local function submit_auth()
local v = key.get_value()
if string.len(v) == 0 or string.len(v) >= 8 then
tmp_cfg.AuthKey = key.get_value()
main_pane.set_value(4)
key_err.hide(true)
else key_err.show() end
end
PushButton{parent=net_c_4,x=19,y=15,text="Next \x1a",callback=submit_auth,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
--#endregion
--#region Logging
local log_c_1 = Div{parent=log_cfg,x=2,y=4,width=24}
TextBox{parent=log_cfg,x=1,y=2,text=" Logging Configuration",fg_bg=cpair(colors.black,colors.pink)}
TextBox{parent=log_c_1,x=1,y=1,text="Configure logging below."}
TextBox{parent=log_c_1,x=1,y=3,text="Log File Mode"}
local mode = RadioButton{parent=log_c_1,x=1,y=4,default=ini_cfg.LogMode+1,options={"Append on Startup","Replace on Startup"},callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.pink}
TextBox{parent=log_c_1,x=1,y=7,text="Log File Path"}
local path = TextField{parent=log_c_1,x=1,y=8,width=24,height=1,value=ini_cfg.LogPath,max_len=128,fg_bg=bw_fg_bg}
local en_dbg = CheckBox{parent=log_c_1,x=1,y=10,default=ini_cfg.LogDebug,label="Enable Debug Messages",box_fg_bg=cpair(colors.pink,colors.black)}
TextBox{parent=log_c_1,x=3,y=11,height=4,text="This results in much larger log files. Use only as needed.",fg_bg=g_lg_fg_bg}
local path_err = TextBox{parent=log_c_1,x=1,y=14,width=24,text="Provide a log file path.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
local function submit_log()
if path.get_value() ~= "" then
path_err.hide(true)
tmp_cfg.LogMode = mode.get_value() - 1
tmp_cfg.LogPath = path.get_value()
tmp_cfg.LogDebug = en_dbg.get_value()
tool_ctl.gen_summary(tmp_cfg)
tool_ctl.viewing_config = false
tool_ctl.importing_legacy = false
tool_ctl.settings_apply.show()
main_pane.set_value(5)
else path_err.show() end
end
PushButton{parent=log_c_1,x=1,y=15,text="\x1b Back",callback=function()main_pane.set_value(3)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
PushButton{parent=log_c_1,x=19,y=15,text="Next \x1a",callback=submit_log,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
--#endregion
--#region Summary and Saving
local sum_c_1 = Div{parent=summary,x=2,y=4,width=24}
local sum_c_2 = Div{parent=summary,x=2,y=4,width=24}
local sum_c_3 = Div{parent=summary,x=2,y=4,width=24}
local sum_c_4 = Div{parent=summary,x=2,y=4,width=24}
local sum_pane = MultiPane{parent=summary,x=1,y=4,panes={sum_c_1,sum_c_2,sum_c_3,sum_c_4}}
TextBox{parent=summary,x=1,y=2,text=" Summary",fg_bg=cpair(colors.black,colors.green)}
local setting_list = ListBox{parent=sum_c_1,x=1,y=1,height=11,width=24,scroll_height=100,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
local function back_from_summary()
if tool_ctl.viewing_config or tool_ctl.importing_legacy then
main_pane.set_value(1)
tool_ctl.viewing_config = false
tool_ctl.importing_legacy = false
tool_ctl.settings_apply.show()
else
main_pane.set_value(4)
end
end
---@param element graphics_element
---@param data any
local function try_set(element, data)
if data ~= nil then element.set_value(data) end
end
local function save_and_continue()
for _, field in ipairs(fields) do
local k, v = field[1], tmp_cfg[field[1]]
if v == nil then settings.unset(k) else settings.set(k, v) end
end
if settings.save("/pocket.settings") then
load_settings(settings_cfg, true)
load_settings(ini_cfg)
try_set(temp_scale, ini_cfg.TempScale)
try_set(energy_scale, ini_cfg.EnergyScale)
try_set(svr_chan, ini_cfg.SVR_Channel)
try_set(crd_chan, ini_cfg.CRD_Channel)
try_set(pkt_chan, ini_cfg.PKT_Channel)
try_set(timeout, ini_cfg.ConnTimeout)
try_set(range, ini_cfg.TrustedRange)
try_set(key, ini_cfg.AuthKey)
try_set(mode, ini_cfg.LogMode)
try_set(path, ini_cfg.LogPath)
try_set(en_dbg, ini_cfg.LogDebug)
tool_ctl.view_cfg.enable()
if tool_ctl.importing_legacy then
tool_ctl.importing_legacy = false
sum_pane.set_value(3)
else
sum_pane.set_value(2)
end
else
sum_pane.set_value(4)
end
end
PushButton{parent=sum_c_1,x=1,y=15,text="\x1b Back",callback=back_from_summary,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
tool_ctl.show_key_btn = PushButton{parent=sum_c_1,x=1,y=13,min_width=17,text="Unhide Auth Key",callback=function()tool_ctl.show_auth_key()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg,dis_fg_bg=dis_fg_bg}
tool_ctl.settings_apply = PushButton{parent=sum_c_1,x=18,y=15,min_width=7,text="Apply",callback=save_and_continue,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg}
TextBox{parent=sum_c_2,x=1,y=1,text="Settings saved!"}
local function go_home()
main_pane.set_value(1)
net_pane.set_value(1)
sum_pane.set_value(1)
end
PushButton{parent=sum_c_2,x=1,y=15,min_width=6,text="Home",callback=go_home,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
PushButton{parent=sum_c_2,x=19,y=15,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray)}
TextBox{parent=sum_c_3,x=1,y=1,height=4,text="The old config.lua file will now be deleted, then the configurator will exit."}
local function delete_legacy()
fs.delete("/pocket/config.lua")
exit()
end
PushButton{parent=sum_c_3,x=1,y=15,min_width=8,text="Cancel",callback=go_home,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
PushButton{parent=sum_c_3,x=19,y=15,min_width=6,text="OK",callback=delete_legacy,fg_bg=cpair(colors.black,colors.green),active_fg_bg=cpair(colors.white,colors.gray)}
TextBox{parent=sum_c_4,x=1,y=1,height=8,text="Failed to save the settings file.\n\nThere may not be enough space for the modification or server file permissions may be denying writes."}
PushButton{parent=sum_c_4,x=1,y=15,min_width=6,text="Home",callback=go_home,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
PushButton{parent=sum_c_4,x=19,y=15,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray)}
--#endregion
-- Config Change Log
--#region Config Change Log
local cl = Div{parent=changelog,x=2,y=4,width=24}
@@ -462,87 +195,7 @@ local function config_view(display)
PushButton{parent=cl,x=1,y=15,text="\x1b Back",callback=function()main_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
-- set tool functions now that we have the elements
-- load a legacy config file
function tool_ctl.load_legacy()
local config = require("pocket.config")
tmp_cfg.SVR_Channel = config.SVR_CHANNEL
tmp_cfg.CRD_Channel = config.CRD_CHANNEL
tmp_cfg.PKT_Channel = config.PKT_CHANNEL
tmp_cfg.ConnTimeout = config.COMMS_TIMEOUT
tmp_cfg.TrustedRange = config.TRUSTED_RANGE
tmp_cfg.AuthKey = config.AUTH_KEY or ""
tmp_cfg.LogMode = config.LOG_MODE
tmp_cfg.LogPath = config.LOG_PATH
tmp_cfg.LogDebug = config.LOG_DEBUG or false
tool_ctl.gen_summary(tmp_cfg)
sum_pane.set_value(1)
main_pane.set_value(5)
tool_ctl.importing_legacy = true
end
-- expose the auth key on the summary page
function tool_ctl.show_auth_key()
tool_ctl.show_key_btn.disable()
tool_ctl.auth_key_textbox.set_value(tool_ctl.auth_key_value)
end
-- generate the summary list
---@param cfg pkt_config
function tool_ctl.gen_summary(cfg)
setting_list.remove_all()
local alternate = false
local inner_width = setting_list.get_width() - 1
tool_ctl.show_key_btn.enable()
tool_ctl.auth_key_value = cfg.AuthKey or "" -- to show auth key
for i = 1, #fields do
local f = fields[i]
local height = 1
local label_w = string.len(f[2])
local val_max_w = (inner_width - label_w) - 1
local raw = cfg[f[1]]
local val = util.strval(raw)
if f[1] == "AuthKey" then
val = string.rep("*", string.len(val))
elseif f[1] == "LogMode" then
val = util.trinary(raw == log.MODE.APPEND, "append", "replace")
elseif f[1] == "TempScale" then
val = util.strval(types.TEMP_SCALE_NAMES[raw])
elseif f[1] == "EnergyScale" then
val = util.strval(types.ENERGY_SCALE_NAMES[raw])
end
if val == "nil" then val = "<not set>" end
local c = util.trinary(alternate, g_lg_fg_bg, cpair(colors.gray,colors.white))
alternate = not alternate
if string.len(val) > val_max_w then
local lines = util.strwrap(val, inner_width)
height = #lines + 1
end
local line = Div{parent=setting_list,height=height,fg_bg=c}
TextBox{parent=line,text=f[2],width=string.len(f[2]),fg_bg=cpair(colors.black,line.get_fg_bg().bkg)}
local textbox
if height > 1 then
textbox = TextBox{parent=line,x=1,y=2,text=val,height=height-1}
else
textbox = TextBox{parent=line,x=label_w+1,y=1,text=val,alignment=RIGHT}
end
if f[1] == "AuthKey" then tool_ctl.auth_key_textbox = textbox end
end
end
--#endregion
end
-- reset terminal screen

View File

@@ -39,15 +39,6 @@ local io = {
ps = psil.create()
}
-- luacheck: no unused args
-- placeholder acknowledge function for type hinting
---@param success boolean
---@diagnostic disable-next-line: unused-local
local function __generic_ack(success) end
-- luacheck: unused args
local config = nil ---@type pkt_config
local comms = nil ---@type pocket_comms
@@ -92,16 +83,18 @@ function iocontrol.init_core(pkt_comms, nav, cfg)
get_tone_states = function () comms.diag__get_alarm_tones() end,
ready_warn = nil, ---@type graphics_element
tone_buttons = {},
alarm_buttons = {},
tone_indicators = {} -- indicators to update from supervisor tone states
ready_warn = nil, ---@type TextBox
tone_buttons = {}, ---@type SwitchButton[]
alarm_buttons = {}, ---@type Checkbox[]
tone_indicators = {} ---@type IndicatorLight[] indicators to update from supervisor tone states
}
-- API access
---@class pocket_ioctl_api
io.api = {
get_unit = function (unit) comms.api__get_unit(unit) end
get_unit = function (unit) comms.api__get_unit(unit) end,
get_ctrl = function () comms.api__get_control() end,
get_proc = function () comms.api__get_process() end
}
end
@@ -145,6 +138,8 @@ function iocontrol.init_fac(conf)
all_sys_ok = false,
rtu_count = 0,
status_lines = { "", "" },
auto_ready = false,
auto_active = false,
auto_ramping = false,
@@ -166,24 +161,21 @@ function iocontrol.init_fac(conf)
radiation = types.new_zero_radiation_reading(),
start_ack = __generic_ack,
stop_ack = __generic_ack,
scram_ack = __generic_ack,
ack_alarms_ack = __generic_ack,
start_ack = nil, ---@type fun(success: boolean)
stop_ack = nil, ---@type fun(success: boolean)
scram_ack = nil, ---@type fun(success: boolean)
ack_alarms_ack = nil, ---@type fun(success: boolean)
ps = psil.create(),
induction_ps_tbl = {},
induction_data_tbl = {},
induction_ps_tbl = {}, ---@type psil[]
induction_data_tbl = {}, ---@type imatrix_session_db[]
sps_ps_tbl = {},
sps_data_tbl = {},
sps_ps_tbl = {}, ---@type psil[]
sps_data_tbl = {}, ---@type sps_session_db[]
tank_ps_tbl = {},
tank_data_tbl = {},
env_d_ps = psil.create(),
env_d_data = {}
tank_ps_tbl = {}, ---@type psil[]
tank_data_tbl = {} ---@type dynamicv_session_db[]
}
-- create induction and SPS tables (currently only 1 of each is supported)
@@ -192,107 +184,14 @@ function iocontrol.init_fac(conf)
table.insert(io.facility.sps_ps_tbl, psil.create())
table.insert(io.facility.sps_data_tbl, {})
-- determine tank information
if io.facility.tank_mode == 0 then
io.facility.tank_defs = {}
-- on facility tank mode 0, setup tank defs to match unit tank option
for i = 1, conf.num_units do
io.facility.tank_defs[i] = util.trinary(conf.cooling.r_cool[i].TankConnection, 1, 0)
end
io.facility.tank_list = { table.unpack(io.facility.tank_defs) }
else
-- decode the layout of tanks from the connections definitions
local tank_mode = io.facility.tank_mode
local tank_defs = io.facility.tank_defs
local tank_list = { table.unpack(tank_defs) }
local function calc_fdef(start_idx, end_idx)
local first = 4
for i = start_idx, end_idx do
if io.facility.tank_defs[i] == 2 then
if i < first then first = i end
end
end
return first
end
if tank_mode == 1 then
-- (1) 1 total facility tank (A A A A)
local first_fdef = calc_fdef(1, #tank_defs)
for i = 1, #tank_defs do
if i > first_fdef and tank_defs[i] == 2 then
tank_list[i] = 0
end
end
elseif tank_mode == 2 then
-- (2) 2 total facility tanks (A A A B)
local first_fdef = calc_fdef(1, math.min(3, #tank_defs))
for i = 1, #tank_defs do
if (i ~= 4) and (i > first_fdef) and (tank_defs[i] == 2) then
tank_list[i] = 0
end
end
elseif tank_mode == 3 then
-- (3) 2 total facility tanks (A A B B)
for _, a in pairs({ 1, 3 }) do
local b = a + 1
if (tank_defs[a] == 2) and (tank_defs[b] == 2) then
tank_list[b] = 0
end
end
elseif tank_mode == 4 then
-- (4) 2 total facility tanks (A B B B)
local first_fdef = calc_fdef(2, #tank_defs)
for i = 1, #tank_defs do
if (i ~= 1) and (i > first_fdef) and (tank_defs[i] == 2) then
tank_list[i] = 0
end
end
elseif tank_mode == 5 then
-- (5) 3 total facility tanks (A A B C)
local first_fdef = calc_fdef(1, math.min(2, #tank_defs))
for i = 1, #tank_defs do
if (not (i == 3 or i == 4)) and (i > first_fdef) and (tank_defs[i] == 2) then
tank_list[i] = 0
end
end
elseif tank_mode == 6 then
-- (6) 3 total facility tanks (A B B C)
local first_fdef = calc_fdef(2, math.min(3, #tank_defs))
for i = 1, #tank_defs do
if (not (i == 1 or i == 4)) and (i > first_fdef) and (tank_defs[i] == 2) then
tank_list[i] = 0
end
end
elseif tank_mode == 7 then
-- (7) 3 total facility tanks (A B C C)
local first_fdef = calc_fdef(3, #tank_defs)
for i = 1, #tank_defs do
if (not (i == 1 or i == 2)) and (i > first_fdef) and (tank_defs[i] == 2) then
tank_list[i] = 0
end
end
end
io.facility.tank_list = tank_list
end
-- create facility tank tables
for i = 1, #io.facility.tank_list do
if io.facility.tank_list[i] == 2 then
table.insert(io.facility.tank_ps_tbl, psil.create())
table.insert(io.facility.tank_data_tbl, {})
end
end
-- create unit data structures
io.units = {}
io.units = {} ---@type pioctl_unit[]
for i = 1, conf.num_units do
---@class pioctl_unit
local entry = {
unit_id = i,
connected = false,
---@type { boilers: { connected: boolean, faulted: boolean }[], turbines: { connected: boolean, faulted: boolean }[] }
rtu_hw = {},
num_boilers = 0,
@@ -300,6 +199,11 @@ function iocontrol.init_fac(conf)
num_snas = 0,
has_tank = conf.cooling.r_cool[i].TankConnection,
status_lines = { "", "" },
auto_ready = false,
auto_degraded = false,
control_state = false,
burn_rate_cmd = 0.0,
radiation = types.new_zero_radiation_reading(),
@@ -323,27 +227,27 @@ function iocontrol.init_fac(conf)
ack_alarms = function () process.ack_all_alarms(i) end,
set_burn = function (rate) process.set_rate(i, rate) end, ---@param rate number burn rate
start_ack = __generic_ack,
scram_ack = __generic_ack,
reset_rps_ack = __generic_ack,
ack_alarms_ack = __generic_ack,
start_ack = nil, ---@type fun(success: boolean)
scram_ack = nil, ---@type fun(success: boolean)
reset_rps_ack = nil, ---@type fun(success: boolean)
ack_alarms_ack = nil, ---@type fun(success: boolean)
---@type alarms
---@type { [ALARM]: ALARM_STATE }
alarms = { ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE },
annunciator = {}, ---@type annunciator
unit_ps = psil.create(),
reactor_data = {}, ---@type reactor_db
reactor_data = types.new_reactor_db(),
boiler_ps_tbl = {},
boiler_data_tbl = {},
boiler_ps_tbl = {}, ---@type psil[]
boiler_data_tbl = {}, ---@type boilerv_session_db[]
turbine_ps_tbl = {},
turbine_data_tbl = {},
turbine_ps_tbl = {}, ---@type psil[]
turbine_data_tbl = {}, ---@type turbinev_session_db[]
tank_ps_tbl = {},
tank_data_tbl = {}
tank_ps_tbl = {}, ---@type psil[]
tank_data_tbl = {} ---@type dynamicv_session_db[]
}
-- on other facility modes, overwrite unit TANK option with facility tank defs
@@ -485,7 +389,7 @@ end
-- update unit status data from API_GET_UNIT
---@param data table
function iocontrol.record_unit_data(data)
local unit = io.units[data[1]] ---@type pioctl_unit
local unit = io.units[data[1]]
unit.connected = data[2]
unit.rtu_hw = data[3]
@@ -625,16 +529,16 @@ function iocontrol.record_unit_data(data)
end
end
if type(unit.reactor_data.rps_status) == "table" then
for key, val in pairs(unit.reactor_data.rps_status) do
unit.unit_ps.publish(key, val)
end
end
if type(unit.reactor_data.mek_status) == "table" then
for key, val in pairs(unit.reactor_data.mek_status) do
for key, val in pairs(unit.reactor_data.mek_struct) do
unit.unit_ps.publish(key, val)
end
for key, val in pairs(unit.reactor_data.mek_status) do
unit.unit_ps.publish(key, val)
end
end
@@ -650,8 +554,8 @@ function iocontrol.record_unit_data(data)
unit.boiler_data_tbl = data[8]
for id = 1, #unit.boiler_data_tbl do
local boiler = unit.boiler_data_tbl[id] ---@type boilerv_session_db
local ps = unit.boiler_ps_tbl[id] ---@type psil
local boiler = unit.boiler_data_tbl[id]
local ps = unit.boiler_ps_tbl[id]
local boiler_status = 1
local computed_status = 1
@@ -683,8 +587,8 @@ function iocontrol.record_unit_data(data)
unit.turbine_data_tbl = data[9]
for id = 1, #unit.turbine_data_tbl do
local turbine = unit.turbine_data_tbl[id] ---@type turbinev_session_db
local ps = unit.turbine_ps_tbl[id] ---@type psil
local turbine = unit.turbine_data_tbl[id]
local ps = unit.turbine_ps_tbl[id]
local turbine_status = 1
local computed_status = 1
@@ -730,21 +634,6 @@ function iocontrol.record_unit_data(data)
local function white(text) return { text = text, color = colors.white } end
local function blue(text) return { text = text, color = colors.blue } end
-- unit.reactor_data.rps_status = {
-- high_dmg = false,
-- high_temp = false,
-- low_cool = false,
-- ex_waste = false,
-- ex_hcool = false,
-- no_fuel = false,
-- fault = false,
-- timeout = false,
-- manual = false,
-- automatic = false,
-- sys_fail = false,
-- force_dis = false
-- }
-- if unit.reactor_data.rps_status then
-- for k, v in pairs(unit.alarms) do
-- unit.alarms[k] = ALARM_STATE.TRIPPED
@@ -928,6 +817,109 @@ function iocontrol.record_unit_data(data)
--#endregion
end
-- update control app with unit data from API_GET_CTRL
---@param data table
function iocontrol.record_control_data(data)
for u_id = 1, #data do
local unit = io.units[u_id]
local u_data = data[u_id]
unit.connected = u_data[1]
unit.reactor_data.rps_tripped = u_data[2]
unit.unit_ps.publish("rps_tripped", u_data[2])
unit.reactor_data.mek_status.status = u_data[3]
unit.unit_ps.publish("status", u_data[3])
unit.reactor_data.mek_status.temp = u_data[4]
unit.unit_ps.publish("temp", u_data[4])
unit.reactor_data.mek_status.burn_rate = u_data[5]
unit.unit_ps.publish("burn_rate", u_data[5])
unit.reactor_data.mek_status.act_burn_rate = u_data[6]
unit.unit_ps.publish("act_burn_rate", u_data[6])
unit.reactor_data.mek_struct.max_burn = u_data[7]
unit.unit_ps.publish("max_burn", u_data[7])
unit.annunciator.AutoControl = u_data[8]
unit.unit_ps.publish("AutoControl", u_data[8])
unit.a_group = u_data[9]
unit.unit_ps.publish("auto_group_id", unit.a_group)
unit.unit_ps.publish("auto_group", types.AUTO_GROUP_NAMES[unit.a_group + 1])
local control_status = 1
if unit.connected then
if unit.reactor_data.rps_tripped then
control_status = 2
end
if unit.reactor_data.mek_status.status then
control_status = util.trinary(unit.annunciator.AutoControl, 4, 3)
end
end
unit.unit_ps.publish("U_ControlStatus", control_status)
end
end
-- update process app with unit data from API_GET_PROC
---@param data table
function iocontrol.record_process_data(data)
-- get unit data
for u_id = 1, #io.units do
local unit = io.units[u_id]
local u_data = data[u_id]
unit.reactor_data.mek_status.status = u_data[1]
unit.reactor_data.mek_struct.max_burn = u_data[2]
unit.annunciator.AutoControl = u_data[6]
unit.a_group = u_data[7]
unit.unit_ps.publish("status", u_data[1])
unit.unit_ps.publish("max_burn", u_data[2])
unit.unit_ps.publish("burn_limit", u_data[3])
unit.unit_ps.publish("U_AutoReady", u_data[4])
unit.unit_ps.publish("U_AutoDegraded", u_data[5])
unit.unit_ps.publish("AutoControl", u_data[6])
unit.unit_ps.publish("auto_group_id", unit.a_group)
unit.unit_ps.publish("auto_group", types.AUTO_GROUP_NAMES[unit.a_group + 1])
end
-- get facility data
local fac = io.facility
local f_data = data[#io.units + 1]
fac.status_lines = f_data[1]
fac.auto_ready = f_data[2][1]
fac.auto_active = f_data[2][2]
fac.auto_ramping = f_data[2][3]
fac.auto_saturated = f_data[2][4]
fac.auto_scram = f_data[3]
fac.ascram_status = f_data[4]
fac.ps.publish("status_line_1", fac.status_lines[1])
fac.ps.publish("status_line_2", fac.status_lines[2])
fac.ps.publish("auto_ready", fac.auto_ready)
fac.ps.publish("auto_active", fac.auto_active)
fac.ps.publish("auto_ramping", fac.auto_ramping)
fac.ps.publish("auto_saturated", fac.auto_saturated)
fac.ps.publish("auto_scram", fac.auto_scram)
fac.ps.publish("as_matrix_dc", fac.ascram_status.matrix_dc)
fac.ps.publish("as_matrix_fill", fac.ascram_status.matrix_fill)
fac.ps.publish("as_crit_alarm", fac.ascram_status.crit_alarm)
fac.ps.publish("as_radiation", fac.ascram_status.radiation)
fac.ps.publish("as_gen_fault", fac.ascram_status.gen_fault)
fac.ps.publish("process_mode", f_data[5][1])
fac.ps.publish("process_burn_target", f_data[5][2])
fac.ps.publish("process_charge_target", f_data[5][3])
fac.ps.publish("process_gen_target", f_data[5][4])
end
-- get the IO controller database
function iocontrol.get_db() return io end

View File

@@ -82,40 +82,42 @@ end
---@enum POCKET_APP_ID
local APP_ID = {
-- core UI
ROOT = 1,
LOADER = 2,
-- main app pages
UNITS = 3,
CONTROL = 4,
GUIDE = 5,
ABOUT = 6,
-- diag app page
ALARMS = 7,
PROCESS = 5,
GUIDE = 6,
ABOUT = 7,
-- diagnostic app pages
ALARMS = 8,
-- other
DUMMY = 8,
NUM_APPS = 8
DUMMY = 9,
NUM_APPS = 9
}
pocket.APP_ID = APP_ID
---@class nav_tree_page
---@field _p nav_tree_page|nil page's parent
---@field _c table page's children
---@field _c nav_tree_page[] page's children
---@field nav_to function function to navigate to this page
---@field switcher function|nil function to switch between children
---@field tasks table tasks to run while viewing this page
---@field tasks function[] tasks to run while viewing this page
-- initialize the page navigation system
---@param smem pkt_shared_memory
function pocket.init_nav(smem)
local self = {
pane = nil, ---@type graphics_element
sidebar = nil, ---@type graphics_element
apps = {},
containers = {},
help_map = {},
help_return = nil,
loader_return = nil,
pane = nil, ---@type AppMultiPane|MultiPane|nil
sidebar = nil, ---@type Sidebar|nil
apps = {}, ---@type pocket_app[]
containers = {}, ---@type Container[]
help_map = {}, ---@type { [string]: function }
help_return = nil, ---@type POCKET_APP_ID|nil
loader_return = nil, ---@type POCKET_APP_ID|nil
cur_app = APP_ID.ROOT
}
@@ -125,17 +127,17 @@ function pocket.init_nav(smem)
local nav = {}
-- set the root pane element to switch between apps with
---@param root_pane graphics_element
---@param root_pane MultiPane
function nav.set_pane(root_pane) self.pane = root_pane end
-- link sidebar element
---@param sidebar graphics_element
---@param sidebar Sidebar
function nav.set_sidebar(sidebar) self.sidebar = sidebar end
-- register an app
---@param app_id POCKET_APP_ID app ID
---@param container graphics_element element that contains this app (usually a Div)
---@param pane? graphics_element multipane if this is a simple paned app, then nav_to must be a number
---@param container Container element that contains this app (usually a Div)
---@param pane? AppMultiPane|MultiPane multipane if this is a simple paned app, then nav_to must be a number
---@param require_sv? boolean true to specifiy if this app should be unloaded when the supervisor connection is lost
---@param require_api? boolean true to specifiy if this app should be unloaded when the api connection is lost
function nav.register_app(app_id, container, pane, require_sv, require_api)
@@ -144,8 +146,8 @@ function pocket.init_nav(smem)
loaded = false,
cur_page = nil, ---@type nav_tree_page
pane = pane,
paned_pages = {},
sidebar_items = {}
paned_pages = {}, ---@type nav_tree_page[]
sidebar_items = {} ---@type sidebar_entry[]
}
app.load = function () app.loaded = true end
@@ -159,24 +161,27 @@ function pocket.init_nav(smem)
function app.requires_conn() return require_sv or require_api or false end
-- delayed set of the pane if it wasn't ready at the start
---@param root_pane graphics_element multipane
---@param root_pane AppMultiPane|MultiPane multipane
function app.set_root_pane(root_pane)
app.pane = root_pane
end
-- configure the sidebar
---@param items table
---@param items sidebar_entry[]
function app.set_sidebar(items)
app.sidebar_items = items
-- only modify the sidebar if this app is still open
if self.cur_app == app_id then
if self.sidebar then self.sidebar.update(items) end
end
end
-- function to run on initial load into memory
---@param on_load function callback
function app.set_load(on_load)
app.load = function ()
app.loaded = true -- must flag first so it can't be repeatedly attempted
on_load()
app.loaded = true
end
end
@@ -184,8 +189,8 @@ function pocket.init_nav(smem)
---@param on_unload function callback
function app.set_unload(on_unload)
app.unload = function ()
on_unload()
app.loaded = false
on_unload()
end
end
@@ -263,7 +268,7 @@ function pocket.init_nav(smem)
-- reset help return on navigating out of an app
if app_id == APP_ID.ROOT then self.help_return = nil end
local app = self.apps[app_id] ---@type pocket_app
local app = self.apps[app_id]
if app then
if app.requires_conn() and not smem.pkt_sys.pocket_comms.is_linked() then
-- bring up the app loader
@@ -285,6 +290,9 @@ function pocket.init_nav(smem)
end
end
-- go home (open the home screen app)
function nav.go_home() nav.open_app(APP_ID.ROOT) end
-- open the app that was blocked on connecting
function nav.on_loader_connected()
if self.loader_return then
@@ -339,7 +347,7 @@ function pocket.init_nav(smem)
return
end
local app = self.apps[self.cur_app] ---@type pocket_app
local app = self.apps[self.cur_app]
log.debug("attempting app nav up for app " .. self.cur_app)
if not app.nav_up() then
@@ -359,6 +367,7 @@ function pocket.init_nav(smem)
end
-- link the help map from the guide app
---@param map { [string]: function }
function nav.link_help(map) self.help_map = map end
return nav
@@ -546,6 +555,16 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav)
if self.api.linked then _send_api(CRDN_TYPE.API_GET_UNIT, { unit }) end
end
-- coordinator get control app data
function public.api__get_control()
if self.api.linked then _send_api(CRDN_TYPE.API_GET_CTRL, {}) end
end
-- coordinator get process app data
function public.api__get_process()
if self.api.linked then _send_api(CRDN_TYPE.API_GET_PROC, {}) end
end
-- send a facility command
---@param cmd FAC_COMMAND command
---@param option any? optional option options for the optional options (like waste mode)
@@ -553,6 +572,12 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav)
_send_api(CRDN_TYPE.FAC_CMD, { cmd, option })
end
-- send the auto process control configuration with a start command
---@param auto_cfg [ PROCESS, number, number, number, number[] ]
function public.send_auto_start(auto_cfg)
_send_api(CRDN_TYPE.FAC_CMD, { FAC_COMMAND.START, table.unpack(auto_cfg) })
end
-- send a unit command
---@param cmd UNIT_COMMAND command
---@param unit integer unit ID
@@ -655,7 +680,9 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav)
if cmd == FAC_COMMAND.SCRAM_ALL then
iocontrol.get_db().facility.scram_ack(ack)
elseif cmd == FAC_COMMAND.STOP then
iocontrol.get_db().facility.stop_ack(ack)
elseif cmd == FAC_COMMAND.START then
iocontrol.get_db().facility.start_ack(ack)
elseif cmd == FAC_COMMAND.ACK_ALL_ALARMS then
iocontrol.get_db().facility.ack_alarms_ack(ack)
elseif cmd == FAC_COMMAND.SET_WASTE_MODE then
@@ -698,6 +725,14 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav)
if _check_length(packet, 12) and type(packet.data[1]) == "number" and iocontrol.get_db().units[packet.data[1]] then
iocontrol.record_unit_data(packet.data)
end
elseif packet.type == CRDN_TYPE.API_GET_CTRL then
if _check_length(packet, #iocontrol.get_db().units) then
iocontrol.record_control_data(packet.data)
end
elseif packet.type == CRDN_TYPE.API_GET_PROC then
if _check_length(packet, #iocontrol.get_db().units + 1) then
iocontrol.record_process_data(packet.data)
end
else _fail_type(packet) end
else
log.debug("discarding coordinator SCADA_CRDN packet before linked")

View File

@@ -6,8 +6,8 @@ local comms = require("scada-common.comms")
local log = require("scada-common.log")
local util = require("scada-common.util")
local FAC_COMMAND = comms.FAC_COMMAND
local UNIT_COMMAND = comms.UNIT_COMMAND
local F_CMD = comms.FAC_COMMAND
local U_CMD = comms.UNIT_COMMAND
---@class pocket_process_controller
local process = {}
@@ -25,23 +25,32 @@ function process.init(iocontrol, pocket_comms)
self.comms = pocket_comms
end
------------------------------
--#region FACILITY COMMANDS --
-- facility SCRAM command
function process.fac_scram()
self.comms.send_fac_command(FAC_COMMAND.SCRAM_ALL)
self.comms.send_fac_command(F_CMD.SCRAM_ALL)
log.debug("PROCESS: FAC SCRAM ALL")
end
-- facility alarm acknowledge command
function process.fac_ack_alarms()
self.comms.send_fac_command(FAC_COMMAND.ACK_ALL_ALARMS)
self.comms.send_fac_command(F_CMD.ACK_ALL_ALARMS)
log.debug("PROCESS: FAC ACK ALL ALARMS")
end
--#endregion
------------------------------
--------------------------
--#region UNIT COMMANDS --
-- start reactor
---@param id integer unit ID
function process.start(id)
self.io.units[id].control_state = true
self.comms.send_unit_command(UNIT_COMMAND.START, id)
self.comms.send_unit_command(U_CMD.START, id)
log.debug(util.c("PROCESS: UNIT[", id, "] START"))
end
@@ -49,14 +58,14 @@ end
---@param id integer unit ID
function process.scram(id)
self.io.units[id].control_state = false
self.comms.send_unit_command(UNIT_COMMAND.SCRAM, id)
self.comms.send_unit_command(U_CMD.SCRAM, id)
log.debug(util.c("PROCESS: UNIT[", id, "] SCRAM"))
end
-- reset reactor protection system
---@param id integer unit ID
function process.reset_rps(id)
self.comms.send_unit_command(UNIT_COMMAND.RESET_RPS, id)
self.comms.send_unit_command(U_CMD.RESET_RPS, id)
log.debug(util.c("PROCESS: UNIT[", id, "] RESET RPS"))
end
@@ -64,14 +73,22 @@ end
---@param id integer unit ID
---@param rate number burn rate
function process.set_rate(id, rate)
self.comms.send_unit_command(UNIT_COMMAND.SET_BURN, id, rate)
self.comms.send_unit_command(U_CMD.SET_BURN, id, rate)
log.debug(util.c("PROCESS: UNIT[", id, "] SET BURN ", rate))
end
-- assign a unit to a group
---@param unit_id integer unit ID
---@param group_id integer|0 group ID or 0 for independent
function process.set_group(unit_id, group_id)
self.comms.send_unit_command(U_CMD.SET_GROUP, unit_id, group_id)
log.debug(util.c("PROCESS: UNIT[", unit_id, "] SET GROUP ", group_id))
end
-- acknowledge all alarms
---@param id integer unit ID
function process.ack_all_alarms(id)
self.comms.send_unit_command(UNIT_COMMAND.ACK_ALL_ALARMS, id)
self.comms.send_unit_command(U_CMD.ACK_ALL_ALARMS, id)
log.debug(util.c("PROCESS: UNIT[", id, "] ACK ALL ALARMS"))
end
@@ -79,7 +96,7 @@ end
---@param id integer unit ID
---@param alarm integer alarm ID
function process.ack_alarm(id, alarm)
self.comms.send_unit_command(UNIT_COMMAND.ACK_ALARM, id, alarm)
self.comms.send_unit_command(U_CMD.ACK_ALARM, id, alarm)
log.debug(util.c("PROCESS: UNIT[", id, "] ACK ALARM ", alarm))
end
@@ -87,8 +104,34 @@ end
---@param id integer unit ID
---@param alarm integer alarm ID
function process.reset_alarm(id, alarm)
self.comms.send_unit_command(UNIT_COMMAND.RESET_ALARM, id, alarm)
self.comms.send_unit_command(U_CMD.RESET_ALARM, id, alarm)
log.debug(util.c("PROCESS: UNIT[", id, "] RESET ALARM ", alarm))
end
-- #endregion
--------------------------
---------------------------------
--#region AUTO PROCESS CONTROL --
-- process start command
---@param mode PROCESS process control mode
---@param burn_target number burn rate target
---@param charge_target number charge level target
---@param gen_target number generation rate target
---@param limits number[] unit burn rate limits
function process.process_start(mode, burn_target, charge_target, gen_target, limits)
self.comms.send_auto_start({ mode, burn_target, charge_target, gen_target, limits })
log.debug("PROCESS: START AUTO CTRL")
end
-- process stop command
function process.process_stop()
self.comms.send_fac_command(F_CMD.STOP)
log.debug("PROCESS: STOP AUTO CTRL")
end
-- #endregion
---------------------------------
return process

View File

@@ -8,7 +8,7 @@ local style = require("pocket.ui.style")
local core = require("graphics.core")
local flasher = require("graphics.flasher")
local DisplayBox = require("graphics.elements.displaybox")
local DisplayBox = require("graphics.elements.DisplayBox")
---@class pocket_renderer
local renderer = {}

View File

@@ -20,7 +20,7 @@ local pocket = require("pocket.pocket")
local renderer = require("pocket.renderer")
local threads = require("pocket.threads")
local POCKET_VERSION = "v0.12.1-alpha"
local POCKET_VERSION = "v0.12.7-alpha"
local println = util.println
local println_ts = util.println_ts

View File

@@ -13,19 +13,19 @@ local style = require("pocket.ui.style")
local core = require("graphics.core")
local Div = require("graphics.elements.div")
local MultiPane = require("graphics.elements.multipane")
local TextBox = require("graphics.elements.textbox")
local Div = require("graphics.elements.Div")
local MultiPane = require("graphics.elements.MultiPane")
local TextBox = require("graphics.elements.TextBox")
local WaitingAnim = require("graphics.elements.animations.waiting")
local WaitingAnim = require("graphics.elements.animations.Waiting")
local HazardButton = require("graphics.elements.controls.hazard_button")
local PushButton = require("graphics.elements.controls.push_button")
local HazardButton = require("graphics.elements.controls.HazardButton")
local PushButton = require("graphics.elements.controls.PushButton")
local NumberField = require("graphics.elements.form.number_field")
local NumberField = require("graphics.elements.form.NumberField")
local DataIndicator = require("graphics.elements.indicators.data")
local IconIndicator = require("graphics.elements.indicators.icon")
local DataIndicator = require("graphics.elements.indicators.DataIndicator")
local IconIndicator = require("graphics.elements.indicators.IconIndicator")
local AUTO_GROUP = types.AUTO_GROUP
@@ -34,16 +34,21 @@ local cpair = core.cpair
local APP_ID = pocket.APP_ID
local label_fg_bg = style.label
local lu_col = style.label_unit_pair
local text_fg = style.text_fg
local mode_states = style.icon_states.mode_states
local hzd_fg_bg = cpair(colors.white, colors.gray)
local dis_colors = cpair(colors.white, colors.lightGray)
local btn_active = cpair(colors.white, colors.black)
local hzd_fg_bg = style.hzd_fg_bg
local hzd_dis_colors = style.hzd_dis_colors
-- new unit control page view
---@param root graphics_element parent
---@param root Container parent
local function new_view(root)
local btn_fg_bg = cpair(colors.green, colors.black)
local db = iocontrol.get_db()
local frame = Div{parent=root,x=1,y=1}
@@ -58,17 +63,14 @@ local function new_view(root)
local load_pane = MultiPane{parent=main,x=1,y=1,panes={load_div,main}}
app.set_sidebar({ { label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = function () db.nav.open_app(APP_ID.ROOT) end } })
app.set_sidebar({ { label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = db.nav.go_home } })
local btn_fg_bg = cpair(colors.green, colors.black)
local btn_active = cpair(colors.white, colors.black)
local page_div = nil ---@type nil|graphics_element
local page_div = nil ---@type Div|nil
-- set sidebar to display unit-specific fields based on a specified unit
local function set_sidebar()
local list = {
{ label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = function () db.nav.open_app(APP_ID.ROOT) end },
{ label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = db.nav.go_home },
{ label = "FAC", color = core.cpair(colors.black, colors.orange), callback = function () app.switcher(db.facility.num_units + 1) end }
}
@@ -83,7 +85,7 @@ local function new_view(root)
local function load()
page_div = Div{parent=main,y=2,width=main.get_width()}
local panes = {}
local panes = {} ---@type Div[]
local active_unit = 1
@@ -105,21 +107,21 @@ local function new_view(root)
app.switcher(active_unit)
end
for i = 1, db.facility.num_units do
local u_pane = panes[i]
local u_div = Div{parent=u_pane,x=2,width=main.get_width()-2}
local unit = db.units[i] ---@type pioctl_unit
local u_ps = unit.unit_ps
-- refresh data callback, every 500ms it will re-send the query
local last_update = 0
-- refresh data callback, every 500ms it will re-send the query
local function update()
if util.time_ms() - last_update >= 500 then
db.api.get_unit(i)
db.api.get_ctrl()
last_update = util.time_ms()
end
end
for i = 1, db.facility.num_units do
local u_pane = panes[i]
local u_div = Div{parent=u_pane,x=2,width=main.get_width()-2}
local unit = db.units[i]
local u_ps = unit.unit_ps
local u_page = app.new_page(nil, i)
u_page.tasks = { update }
@@ -138,12 +140,12 @@ local function new_view(root)
u_div.line_break()
TextBox{parent=u_div,y=8,text="CMD",width=4,fg_bg=cpair(colors.lightGray,colors.black)}
TextBox{parent=u_div,x=14,y=8,text="mB/t",width=4,fg_bg=cpair(colors.lightGray,colors.black)}
local burn_cmd = NumberField{parent=u_div,x=5,y=8,width=8,default=0.01,min=0.01,max_frac_digits=2,max_chars=8,allow_decimal=true,align_right=true,fg_bg=cpair(colors.white,colors.gray),dis_fg_bg=cpair(colors.gray,colors.lightGray)}
TextBox{parent=u_div,y=8,text="CMD",width=4,fg_bg=label_fg_bg}
TextBox{parent=u_div,x=14,y=8,text="mB/t",width=4,fg_bg=label_fg_bg}
local burn_cmd = NumberField{parent=u_div,x=5,y=8,width=8,default=0.01,min=0.01,max_frac_digits=2,max_chars=8,allow_decimal=true,align_right=true,fg_bg=style.field,dis_fg_bg=style.field_disable}
local set_burn = function () unit.set_burn(burn_cmd.get_value()) end
local set_burn_btn = PushButton{parent=u_div,x=19,y=8,text="SET",min_width=5,fg_bg=cpair(colors.green,colors.black),active_fg_bg=cpair(colors.white,colors.black),dis_fg_bg=cpair(colors.gray,colors.black),callback=set_burn}
local set_burn = function () unit.set_burn(burn_cmd.get_numeric()) end
local set_burn_btn = PushButton{parent=u_div,x=19,y=8,text="SET",min_width=5,fg_bg=cpair(colors.green,colors.black),active_fg_bg=cpair(colors.white,colors.black),dis_fg_bg=style.btn_disable,callback=set_burn}
-- enable/disable controls based on group assignment (start button is separate)
burn_cmd.register(u_ps, "auto_group_id", function (gid)
@@ -156,10 +158,10 @@ local function new_view(root)
burn_cmd.register(u_ps, "burn_rate", burn_cmd.set_value)
burn_cmd.register(u_ps, "max_burn", burn_cmd.set_max)
local start = HazardButton{parent=u_div,x=2,y=11,text="START",accent=colors.lightBlue,dis_colors=dis_colors,callback=unit.start,timeout=3,fg_bg=hzd_fg_bg}
local ack_a = HazardButton{parent=u_div,x=12,y=11,text="ACK \x13",accent=colors.orange,dis_colors=dis_colors,callback=unit.ack_alarms,timeout=3,fg_bg=hzd_fg_bg}
local scram = HazardButton{parent=u_div,x=2,y=15,text="SCRAM",accent=colors.yellow,dis_colors=dis_colors,callback=unit.scram,timeout=3,fg_bg=hzd_fg_bg}
local reset = HazardButton{parent=u_div,x=12,y=15,text="RESET",accent=colors.red,dis_colors=dis_colors,callback=unit.reset_rps,timeout=3,fg_bg=hzd_fg_bg}
local start = HazardButton{parent=u_div,x=2,y=11,text="START",accent=colors.lightBlue,callback=unit.start,timeout=3,fg_bg=hzd_fg_bg,dis_colors=hzd_dis_colors}
local ack_a = HazardButton{parent=u_div,x=12,y=11,text="ACK \x13",accent=colors.orange,callback=unit.ack_alarms,timeout=3,fg_bg=hzd_fg_bg,dis_colors=hzd_dis_colors}
local scram = HazardButton{parent=u_div,x=2,y=15,text="SCRAM",accent=colors.yellow,callback=unit.scram,timeout=3,fg_bg=hzd_fg_bg,dis_colors=hzd_dis_colors}
local reset = HazardButton{parent=u_div,x=12,y=15,text="RESET",accent=colors.red,callback=unit.reset_rps,timeout=3,fg_bg=hzd_fg_bg,dis_colors=hzd_dis_colors}
unit.start_ack = start.on_response
unit.ack_alarms_ack = ack_a.on_response
@@ -167,13 +169,11 @@ local function new_view(root)
unit.reset_rps_ack = reset.on_response
local function start_button_en_check()
if (unit.reactor_data ~= nil) and (unit.reactor_data.mek_status ~= nil) then
local can_start = (not unit.reactor_data.mek_status.status) and
(not unit.reactor_data.rps_tripped) and
(unit.a_group == AUTO_GROUP.MANUAL)
if can_start then start.enable() else start.disable() end
end
end
start.register(u_ps, "status", start_button_en_check)
start.register(u_ps, "rps_tripped", start_button_en_check)
@@ -194,8 +194,8 @@ local function new_view(root)
TextBox{parent=f_div,y=1,text="Facility Commands",alignment=ALIGN.CENTER}
local scram = HazardButton{parent=f_div,x=5,y=6,text="FAC SCRAM",accent=colors.yellow,dis_colors=dis_colors,callback=process.fac_scram,timeout=3,fg_bg=hzd_fg_bg}
local ack_a = HazardButton{parent=f_div,x=7,y=11,text="ACK \x13",accent=colors.orange,dis_colors=dis_colors,callback=process.fac_ack_alarms,timeout=3,fg_bg=hzd_fg_bg}
local scram = HazardButton{parent=f_div,x=5,y=6,text="FAC SCRAM",accent=colors.yellow,dis_colors=hzd_dis_colors,callback=process.fac_scram,timeout=3,fg_bg=hzd_fg_bg}
local ack_a = HazardButton{parent=f_div,x=7,y=11,text="ACK \x13",accent=colors.orange,dis_colors=hzd_dis_colors,callback=process.fac_ack_alarms,timeout=3,fg_bg=hzd_fg_bg}
db.facility.scram_ack = scram.on_response
db.facility.ack_alarms_ack = ack_a.on_response
@@ -217,7 +217,7 @@ local function new_view(root)
page_div = nil
end
app.set_sidebar({ { label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = function () db.nav.open_app(APP_ID.ROOT) end } })
app.set_sidebar({ { label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = db.nav.go_home } })
app.delete_pages()
-- show loading screen

View File

@@ -7,14 +7,14 @@ local pocket = require("pocket.pocket")
local core = require("graphics.core")
local Div = require("graphics.elements.div")
local TextBox = require("graphics.elements.textbox")
local Div = require("graphics.elements.Div")
local TextBox = require("graphics.elements.TextBox")
local IndicatorLight = require("graphics.elements.indicators.light")
local IndicatorLight = require("graphics.elements.indicators.IndicatorLight")
local Checkbox = require("graphics.elements.controls.checkbox")
local PushButton = require("graphics.elements.controls.push_button")
local SwitchButton = require("graphics.elements.controls.switch_button")
local Checkbox = require("graphics.elements.controls.Checkbox")
local PushButton = require("graphics.elements.controls.PushButton")
local SwitchButton = require("graphics.elements.controls.SwitchButton")
local ALIGN = core.ALIGN
local cpair = core.cpair
@@ -22,7 +22,7 @@ local cpair = core.cpair
local APP_ID = pocket.APP_ID
-- create diagnostic app pages
---@param root graphics_element parent
---@param root Container parent
local function create_pages(root)
local db = iocontrol.get_db()

View File

@@ -7,13 +7,13 @@ local pocket = require("pocket.pocket")
local core = require("graphics.core")
local Div = require("graphics.elements.div")
local TextBox = require("graphics.elements.textbox")
local Div = require("graphics.elements.Div")
local TextBox = require("graphics.elements.TextBox")
local APP_ID = pocket.APP_ID
-- create placeholder app page
---@param root graphics_element parent
---@param root Container parent
local function create_pages(root)
local db = iocontrol.get_db()

View File

@@ -15,16 +15,16 @@ local guide_section = require("pocket.ui.pages.guide_section")
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 Div = require("graphics.elements.Div")
local ListBox = require("graphics.elements.ListBox")
local MultiPane = require("graphics.elements.MultiPane")
local TextBox = require("graphics.elements.TextBox")
local WaitingAnim = require("graphics.elements.animations.waiting")
local WaitingAnim = require("graphics.elements.animations.Waiting")
local PushButton = require("graphics.elements.controls.push_button")
local PushButton = require("graphics.elements.controls.PushButton")
local TextField = require("graphics.elements.form.text_field")
local TextField = require("graphics.elements.form.TextField")
local ALIGN = core.ALIGN
local cpair = core.cpair
@@ -36,7 +36,7 @@ local APP_ID = pocket.APP_ID
-- local text_fg = style.text_fg
-- new system guide view
---@param root graphics_element parent
---@param root Container parent
local function new_view(root)
local db = iocontrol.get_db()
@@ -56,14 +56,14 @@ local function new_view(root)
local btn_active = cpair(colors.white, colors.black)
local btn_disable = cpair(colors.gray, colors.black)
app.set_sidebar({{ label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = function () db.nav.open_app(APP_ID.ROOT) end }})
app.set_sidebar({{ label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = db.nav.go_home }})
local page_div = nil ---@type nil|graphics_element
local page_div = nil ---@type Div|nil
-- load the app (create the elements)
local function load()
local list = {
{ label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = function () db.nav.open_app(APP_ID.ROOT) end },
{ label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = db.nav.go_home },
{ label = " \x14 ", color = core.cpair(colors.black, colors.cyan), callback = function () app.switcher(1) end },
{ label = "__?", color = core.cpair(colors.black, colors.lightGray), callback = function () app.switcher(2) end }
}
@@ -88,12 +88,11 @@ local function new_view(root)
local fps = Div{parent=page_div,x=2,width=p_width}
local gls = Div{parent=page_div,x=2,width=p_width}
local lnk = Div{parent=page_div,x=2,width=p_width}
local panes = { home, search, use, uis, fps, gls, lnk }
local panes = { home, search, use, uis, fps, gls, lnk } ---@type Div[]
local doc_map = {}
local search_db = {}
local doc_map = {} ---@type { [string]: function }
local search_db = {} ---@type [ string, string, string, function ][]
---@class _guide_section_constructor_data
local sect_construct_data = { app, page_div, panes, doc_map, search_db, btn_fg_bg, btn_active }
TextBox{parent=home,y=1,text="cc-mek-scada Guide",alignment=ALIGN.CENTER}
@@ -117,7 +116,7 @@ local function new_view(root)
function func_ref.run_search()
local query = string.lower(query_field.get_value())
local s_results = { {}, {}, {}, {} }
local s_results = { {}, {}, {}, {} } ---@type [ string, string, string, function ][][]
search_results.remove_all()
@@ -155,7 +154,7 @@ local function new_view(root)
for idx = 1, #s_results[tier] do
local entry = s_results[tier][idx]
TextBox{parent=search_results,text=entry[3].." >",fg_bg=cpair(colors.gray,colors.black)}
PushButton{parent=search_results,text=entry[2],fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=entry[4]}
PushButton{parent=search_results,text=entry[2],alignment=ALIGN.LEFT,fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=entry[4]}
empty = false
end
@@ -264,7 +263,7 @@ local function new_view(root)
page_div = nil
end
app.set_sidebar({ { label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = function () db.nav.open_app(APP_ID.ROOT) end } })
app.set_sidebar({ { label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = db.nav.go_home } })
app.delete_pages()
-- show loading screen

View File

@@ -9,16 +9,16 @@ local conn_waiting = require("pocket.ui.components.conn_waiting")
local core = require("graphics.core")
local Div = require("graphics.elements.div")
local MultiPane = require("graphics.elements.multipane")
local TextBox = require("graphics.elements.textbox")
local Div = require("graphics.elements.Div")
local MultiPane = require("graphics.elements.MultiPane")
local TextBox = require("graphics.elements.TextBox")
local APP_ID = pocket.APP_ID
local LINK_STATE = iocontrol.LINK_STATE
-- create the connecting to SV & API page
---@param root graphics_element parent
---@param root Container parent
local function create_pages(root)
local db = iocontrol.get_db()

337
pocket/ui/apps/process.lua Normal file
View File

@@ -0,0 +1,337 @@
--
-- Process Control Page
--
local types = require("scada-common.types")
local util = require("scada-common.util")
local iocontrol = require("pocket.iocontrol")
local pocket = require("pocket.pocket")
local process = require("pocket.process")
local style = require("pocket.ui.style")
local core = require("graphics.core")
local Div = require("graphics.elements.Div")
local MultiPane = require("graphics.elements.MultiPane")
local Rectangle = require("graphics.elements.Rectangle")
local TextBox = require("graphics.elements.TextBox")
local WaitingAnim = require("graphics.elements.animations.Waiting")
local HazardButton = require("graphics.elements.controls.HazardButton")
local RadioButton = require("graphics.elements.controls.RadioButton")
local NumberField = require("graphics.elements.form.NumberField")
local IconIndicator = require("graphics.elements.indicators.IconIndicator")
local ALIGN = core.ALIGN
local cpair = core.cpair
local border = core.border
local APP_ID = pocket.APP_ID
local label_fg_bg = style.label
local text_fg = style.text_fg
local field_fg_bg = style.field
local field_dis_fg_bg = style.field_disable
local red_ind_s = style.icon_states.red_ind_s
local yel_ind_s = style.icon_states.yel_ind_s
local grn_ind_s = style.icon_states.grn_ind_s
local wht_ind_s = style.icon_states.wht_ind_s
local hzd_fg_bg = style.hzd_fg_bg
local dis_colors = cpair(colors.white, colors.lightGray)
-- new process control page view
---@param root Container parent
local function new_view(root)
local db = iocontrol.get_db()
local frame = Div{parent=root,x=1,y=1}
local app = db.nav.register_app(APP_ID.PROCESS, frame, nil, false, true)
local load_div = Div{parent=frame,x=1,y=1}
local main = Div{parent=frame,x=1,y=1}
TextBox{parent=load_div,y=12,text="Loading...",alignment=ALIGN.CENTER}
WaitingAnim{parent=load_div,x=math.floor(main.get_width()/2)-1,y=8,fg_bg=cpair(colors.purple,colors._INHERIT)}
local load_pane = MultiPane{parent=main,x=1,y=1,panes={load_div,main}}
app.set_sidebar({ { label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = db.nav.go_home } })
local page_div = nil ---@type Div|nil
-- load the app (create the elements)
local function load()
local f_ps = db.facility.ps
page_div = Div{parent=main,y=2,width=main.get_width()}
local panes = {} ---@type Div[]
-- create all page divs
for _ = 1, db.facility.num_units + 3 do
local div = Div{parent=page_div}
table.insert(panes, div)
end
local last_update = 0
-- refresh data callback, every 500ms it will re-send the query
local function update()
if util.time_ms() - last_update >= 500 then
db.api.get_proc()
last_update = util.time_ms()
end
end
--#region unit settings/status
local rate_limits = {} ---@type NumberField[]
for i = 1, db.facility.num_units do
local u_pane = panes[i]
local u_div = Div{parent=u_pane,x=2,width=main.get_width()-2}
local unit = db.units[i]
local u_ps = unit.unit_ps
local u_page = app.new_page(nil, i)
u_page.tasks = { update }
TextBox{parent=u_div,y=1,text="Reactor Unit #"..i,alignment=ALIGN.CENTER}
TextBox{parent=u_div,y=3,text="Auto Rate Limit",fg_bg=label_fg_bg}
rate_limits[i] = NumberField{parent=u_div,x=1,y=4,width=16,default=0.01,min=0.01,max_frac_digits=2,max_chars=8,allow_decimal=true,align_right=true,fg_bg=field_fg_bg,dis_fg_bg=field_dis_fg_bg}
TextBox{parent=u_div,x=18,y=4,text="mB/t",width=4,fg_bg=label_fg_bg}
rate_limits[i].register(unit.unit_ps, "max_burn", rate_limits[i].set_max)
rate_limits[i].register(unit.unit_ps, "burn_limit", rate_limits[i].set_value)
local ready = IconIndicator{parent=u_div,y=6,label="Auto Ready",states=grn_ind_s}
local a_stb = IconIndicator{parent=u_div,label="Auto Standby",states=wht_ind_s}
local degraded = IconIndicator{parent=u_div,label="Unit Degraded",states=red_ind_s}
ready.register(u_ps, "U_AutoReady", ready.update)
degraded.register(u_ps, "U_AutoDegraded", degraded.update)
-- update standby indicator
a_stb.register(u_ps, "status", function (active)
a_stb.update(unit.annunciator.AutoControl and (not active))
end)
a_stb.register(u_ps, "AutoControl", function (auto_active)
if auto_active then
a_stb.update(unit.reactor_data.mek_status.status == false)
else a_stb.update(false) end
end)
local function _set_group(value) process.set_group(i, value - 1) end
local group = RadioButton{parent=u_div,y=10,options=types.AUTO_GROUP_NAMES,callback=_set_group,radio_colors=cpair(colors.lightGray,colors.gray),select_color=colors.purple,dis_fg_bg=style.btn_disable}
-- can't change group if auto is engaged regardless of if this unit is part of auto control
group.register(f_ps, "auto_active", function (auto_active)
if auto_active then group.disable() else group.enable() end
end)
group.register(u_ps, "auto_group_id", function (gid) group.set_value(gid + 1) end)
TextBox{parent=u_div,y=16,text="Assigned Group",fg_bg=style.label}
local auto_grp = TextBox{parent=u_div,text="Manual",width=11,fg_bg=text_fg}
auto_grp.register(u_ps, "auto_group", auto_grp.set_value)
util.nop()
end
--#endregion
--#region process control options page
local o_pane = panes[db.facility.num_units + 2]
local o_div = Div{parent=o_pane,x=2,width=main.get_width()-2}
local opt_page = app.new_page(nil, db.facility.num_units + 2)
opt_page.tasks = { update }
TextBox{parent=o_div,y=1,text="Process Options",alignment=ALIGN.CENTER}
local ctl_opts = { "Monitored Max Burn", "Combined Burn Rate", "Charge Level", "Generation Rate" }
local mode = RadioButton{parent=o_div,x=1,y=3,options=ctl_opts,callback=function()end,radio_colors=cpair(colors.lightGray,colors.gray),select_color=colors.purple,dis_fg_bg=style.btn_disable}
mode.register(f_ps, "process_mode", mode.set_value)
TextBox{parent=o_div,y=9,text="Burn Rate Target",fg_bg=label_fg_bg}
local b_target = NumberField{parent=o_div,x=1,y=10,width=15,default=0.01,min=0.01,max_frac_digits=2,max_chars=8,allow_decimal=true,align_right=true,fg_bg=field_fg_bg,dis_fg_bg=field_dis_fg_bg}
TextBox{parent=o_div,x=17,y=10,text="mB/t",fg_bg=label_fg_bg}
TextBox{parent=o_div,y=12,text="Charge Level Target",fg_bg=label_fg_bg}
local c_target = NumberField{parent=o_div,x=1,y=13,width=15,default=0,min=0,max_chars=16,align_right=true,fg_bg=field_fg_bg,dis_fg_bg=field_dis_fg_bg}
TextBox{parent=o_div,x=17,y=13,text="M"..db.energy_label,fg_bg=label_fg_bg}
TextBox{parent=o_div,y=15,text="Generation Target",fg_bg=label_fg_bg}
local g_target = NumberField{parent=o_div,x=1,y=16,width=15,default=0,min=0,max_chars=16,align_right=true,fg_bg=field_fg_bg,dis_fg_bg=field_dis_fg_bg}
TextBox{parent=o_div,x=17,y=16,text="k"..db.energy_label.."/t",fg_bg=label_fg_bg}
b_target.register(f_ps, "process_burn_target", b_target.set_value)
c_target.register(f_ps, "process_charge_target", c_target.set_value)
g_target.register(f_ps, "process_gen_target", g_target.set_value)
--#endregion
--#region process control page
local c_pane = panes[db.facility.num_units + 1]
local c_div = Div{parent=c_pane,x=2,width=main.get_width()-2}
local proc_ctrl = app.new_page(nil, db.facility.num_units + 1)
proc_ctrl.tasks = { update }
TextBox{parent=c_div,y=1,text="Process Control",alignment=ALIGN.CENTER}
local u_stat = Rectangle{parent=c_div,border=border(1,colors.gray,true),thin=true,width=21,height=5,x=1,y=3,fg_bg=cpair(colors.black,colors.lightGray)}
local stat_line_1 = TextBox{parent=u_stat,x=1,y=1,text="UNKNOWN",alignment=ALIGN.CENTER}
local stat_line_2 = TextBox{parent=u_stat,x=1,y=2,text="awaiting data...",height=2,alignment=ALIGN.CENTER,trim_whitespace=true,fg_bg=cpair(colors.gray,colors.lightGray)}
stat_line_1.register(f_ps, "status_line_1", stat_line_1.set_value)
stat_line_2.register(f_ps, "status_line_2", stat_line_2.set_value)
local function _start_auto()
local limits = {}
for i = 1, #rate_limits do limits[i] = rate_limits[i].get_numeric() end
process.process_start(mode.get_value(), b_target.get_numeric(), db.energy_convert_to_fe(c_target.get_numeric()),
db.energy_convert_to_fe(g_target.get_numeric()), limits)
end
local start = HazardButton{parent=c_div,x=2,y=9,text="START",accent=colors.lightBlue,callback=_start_auto,timeout=3,fg_bg=hzd_fg_bg,dis_colors=dis_colors}
local stop = HazardButton{parent=c_div,x=13,y=9,text="STOP",accent=colors.red,callback=process.process_stop,timeout=3,fg_bg=hzd_fg_bg,dis_colors=dis_colors}
db.facility.start_ack = start.on_response
db.facility.stop_ack = stop.on_response
start.register(f_ps, "auto_ready", function (ready)
if ready and (not db.facility.auto_active) then start.enable() else start.disable() end
end)
local auto_ready = IconIndicator{parent=c_div,y=14,label="Units Ready",states=grn_ind_s}
local auto_act = IconIndicator{parent=c_div,label="Process Active",states=grn_ind_s}
local auto_ramp = IconIndicator{parent=c_div,label="Process Ramping",states=wht_ind_s}
local auto_sat = IconIndicator{parent=c_div,label="Min/Max Burn Rate",states=yel_ind_s}
auto_ready.register(f_ps, "auto_ready", auto_ready.update)
auto_act.register(f_ps, "auto_active", auto_act.update)
auto_ramp.register(f_ps, "auto_ramping", auto_ramp.update)
auto_sat.register(f_ps, "auto_saturated", auto_sat.update)
-- REGISTER_NOTE: for optimization/brevity, due to not deleting anything but the whole element tree
-- when it comes to unloading the process app, child elements will not directly be registered here
-- (preventing garbage collection until the parent 'page_div' is deleted)
page_div.register(f_ps, "auto_active", function (active)
if active then
b_target.disable()
c_target.disable()
g_target.disable()
mode.disable()
start.disable()
for i = 1, #rate_limits do rate_limits[i].disable() end
else
b_target.enable()
c_target.enable()
g_target.enable()
mode.enable()
if db.facility.auto_ready then start.enable() end
for i = 1, #rate_limits do rate_limits[i].enable() end
end
end)
--#endregion
--#region auto-SCRAM annunciator page
local a_pane = panes[db.facility.num_units + 3]
local a_div = Div{parent=a_pane,x=2,width=main.get_width()-2}
local annunc_page = app.new_page(nil, db.facility.num_units + 3)
annunc_page.tasks = { update }
TextBox{parent=a_div,y=1,text="Automatic SCRAM",alignment=ALIGN.CENTER}
local auto_scram = IconIndicator{parent=a_div,y=3,label="Automatic SCRAM",states=red_ind_s}
TextBox{parent=a_div,y=5,text="Induction Matrix",fg_bg=label_fg_bg}
local matrix_dc = IconIndicator{parent=a_div,label="Disconnected",states=yel_ind_s}
local matrix_fill = IconIndicator{parent=a_div,label="Charge High",states=red_ind_s}
TextBox{parent=a_div,y=9,text="Assigned Units",fg_bg=label_fg_bg}
local unit_crit = IconIndicator{parent=a_div,label="Critical Alarm",states=red_ind_s}
TextBox{parent=a_div,y=12,text="Facility",fg_bg=label_fg_bg}
local fac_rad_h = IconIndicator{parent=a_div,label="Radiation High",states=red_ind_s}
TextBox{parent=a_div,y=15,text="Generation Rate Mode",fg_bg=label_fg_bg}
local gen_fault = IconIndicator{parent=a_div,label="Control Fault",states=yel_ind_s}
auto_scram.register(f_ps, "auto_scram", auto_scram.update)
matrix_dc.register(f_ps, "as_matrix_dc", matrix_dc.update)
matrix_fill.register(f_ps, "as_matrix_fill", matrix_fill.update)
unit_crit.register(f_ps, "as_crit_alarm", unit_crit.update)
fac_rad_h.register(f_ps, "as_radiation", fac_rad_h.update)
gen_fault.register(f_ps, "as_gen_fault", gen_fault.update)
--#endregion
-- setup multipane
local u_pane = MultiPane{parent=page_div,x=1,y=1,panes=panes}
app.set_root_pane(u_pane)
-- setup sidebar
local list = {
{ label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = db.nav.go_home },
{ label = " \x17 ", color = core.cpair(colors.black, colors.purple), callback = proc_ctrl.nav_to },
{ label = " \x13 ", color = core.cpair(colors.black, colors.red), callback = annunc_page.nav_to },
{ label = "OPT", color = core.cpair(colors.black, colors.yellow), callback = opt_page.nav_to }
}
for i = 1, db.facility.num_units do
table.insert(list, { label = "U-" .. i, color = core.cpair(colors.black, colors.lightGray), callback = function () app.switcher(i) end })
end
app.set_sidebar(list)
-- done, show the app
proc_ctrl.nav_to()
load_pane.set_value(2)
end
-- delete the elements and switch back to the loading screen
local function unload()
if page_div then
page_div.delete()
page_div = nil
end
app.set_sidebar({ { label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = db.nav.go_home } })
app.delete_pages()
-- show loading screen
load_pane.set_value(1)
end
app.set_load(load)
app.set_unload(unload)
return main
end
return new_view

View File

@@ -12,12 +12,12 @@ local pocket = require("pocket.pocket")
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 Div = require("graphics.elements.Div")
local ListBox = require("graphics.elements.ListBox")
local MultiPane = require("graphics.elements.MultiPane")
local TextBox = require("graphics.elements.TextBox")
local PushButton = require("graphics.elements.controls.push_button")
local PushButton = require("graphics.elements.controls.PushButton")
local ALIGN = core.ALIGN
local cpair = core.cpair
@@ -25,7 +25,7 @@ local cpair = core.cpair
local APP_ID = pocket.APP_ID
-- create system app pages
---@param root graphics_element parent
---@param root Container parent
local function create_pages(root)
local db = iocontrol.get_db()

View File

@@ -15,17 +15,17 @@ local turbine = require("pocket.ui.pages.unit_turbine")
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 Div = require("graphics.elements.Div")
local ListBox = require("graphics.elements.ListBox")
local MultiPane = require("graphics.elements.MultiPane")
local TextBox = require("graphics.elements.TextBox")
local WaitingAnim = require("graphics.elements.animations.waiting")
local WaitingAnim = require("graphics.elements.animations.Waiting")
local PushButton = require("graphics.elements.controls.push_button")
local PushButton = require("graphics.elements.controls.PushButton")
local DataIndicator = require("graphics.elements.indicators.data")
local IconIndicator = require("graphics.elements.indicators.icon")
local DataIndicator = require("graphics.elements.indicators.DataIndicator")
local IconIndicator = require("graphics.elements.indicators.IconIndicator")
local ALIGN = core.ALIGN
local cpair = core.cpair
@@ -47,7 +47,7 @@ local emc_ind_s = {
}
-- new unit page view
---@param root graphics_element parent
---@param root Container parent
local function new_view(root)
local db = iocontrol.get_db()
@@ -63,20 +63,20 @@ local function new_view(root)
local load_pane = MultiPane{parent=main,x=1,y=1,panes={load_div,main}}
app.set_sidebar({ { label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = function () db.nav.open_app(APP_ID.ROOT) end } })
app.set_sidebar({ { label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = db.nav.go_home } })
local btn_fg_bg = cpair(colors.yellow, colors.black)
local btn_active = cpair(colors.white, colors.black)
local nav_links = {}
local page_div = nil ---@type nil|graphics_element
local page_div = nil ---@type Div|nil
-- set sidebar to display unit-specific fields based on a specified unit
local function set_sidebar(id)
local unit = db.units[id] ---@type pioctl_unit
local unit = db.units[id]
local list = {
{ label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = function () db.nav.open_app(APP_ID.ROOT) end },
{ label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = db.nav.go_home },
{ label = "U-" .. id, color = core.cpair(colors.black, colors.yellow), callback = function () app.switcher(id) end },
{ label = " \x13 ", color = core.cpair(colors.black, colors.red), callback = nav_links[id].alarm },
{ label = "RPS", tall = true, color = core.cpair(colors.black, colors.cyan), callback = nav_links[id].rps },
@@ -99,7 +99,7 @@ local function new_view(root)
local function load()
page_div = Div{parent=main,y=2,width=main.get_width()}
local panes = {}
local panes = {} ---@type Div[]
local active_unit = 1
@@ -127,7 +127,7 @@ local function new_view(root)
for i = 1, db.facility.num_units do
local u_pane = panes[i]
local u_div = Div{parent=u_pane,x=2,width=main.get_width()-2}
local unit = db.units[i] ---@type pioctl_unit
local unit = db.units[i]
local u_ps = unit.unit_ps
-- refresh data callback, every 500ms it will re-send the query
@@ -383,7 +383,7 @@ local function new_view(root)
page_div = nil
end
app.set_sidebar({ { label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = function () db.nav.open_app(APP_ID.ROOT) end } })
app.set_sidebar({ { label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = db.nav.go_home } })
app.delete_pages()
-- show loading screen

View File

@@ -8,17 +8,17 @@ local style = require("pocket.ui.style")
local core = require("graphics.core")
local Div = require("graphics.elements.div")
local TextBox = require("graphics.elements.textbox")
local Div = require("graphics.elements.Div")
local TextBox = require("graphics.elements.TextBox")
local WaitingAnim = require("graphics.elements.animations.waiting")
local WaitingAnim = require("graphics.elements.animations.Waiting")
local ALIGN = core.ALIGN
local cpair = core.cpair
-- create a waiting view
---@param parent graphics_element parent
---@param parent Container parent
---@param y integer y offset
local function init(parent, y, is_api)
-- root div
@@ -29,15 +29,15 @@ local function init(parent, y, is_api)
local waiting_x = math.floor(parent.get_width() / 2) - 1
local msg = TextBox{parent=box,x=3,y=11,width=box.get_width()-4,height=2,text="",alignment=ALIGN.CENTER,fg_bg=cpair(colors.red,style.root.bkg)}
local msg = TextBox{parent=box,x=3,y=11,width=box.get_width()-4,height=2,text="",alignment=ALIGN.CENTER,fg_bg=cpair(colors.red,style.root.bkg),trim_whitespace=true}
if is_api then
WaitingAnim{parent=box,x=waiting_x,y=1,fg_bg=cpair(colors.blue,style.root.bkg)}
TextBox{parent=box,y=5,text="Connecting to API",alignment=ALIGN.CENTER,fg_bg=cpair(colors.white,style.root.bkg)}
TextBox{parent=box,y=5,text="Connecting to API",alignment=ALIGN.CENTER,fg_bg=cpair(colors.white,style.root.bkg),trim_whitespace=true}
msg.register(iocontrol.get_db().ps, "api_link_msg", msg.set_value)
else
WaitingAnim{parent=box,x=waiting_x,y=1,fg_bg=cpair(colors.green,style.root.bkg)}
TextBox{parent=box,y=5,text="Connecting to Supervisor",alignment=ALIGN.CENTER,fg_bg=cpair(colors.white,style.root.bkg)}
TextBox{parent=box,y=5,text="Connecting to Supervisor",alignment=ALIGN.CENTER,fg_bg=cpair(colors.white,style.root.bkg),trim_whitespace=true}
msg.register(iocontrol.get_db().ps, "svr_link_msg", msg.set_value)
end

View File

@@ -12,6 +12,7 @@ local diag_apps = require("pocket.ui.apps.diag_apps")
local dummy_app = require("pocket.ui.apps.dummy_app")
local guide_app = require("pocket.ui.apps.guide")
local loader_app = require("pocket.ui.apps.loader")
local process_app = require("pocket.ui.apps.process")
local sys_apps = require("pocket.ui.apps.sys_apps")
local unit_app = require("pocket.ui.apps.unit")
@@ -21,16 +22,16 @@ local style = require("pocket.ui.style")
local core = require("graphics.core")
local Div = require("graphics.elements.div")
local MultiPane = require("graphics.elements.multipane")
local TextBox = require("graphics.elements.textbox")
local Div = require("graphics.elements.Div")
local MultiPane = require("graphics.elements.MultiPane")
local TextBox = require("graphics.elements.TextBox")
local WaitingAnim = require("graphics.elements.animations.waiting")
local WaitingAnim = require("graphics.elements.animations.Waiting")
local PushButton = require("graphics.elements.controls.push_button")
local Sidebar = require("graphics.elements.controls.sidebar")
local PushButton = require("graphics.elements.controls.PushButton")
local Sidebar = require("graphics.elements.controls.Sidebar")
local SignalBar = require("graphics.elements.indicators.signal")
local SignalBar = require("graphics.elements.indicators.SignalBar")
local ALIGN = core.ALIGN
local cpair = core.cpair
@@ -38,7 +39,7 @@ local cpair = core.cpair
local APP_ID = pocket.APP_ID
-- create new main view
---@param main graphics_element main displaybox
---@param main DisplayBox main displaybox
local function init(main)
local db = iocontrol.get_db()
@@ -64,6 +65,7 @@ local function init(main)
home_page(page_div)
unit_app(page_div)
control_app(page_div)
process_app(page_div)
guide_app(page_div)
loader_app(page_div)
sys_apps(page_div)
@@ -78,7 +80,7 @@ local function init(main)
PushButton{parent=main_pane,x=1,y=19,text="\x1b",min_width=3,fg_bg=cpair(colors.white,colors.gray),active_fg_bg=cpair(colors.gray,colors.black),callback=db.nav.nav_up}
db.nav.open_app(APP_ID.ROOT)
db.nav.go_home()
-- done with initial render, lets go!
root_pane.set_value(2)

View File

@@ -5,14 +5,14 @@ local docs = require("pocket.ui.docs")
local core = require("graphics.core")
local Div = require("graphics.elements.div")
local ListBox = require("graphics.elements.listbox")
local TextBox = require("graphics.elements.textbox")
local Div = require("graphics.elements.Div")
local ListBox = require("graphics.elements.ListBox")
local TextBox = require("graphics.elements.TextBox")
local PushButton = require("graphics.elements.controls.push_button")
local PushButton = require("graphics.elements.controls.PushButton")
local IndicatorLight = require("graphics.elements.indicators.light")
local LED = require("graphics.elements.indicators.led")
local IndicatorLight = require("graphics.elements.indicators.IndicatorLight")
local LED = require("graphics.elements.indicators.LED")
local ALIGN = core.ALIGN
local cpair = core.cpair
@@ -21,14 +21,14 @@ local DOC_TYPE = docs.DOC_ITEM_TYPE
local LIST_TYPE = docs.DOC_LIST_TYPE
-- new guide documentation section
---@param data _guide_section_constructor_data
---@param data { [1]: pocket_app, [2]: Div, [3]: Div[], [4]: { [string]: function }, [5]: [ string, string, string, function ][], [6]: cpair, [7]: cpair }
---@param base_page nav_tree_page
---@param title string
---@param items table
---@param scroll_height integer
---@return nav_tree_page
return function (data, base_page, title, items, scroll_height)
local app, page_div, panes, doc_map, search_db, btn_fg_bg, btn_active = table.unpack(data)
local app, page_div, panes, doc_map, search_db, btn_fg_bg, btn_active = data[1], data[2], data[3], data[4], data[5], data[6], data[7]
local section_page = app.new_page(base_page, #panes + 1)
local section_div = Div{parent=page_div,x=2}

View File

@@ -7,11 +7,11 @@ local pocket = require("pocket.pocket")
local core = require("graphics.core")
local AppMultiPane = require("graphics.elements.appmultipane")
local Div = require("graphics.elements.div")
local TextBox = require("graphics.elements.textbox")
local AppMultiPane = require("graphics.elements.AppMultiPane")
local Div = require("graphics.elements.Div")
local TextBox = require("graphics.elements.TextBox")
local App = require("graphics.elements.controls.app")
local App = require("graphics.elements.controls.App")
local ALIGN = core.ALIGN
local cpair = core.cpair
@@ -19,7 +19,7 @@ local cpair = core.cpair
local APP_ID = pocket.APP_ID
-- new home page view
---@param root graphics_element parent
---@param root Container parent
local function new_view(root)
local db = iocontrol.get_db()
@@ -48,7 +48,7 @@ local function new_view(root)
App{parent=apps_1,x=2,y=2,text="U",title="Units",callback=function()open(APP_ID.UNITS)end,app_fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=active_fg_bg}
App{parent=apps_1,x=9,y=2,text="F",title="Facil",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.orange),active_fg_bg=active_fg_bg}
App{parent=apps_1,x=16,y=2,text="\x15",title="Control",callback=function()open(APP_ID.CONTROL)end,app_fg_bg=cpair(colors.black,colors.green),active_fg_bg=active_fg_bg}
App{parent=apps_1,x=2,y=7,text="\x17",title="Process",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.purple),active_fg_bg=active_fg_bg}
App{parent=apps_1,x=2,y=7,text="\x17",title="Process",callback=function()open(APP_ID.PROCESS)end,app_fg_bg=cpair(colors.black,colors.purple),active_fg_bg=active_fg_bg}
App{parent=apps_1,x=9,y=7,text="\x7f",title="Waste",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.brown),active_fg_bg=active_fg_bg}
App{parent=apps_1,x=16,y=7,text="\x08",title="Devices",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.lightGray),active_fg_bg=active_fg_bg}
App{parent=apps_1,x=2,y=12,text="\xb6",title="Guide",callback=function()open(APP_ID.GUIDE)end,app_fg_bg=cpair(colors.black,colors.cyan),active_fg_bg=active_fg_bg}

View File

@@ -7,15 +7,15 @@ local style = require("pocket.ui.style")
local core = require("graphics.core")
local Div = require("graphics.elements.div")
local TextBox = require("graphics.elements.textbox")
local Div = require("graphics.elements.Div")
local TextBox = require("graphics.elements.TextBox")
local PushButton = require("graphics.elements.controls.push_button")
local PushButton = require("graphics.elements.controls.PushButton")
local DataIndicator = require("graphics.elements.indicators.data")
local StateIndicator = require("graphics.elements.indicators.state")
local IconIndicator = require("graphics.elements.indicators.icon")
local VerticalBar = require("graphics.elements.indicators.vbar")
local DataIndicator = require("graphics.elements.indicators.DataIndicator")
local StateIndicator = require("graphics.elements.indicators.StateIndicator")
local IconIndicator = require("graphics.elements.indicators.IconIndicator")
local VerticalBar = require("graphics.elements.indicators.VerticalBar")
local ALIGN = core.ALIGN
local cpair = core.cpair
@@ -29,8 +29,8 @@ local yel_ind_s = style.icon_states.yel_ind_s
-- create a boiler view in the unit app
---@param app pocket_app
---@param u_page nav_tree_page
---@param panes table
---@param blr_pane graphics_element
---@param panes Div[]
---@param blr_pane Div
---@param b_id integer boiler ID
---@param ps psil
---@param update function

View File

@@ -7,15 +7,15 @@ local style = require("pocket.ui.style")
local core = require("graphics.core")
local Div = require("graphics.elements.div")
local TextBox = require("graphics.elements.textbox")
local Div = require("graphics.elements.Div")
local TextBox = require("graphics.elements.TextBox")
local PushButton = require("graphics.elements.controls.push_button")
local PushButton = require("graphics.elements.controls.PushButton")
local DataIndicator = require("graphics.elements.indicators.data")
local StateIndicator = require("graphics.elements.indicators.state")
local IconIndicator = require("graphics.elements.indicators.icon")
local VerticalBar = require("graphics.elements.indicators.vbar")
local DataIndicator = require("graphics.elements.indicators.DataIndicator")
local StateIndicator = require("graphics.elements.indicators.StateIndicator")
local IconIndicator = require("graphics.elements.indicators.IconIndicator")
local VerticalBar = require("graphics.elements.indicators.VerticalBar")
local ALIGN = core.ALIGN
local cpair = core.cpair
@@ -29,8 +29,8 @@ local yel_ind_s = style.icon_states.yel_ind_s
-- create a reactor view in the unit app
---@param app pocket_app
---@param u_page nav_tree_page
---@param panes table
---@param page_div graphics_element
---@param panes Div[]
---@param page_div Div
---@param u_ps psil
---@param update function
return function (app, u_page, panes, page_div, u_ps, update)

View File

@@ -6,16 +6,16 @@ local style = require("pocket.ui.style")
local core = require("graphics.core")
local Div = require("graphics.elements.div")
local TextBox = require("graphics.elements.textbox")
local Div = require("graphics.elements.Div")
local TextBox = require("graphics.elements.TextBox")
local PushButton = require("graphics.elements.controls.push_button")
local PushButton = require("graphics.elements.controls.PushButton")
local DataIndicator = require("graphics.elements.indicators.data")
local IconIndicator = require("graphics.elements.indicators.icon")
local PowerIndicator = require("graphics.elements.indicators.power")
local StateIndicator = require("graphics.elements.indicators.state")
local VerticalBar = require("graphics.elements.indicators.vbar")
local DataIndicator = require("graphics.elements.indicators.DataIndicator")
local IconIndicator = require("graphics.elements.indicators.IconIndicator")
local PowerIndicator = require("graphics.elements.indicators.PowerIndicator")
local StateIndicator = require("graphics.elements.indicators.StateIndicator")
local VerticalBar = require("graphics.elements.indicators.VerticalBar")
local ALIGN = core.ALIGN
local cpair = core.cpair
@@ -30,8 +30,8 @@ local yel_ind_s = style.icon_states.yel_ind_s
-- create a turbine view in the unit app
---@param app pocket_app
---@param u_page nav_tree_page
---@param panes table
---@param tbn_pane graphics_element
---@param panes Div[]
---@param tbn_pane Div
---@param u_id integer unit ID
---@param t_id integer turbine ID
---@param ps psil

View File

@@ -13,9 +13,16 @@ local cpair = core.cpair
style.root = cpair(colors.white, colors.black)
style.header = cpair(colors.white, colors.gray)
style.text_fg = cpair(colors.white, colors._INHERIT)
style.label = cpair(colors.lightGray, colors.black)
style.label_unit_pair = cpair(colors.lightGray, colors.lightGray)
style.field = cpair(colors.white, colors.gray)
style.field_disable = cpair(colors.gray, colors.lightGray)
style.btn_disable = cpair(colors.gray, colors.black)
style.hzd_fg_bg = cpair(colors.white, colors.gray)
style.hzd_dis_colors = cpair(colors.white, colors.lightGray)
style.colors = {
{ c = colors.red, hex = 0xdf4949 },
{ c = colors.orange, hex = 0xffb659 },
@@ -73,6 +80,16 @@ states.yel_ind_s = {
{ color = cpair(colors.black, colors.yellow), symbol = "-" }
}
states.grn_ind_s = {
{ color = cpair(colors.black, colors.lightGray), symbol = "\x07" },
{ color = cpair(colors.black, colors.green), symbol = "+" }
}
states.wht_ind_s = {
{ color = cpair(colors.black, colors.lightGray), symbol = "\x07" },
{ color = cpair(colors.black, colors.white), symbol = "+" }
}
style.icon_states = states
-- MAIN LAYOUT --

View File

@@ -8,11 +8,11 @@ local plc = require("reactor-plc.plc")
local core = require("graphics.core")
local Div = require("graphics.elements.div")
local ListBox = require("graphics.elements.listbox")
local TextBox = require("graphics.elements.textbox")
local Div = require("graphics.elements.Div")
local ListBox = require("graphics.elements.ListBox")
local TextBox = require("graphics.elements.TextBox")
local PushButton = require("graphics.elements.controls.push_button")
local PushButton = require("graphics.elements.controls.PushButton")
local tri = util.trinary
@@ -33,8 +33,8 @@ local self = {
settings = nil, ---@type plc_config
run_test_btn = nil, ---@type graphics_element
sc_log = nil, ---@type graphics_element
run_test_btn = nil, ---@type PushButton
sc_log = nil, ---@type ListBox
self_check_msg = nil ---@type function
}
@@ -160,7 +160,7 @@ local function self_check()
end
-- exit self check back home
---@param main_pane graphics_element
---@param main_pane MultiPane
local function exit_self_check(main_pane)
tcd.abort(handle_timeout)
self.net_listen = false
@@ -172,10 +172,10 @@ end
local check = {}
-- create the self-check view
---@param main_pane graphics_element
---@param main_pane MultiPane
---@param settings_cfg plc_config
---@param check_sys graphics_element
---@param style table
---@param check_sys Div
---@param style { [string]: cpair }
function check.create(main_pane, settings_cfg, check_sys, style)
local bw_fg_bg = style.bw_fg_bg
local g_lg_fg_bg = style.g_lg_fg_bg

View File

@@ -5,20 +5,20 @@ local util = require("scada-common.util")
local core = require("graphics.core")
local themes = require("graphics.themes")
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 Div = require("graphics.elements.Div")
local ListBox = require("graphics.elements.ListBox")
local MultiPane = require("graphics.elements.MultiPane")
local TextBox = require("graphics.elements.TextBox")
local CheckBox = require("graphics.elements.controls.checkbox")
local PushButton = require("graphics.elements.controls.push_button")
local Radio2D = require("graphics.elements.controls.radio_2d")
local RadioButton = require("graphics.elements.controls.radio_button")
local Checkbox = require("graphics.elements.controls.Checkbox")
local PushButton = require("graphics.elements.controls.PushButton")
local Radio2D = require("graphics.elements.controls.Radio2D")
local RadioButton = require("graphics.elements.controls.RadioButton")
local NumberField = require("graphics.elements.form.number_field")
local TextField = require("graphics.elements.form.text_field")
local NumberField = require("graphics.elements.form.NumberField")
local TextField = require("graphics.elements.form.TextField")
local IndLight = require("graphics.elements.indicators.light")
local IndLight = require("graphics.elements.indicators.IndicatorLight")
local cpair = core.cpair
@@ -31,8 +31,8 @@ local self = {
bundled_emcool = nil, ---@type function
show_auth_key = nil, ---@type function
show_key_btn = nil, ---@type graphics_element
auth_key_textbox = nil, ---@type graphics_element
show_key_btn = nil, ---@type PushButton
auth_key_textbox = nil, ---@type TextBox
auth_key_value = ""
}
@@ -61,17 +61,14 @@ local system = {}
-- create the system configuration view
---@param tool_ctl _plc_cfg_tool_ctl
---@param main_pane graphics_element
---@param cfg_sys table
---@param divs table
---@param style table
---@param main_pane MultiPane
---@param cfg_sys [ plc_config, plc_config, plc_config, { [1]: string, [2]: string, [3]: any }[], function ]
---@param divs Div[]
---@param style { [string]: cpair }
---@param exit function
function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit)
---@type plc_config, plc_config, plc_config, table, function
local settings_cfg, ini_cfg, tmp_cfg, fields, load_settings = table.unpack(cfg_sys)
---@type graphics_element, graphics_element, graphics_element, graphics_element, graphics_element
local plc_cfg, net_cfg, log_cfg, clr_cfg, summary = table.unpack(divs)
local settings_cfg, ini_cfg, tmp_cfg, fields, load_settings = cfg_sys[1], cfg_sys[2], cfg_sys[3], cfg_sys[4], cfg_sys[5]
local plc_cfg, net_cfg, log_cfg, clr_cfg, summary = divs[1], divs[2], divs[3], divs[4], divs[5]
local bw_fg_bg = style.bw_fg_bg
local g_lg_fg_bg = style.g_lg_fg_bg
@@ -93,7 +90,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit)
TextBox{parent=plc_c_1,x=1,y=1,text="Would you like to set this PLC as networked?"}
TextBox{parent=plc_c_1,x=1,y=3,height=4,text="If you have a supervisor, select the box. You will later be prompted to select the network configuration. If you instead want to use this as a standalone safety system, don't select the box.",fg_bg=g_lg_fg_bg}
local networked = CheckBox{parent=plc_c_1,x=1,y=8,label="Networked",default=ini_cfg.Networked,box_fg_bg=cpair(colors.orange,colors.black)}
local networked = Checkbox{parent=plc_c_1,x=1,y=8,label="Networked",default=ini_cfg.Networked,box_fg_bg=cpair(colors.orange,colors.black)}
local function submit_networked()
self.set_networked(networked.get_value())
@@ -131,7 +128,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit)
TextBox{parent=plc_c_3,x=1,y=1,height=4,text="When networked, the supervisor takes care of emergency coolant via RTUs. However, you can configure independent emergency coolant via the PLC."}
TextBox{parent=plc_c_3,x=1,y=6,height=5,text="This independent control can be used with or without a supervisor. To configure, you would next select the interface of the redstone output connected to one or more mekanism pipes.",fg_bg=g_lg_fg_bg}
local en_em_cool = CheckBox{parent=plc_c_3,x=1,y=11,label="Enable PLC Emergency Coolant Control",default=ini_cfg.EmerCoolEnable,box_fg_bg=cpair(colors.orange,colors.black)}
local en_em_cool = Checkbox{parent=plc_c_3,x=1,y=11,label="Enable PLC Emergency Coolant Control",default=ini_cfg.EmerCoolEnable,box_fg_bg=cpair(colors.orange,colors.black)}
local function next_from_plc()
if tmp_cfg.Networked then main_pane.set_value(3) else main_pane.set_value(4) end
@@ -149,7 +146,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit)
local side = Radio2D{parent=plc_c_4,x=1,y=2,rows=2,columns=3,default=side_to_idx(ini_cfg.EmerCoolSide),options=side_options,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.orange}
TextBox{parent=plc_c_4,x=1,y=5,text="Bundled Redstone Configuration"}
local bundled = CheckBox{parent=plc_c_4,x=1,y=6,label="Is Bundled?",default=ini_cfg.EmerCoolColor~=nil,box_fg_bg=cpair(colors.orange,colors.black),callback=function(v)self.bundled_emcool(v)end}
local bundled = Checkbox{parent=plc_c_4,x=1,y=6,label="Is Bundled?",default=ini_cfg.EmerCoolColor~=nil,box_fg_bg=cpair(colors.orange,colors.black),callback=function(v)self.bundled_emcool(v)end}
local color = Radio2D{parent=plc_c_4,x=1,y=8,rows=4,columns=4,default=color_to_idx(ini_cfg.EmerCoolColor),options=color_options,radio_colors=cpair(colors.lightGray,colors.black),color_map=color_options_map,disable_color=colors.gray,disable_fg_bg=g_lg_fg_bg}
if ini_cfg.EmerCoolColor == nil then color.disable() end
@@ -240,14 +237,14 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit)
PushButton{parent=net_c_2,x=44,y=14,text="Next \x1a",callback=submit_ct_tr,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
TextBox{parent=net_c_3,x=1,y=1,height=2,text="Optionally, set the facility authentication key below. Do NOT use one of your passwords."}
TextBox{parent=net_c_3,x=1,y=4,height=6,text="This enables verifying that messages are authentic, so it is intended for security on multiplayer servers. All devices on the same network MUST use the same key if any device has a key. This does result in some extra compution (can slow things down).",fg_bg=g_lg_fg_bg}
TextBox{parent=net_c_3,x=1,y=4,height=6,text="This enables verifying that messages are authentic, so it is intended for security on multiplayer servers. All devices on the same network MUST use the same key if any device has a key. This does result in some extra computation (can slow things down).",fg_bg=g_lg_fg_bg}
TextBox{parent=net_c_3,x=1,y=11,text="Facility Auth Key"}
local key, _, censor = TextField{parent=net_c_3,x=1,y=12,max_len=64,value=ini_cfg.AuthKey,width=32,height=1,fg_bg=bw_fg_bg}
local key, _ = TextField{parent=net_c_3,x=1,y=12,max_len=64,value=ini_cfg.AuthKey,width=32,height=1,fg_bg=bw_fg_bg}
local function censor_key(enable) censor(util.trinary(enable, "*", nil)) end
local function censor_key(enable) key.censor(util.trinary(enable, "*", nil)) end
local hide_key = CheckBox{parent=net_c_3,x=34,y=12,label="Hide",box_fg_bg=cpair(colors.lightBlue,colors.black),callback=censor_key}
local hide_key = Checkbox{parent=net_c_3,x=34,y=12,label="Hide",box_fg_bg=cpair(colors.lightBlue,colors.black),callback=censor_key}
hide_key.set_value(true)
censor_key(true)
@@ -282,7 +279,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit)
TextBox{parent=log_c_1,x=1,y=7,text="Log File Path"}
local path = TextField{parent=log_c_1,x=1,y=8,width=49,height=1,value=ini_cfg.LogPath,max_len=128,fg_bg=bw_fg_bg}
local en_dbg = CheckBox{parent=log_c_1,x=1,y=10,default=ini_cfg.LogDebug,label="Enable Logging Debug Messages",box_fg_bg=cpair(colors.pink,colors.black)}
local en_dbg = Checkbox{parent=log_c_1,x=1,y=10,default=ini_cfg.LogDebug,label="Enable Logging Debug Messages",box_fg_bg=cpair(colors.pink,colors.black)}
TextBox{parent=log_c_1,x=3,y=11,height=2,text="This results in much larger log files. It is best to only use this when there is a problem.",fg_bg=g_lg_fg_bg}
local path_err = TextBox{parent=log_c_1,x=8,y=14,width=35,text="Please provide a log file path.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}

View File

@@ -12,13 +12,13 @@ local system = require("reactor-plc.config.system")
local core = require("graphics.core")
local themes = require("graphics.themes")
local DisplayBox = require("graphics.elements.displaybox")
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 DisplayBox = require("graphics.elements.DisplayBox")
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 PushButton = require("graphics.elements.controls.push_button")
local PushButton = require("graphics.elements.controls.PushButton")
local println = util.println
local tri = util.trinary
@@ -58,11 +58,11 @@ local tool_ctl = {
viewing_config = false,
jumped_to_color = false,
view_cfg = nil, ---@type graphics_element
color_cfg = nil, ---@type graphics_element
color_next = nil, ---@type graphics_element
color_apply = nil, ---@type graphics_element
settings_apply = nil, ---@type graphics_element
view_cfg = nil, ---@type PushButton
color_cfg = nil, ---@type PushButton
color_next = nil, ---@type PushButton
color_apply = nil, ---@type PushButton
settings_apply = nil, ---@type PushButton
gen_summary = nil, ---@type function
load_legacy = nil, ---@type function
@@ -80,11 +80,11 @@ local tmp_cfg = {
ConnTimeout = nil, ---@type number
TrustedRange = nil, ---@type number
AuthKey = nil, ---@type string|nil
LogMode = 0,
LogMode = 0, ---@type LOG_MODE
LogPath = "",
LogDebug = false,
FrontPanelTheme = 1,
ColorMode = 1
FrontPanelTheme = 1, ---@type FP_THEME
ColorMode = 1 ---@type COLOR_MODE
}
---@class plc_config
@@ -125,7 +125,7 @@ local function load_settings(target, raw)
end
-- create the config view
---@param display graphics_element
---@param display DisplayBox
local function config_view(display)
local bw_fg_bg = style.bw_fg_bg
local g_lg_fg_bg = style.g_lg_fg_bg

View File

@@ -77,7 +77,7 @@ end
-- transmit RPS data across the bus
---@param tripped boolean RPS tripped
---@param status table RPS status
---@param status boolean[] RPS status
---@param emer_cool_active boolean RPS activated the emergency coolant
function databus.tx_rps(tripped, status, emer_cool_active)
databus.ps.publish("rps_scram", tripped)

View File

@@ -13,15 +13,15 @@ local style = require("reactor-plc.panel.style")
local core = require("graphics.core")
local flasher = require("graphics.flasher")
local Div = require("graphics.elements.div")
local Rectangle = require("graphics.elements.rectangle")
local TextBox = require("graphics.elements.textbox")
local Div = require("graphics.elements.Div")
local Rectangle = require("graphics.elements.Rectangle")
local TextBox = require("graphics.elements.TextBox")
local PushButton = require("graphics.elements.controls.push_button")
local PushButton = require("graphics.elements.controls.PushButton")
local LED = require("graphics.elements.indicators.led")
local LEDPair = require("graphics.elements.indicators.ledpair")
local RGBLED = require("graphics.elements.indicators.ledrgb")
local LED = require("graphics.elements.indicators.LED")
local LEDPair = require("graphics.elements.indicators.LEDPair")
local RGBLED = require("graphics.elements.indicators.RGBLED")
local LINK_STATE = types.PANEL_LINK_STATE
@@ -34,7 +34,7 @@ local ind_grn = style.ind_grn
local ind_red = style.ind_red
-- create new front panel view
---@param panel graphics_element main displaybox
---@param panel DisplayBox main displaybox
local function init(panel)
local s_hi_box = style.theme.highlight_box

View File

@@ -110,22 +110,8 @@ end
---@param reactor table
---@param is_formed boolean
function plc.rps_init(reactor, is_formed)
local state_keys = {
high_dmg = 1,
high_temp = 2,
low_coolant = 3,
ex_waste = 4,
ex_hcoolant = 5,
no_fuel = 6,
fault = 7,
timeout = 8,
manual = 9,
automatic = 10,
sys_fail = 11,
force_disabled = 12
}
local self = {
---@type boolean[] check states
state = { false, false, false, false, false, false, false, false, false, false, false, false },
reactor_enabled = false,
enabled_at = 0,
@@ -136,12 +122,27 @@ function plc.rps_init(reactor, is_formed)
trip_cause = "ok" ---@type rps_trip_cause
}
local CHK = {
HIGH_DMG = 1,
HIGH_TEMP = 2,
LOW_COOLANT = 3,
EX_WASTE = 4,
EX_HCOOLANT = 5,
NO_FUEL = 6,
FAULT = 7,
TIMEOUT = 8,
MANUAL = 9,
AUTOMATIC = 10,
SYS_FAIL = 11,
FORCE_DISABLED = 12
}
-- PRIVATE FUNCTIONS --
-- set reactor access fault flag
local function _set_fault()
if reactor.__p_last_fault() ~= "Terminated" then
self.state[state_keys.fault] = true
self.state[CHK.FAULT] = true
end
end
@@ -203,8 +204,8 @@ function plc.rps_init(reactor, is_formed)
end
-- always update, since some ppm failures constitute not being formed
if not self.state[state_keys.sys_fail] then
self.state[state_keys.sys_fail] = not self.formed
if not self.state[CHK.SYS_FAIL] then
self.state[CHK.SYS_FAIL] = not self.formed
end
end
@@ -214,8 +215,8 @@ function plc.rps_init(reactor, is_formed)
if _check_and_handle_ppm_call(disabled) then
self.force_disabled = disabled
if not self.state[state_keys.force_disabled] then
self.state[state_keys.force_disabled] = disabled
if not self.state[CHK.FORCE_DISABLED] then
self.state[CHK.FORCE_DISABLED] = disabled
end
end
end
@@ -223,8 +224,8 @@ function plc.rps_init(reactor, is_formed)
-- check for high damage
local function _high_damage()
local damage_percent = reactor.getDamagePercent()
if _check_and_handle_ppm_call(damage_percent) and not self.state[state_keys.high_dmg] then
self.state[state_keys.high_dmg] = damage_percent >= RPS_LIMITS.MAX_DAMAGE_PERCENT
if _check_and_handle_ppm_call(damage_percent) and not self.state[CHK.HIGH_DMG] then
self.state[CHK.HIGH_DMG] = damage_percent >= RPS_LIMITS.MAX_DAMAGE_PERCENT
end
end
@@ -232,40 +233,40 @@ function plc.rps_init(reactor, is_formed)
local function _high_temp()
-- mekanism: MAX_DAMAGE_TEMPERATURE = 1200K
local temp = reactor.getTemperature()
if _check_and_handle_ppm_call(temp) and not self.state[state_keys.high_temp] then
self.state[state_keys.high_temp] = temp >= RPS_LIMITS.MAX_DAMAGE_TEMPERATURE
if _check_and_handle_ppm_call(temp) and not self.state[CHK.HIGH_TEMP] then
self.state[CHK.HIGH_TEMP] = temp >= RPS_LIMITS.MAX_DAMAGE_TEMPERATURE
end
end
-- check if there is very low coolant
local function _low_coolant()
local coolant_filled = reactor.getCoolantFilledPercentage()
if _check_and_handle_ppm_call(coolant_filled) and not self.state[state_keys.low_coolant] then
self.state[state_keys.low_coolant] = coolant_filled < RPS_LIMITS.MIN_COOLANT_FILL
if _check_and_handle_ppm_call(coolant_filled) and not self.state[CHK.LOW_COOLANT] then
self.state[CHK.LOW_COOLANT] = coolant_filled < RPS_LIMITS.MIN_COOLANT_FILL
end
end
-- check for excess waste (>80% filled)
local function _excess_waste()
local w_filled = reactor.getWasteFilledPercentage()
if _check_and_handle_ppm_call(w_filled) and not self.state[state_keys.ex_waste] then
self.state[state_keys.ex_waste] = w_filled > RPS_LIMITS.MAX_WASTE_FILL
if _check_and_handle_ppm_call(w_filled) and not self.state[CHK.EX_WASTE] then
self.state[CHK.EX_WASTE] = w_filled > RPS_LIMITS.MAX_WASTE_FILL
end
end
-- check for heated coolant backup (>95% filled)
local function _excess_heated_coolant()
local hc_filled = reactor.getHeatedCoolantFilledPercentage()
if _check_and_handle_ppm_call(hc_filled) and not self.state[state_keys.ex_hcoolant] then
self.state[state_keys.ex_hcoolant] = hc_filled > RPS_LIMITS.MAX_HEATED_COLLANT_FILL
if _check_and_handle_ppm_call(hc_filled) and not self.state[CHK.EX_HCOOLANT] then
self.state[CHK.EX_HCOOLANT] = hc_filled > RPS_LIMITS.MAX_HEATED_COLLANT_FILL
end
end
-- check if there is no fuel
local function _insufficient_fuel()
local fuel = reactor.getFuelFilledPercentage()
if _check_and_handle_ppm_call(fuel) and not self.state[state_keys.no_fuel] then
self.state[state_keys.no_fuel] = fuel <= RPS_LIMITS.NO_FUEL_FILL
if _check_and_handle_ppm_call(fuel) and not self.state[CHK.NO_FUEL] then
self.state[CHK.NO_FUEL] = fuel <= RPS_LIMITS.NO_FUEL_FILL
end
end
@@ -287,23 +288,23 @@ function plc.rps_init(reactor, is_formed)
-- trip for a PLC comms timeout
function public.trip_timeout()
self.state[state_keys.timeout] = true
self.state[CHK.TIMEOUT] = true
end
-- manually SCRAM the reactor
function public.trip_manual()
self.state[state_keys.manual] = true
self.state[CHK.MANUAL] = true
end
-- automatic SCRAM commanded by supervisor
function public.trip_auto()
self.state[state_keys.automatic] = true
self.state[CHK.AUTOMATIC] = true
end
-- trip for unformed reactor
function public.trip_sys_fail()
self.state[state_keys.fault] = true
self.state[state_keys.sys_fail] = true
self.state[CHK.FAULT] = true
self.state[CHK.SYS_FAIL] = true
end
-- SCRAM the reactor now<br>
@@ -350,7 +351,7 @@ function plc.rps_init(reactor, is_formed)
function public.auto_activate()
-- clear automatic SCRAM if it was the cause
if self.tripped and self.trip_cause == "automatic" then
self.state[state_keys.automatic] = true
self.state[CHK.AUTOMATIC] = true
self.trip_cause = RPS_TRIP_CAUSE.OK
self.tripped = false
@@ -388,40 +389,40 @@ function plc.rps_init(reactor, is_formed)
-- check system states in order of severity
if self.tripped then
status = self.trip_cause
elseif self.state[state_keys.sys_fail] then
elseif self.state[CHK.SYS_FAIL] then
log.warning("RPS: system failure, reactor not formed")
status = RPS_TRIP_CAUSE.SYS_FAIL
elseif self.state[state_keys.force_disabled] then
elseif self.state[CHK.FORCE_DISABLED] then
log.warning("RPS: reactor was force disabled")
status = RPS_TRIP_CAUSE.FORCE_DISABLED
elseif self.state[state_keys.high_dmg] then
elseif self.state[CHK.HIGH_DMG] then
log.warning("RPS: high damage")
status = RPS_TRIP_CAUSE.HIGH_DMG
elseif self.state[state_keys.high_temp] then
elseif self.state[CHK.HIGH_TEMP] then
log.warning("RPS: high temperature")
status = RPS_TRIP_CAUSE.HIGH_TEMP
elseif self.state[state_keys.low_coolant] then
elseif self.state[CHK.LOW_COOLANT] then
log.warning("RPS: low coolant")
status = RPS_TRIP_CAUSE.LOW_COOLANT
elseif self.state[state_keys.ex_waste] then
elseif self.state[CHK.EX_WASTE] then
log.warning("RPS: full waste")
status = RPS_TRIP_CAUSE.EX_WASTE
elseif self.state[state_keys.ex_hcoolant] then
elseif self.state[CHK.EX_HCOOLANT] then
log.warning("RPS: heated coolant backup")
status = RPS_TRIP_CAUSE.EX_HCOOLANT
elseif self.state[state_keys.no_fuel] then
elseif self.state[CHK.NO_FUEL] then
log.warning("RPS: no fuel")
status = RPS_TRIP_CAUSE.NO_FUEL
elseif self.state[state_keys.fault] then
elseif self.state[CHK.FAULT] then
log.warning("RPS: reactor access fault")
status = RPS_TRIP_CAUSE.FAULT
elseif self.state[state_keys.timeout] then
elseif self.state[CHK.TIMEOUT] then
log.warning("RPS: supervisor connection timeout")
status = RPS_TRIP_CAUSE.TIMEOUT
elseif self.state[state_keys.manual] then
elseif self.state[CHK.MANUAL] then
log.warning("RPS: manual SCRAM requested")
status = RPS_TRIP_CAUSE.MANUAL
elseif self.state[state_keys.automatic] then
elseif self.state[CHK.AUTOMATIC] then
log.warning("RPS: automatic SCRAM requested")
status = RPS_TRIP_CAUSE.AUTOMATIC
else
@@ -449,7 +450,7 @@ function plc.rps_init(reactor, is_formed)
end
-- update emergency coolant control if configured
_set_emer_cool(self.state[state_keys.low_coolant])
_set_emer_cool(self.state[CHK.LOW_COOLANT])
-- report RPS status
databus.tx_rps(self.tripped, self.state, self.emer_cool_active)
@@ -465,7 +466,7 @@ function plc.rps_init(reactor, is_formed)
---@nodiscard
function public.get_trip_cause() return self.trip_cause end
---@nodiscard
function public.is_low_coolant() return self.states[state_keys.low_coolant] end
function public.is_low_coolant() return self.states[CHK.LOW_COOLANT] end
---@nodiscard
function public.is_active() return self.reactor_enabled end
@@ -495,16 +496,16 @@ function plc.rps_init(reactor, is_formed)
self.tripped = false
self.trip_cause = RPS_TRIP_CAUSE.OK
self.state[state_keys.fault] = false
self.state[state_keys.sys_fail] = false
self.state[CHK.FAULT] = false
self.state[CHK.SYS_FAIL] = false
log.info("RPS: partial reset on formed")
end
-- reset the automatic and timeout trip flags, then clear trip if that was the trip cause
function public.auto_reset()
self.state[state_keys.automatic] = false
self.state[state_keys.timeout] = false
self.state[CHK.AUTOMATIC] = false
self.state[CHK.TIMEOUT] = false
if self.trip_cause == RPS_TRIP_CAUSE.AUTOMATIC or self.trip_cause == RPS_TRIP_CAUSE.TIMEOUT then
self.trip_cause = RPS_TRIP_CAUSE.OK

Some files were not shown because too many files have changed in this diff Show More