mirror of
https://github.com/MikaylaFischler/cc-mek-scada.git
synced 2024-08-30 18:22:34 +00:00
commit
9bd79dacad
171
ccmsi.lua
171
ccmsi.lua
@ -20,7 +20,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|||||||
local function println(message) print(tostring(message)) end
|
local function println(message) print(tostring(message)) end
|
||||||
local function print(message) term.write(tostring(message)) end
|
local function print(message) term.write(tostring(message)) end
|
||||||
|
|
||||||
local CCMSI_VERSION = "v1.5a"
|
local CCMSI_VERSION = "v1.7d"
|
||||||
|
|
||||||
local install_dir = "/.install-cache"
|
local install_dir = "/.install-cache"
|
||||||
local manifest_path = "https://mikaylafischler.github.io/cc-mek-scada/manifests/"
|
local manifest_path = "https://mikaylafischler.github.io/cc-mek-scada/manifests/"
|
||||||
@ -44,11 +44,15 @@ local function get_opt(opt, options)
|
|||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- wait for any key to be pressed
|
||||||
|
---@diagnostic disable-next-line: undefined-field
|
||||||
|
local function any_key() os.pullEvent("key_up") end
|
||||||
|
|
||||||
-- ask the user yes or no
|
-- ask the user yes or no
|
||||||
local function ask_y_n(question, default)
|
local function ask_y_n(question, default)
|
||||||
print(question)
|
print(question)
|
||||||
if default == true then print(" (Y/n)? ") else print(" (y/N)? ") end
|
if default == true then print(" (Y/n)? ") else print(" (y/N)? ") end
|
||||||
local response = read()
|
local response = read();any_key()
|
||||||
if response == "" then return default
|
if response == "" then return default
|
||||||
elseif response == "Y" or response == "y" then return true
|
elseif response == "Y" or response == "y" then return true
|
||||||
elseif response == "N" or response == "n" then return false
|
elseif response == "N" or response == "n" then return false
|
||||||
@ -56,13 +60,13 @@ local function ask_y_n(question, default)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- print out a white + blue text message
|
-- print out a white + blue text message
|
||||||
local function pkg_message(message, package) white(); print(message .. " "); blue(); println(package); white() end
|
local function pkg_message(message, package) white();print(message .. " ");blue();println(package);white() end
|
||||||
|
|
||||||
-- indicate actions to be taken based on package differences for installs/updates
|
-- indicate actions to be taken based on package differences for installs/updates
|
||||||
local function show_pkg_change(name, v_local, v_remote)
|
local function show_pkg_change(name, v_local, v_remote)
|
||||||
if v_local ~= nil then
|
if v_local ~= nil then
|
||||||
if v_local ~= v_remote then
|
if v_local ~= v_remote then
|
||||||
print("[" .. name .. "] updating "); blue(); print(v_local); white(); print(" \xbb "); blue(); println(v_remote); white()
|
print("[" .. name .. "] updating ");blue();print(v_local);white();print(" \xbb ");blue();println(v_remote);white()
|
||||||
elseif mode == "install" then
|
elseif mode == "install" then
|
||||||
pkg_message("[" .. name .. "] reinstalling", v_local)
|
pkg_message("[" .. name .. "] reinstalling", v_local)
|
||||||
end
|
end
|
||||||
@ -87,15 +91,13 @@ end
|
|||||||
local function get_remote_manifest()
|
local function get_remote_manifest()
|
||||||
local response, error = http.get(install_manifest)
|
local response, error = http.get(install_manifest)
|
||||||
if response == nil then
|
if response == nil then
|
||||||
orange(); println("failed to get installation manifest from GitHub, cannot update or install")
|
orange();println("failed to get installation manifest from GitHub, cannot update or install")
|
||||||
red(); println("HTTP error: " .. error); white()
|
red();println("HTTP error: " .. error);white()
|
||||||
return false, {}
|
return false, {}
|
||||||
end
|
end
|
||||||
|
|
||||||
local ok, manifest = pcall(function () return textutils.unserializeJSON(response.readAll()) end)
|
local ok, manifest = pcall(function () return textutils.unserializeJSON(response.readAll()) end)
|
||||||
if not ok then
|
if not ok then red();println("error parsing remote installation manifest");white() end
|
||||||
red(); println("error parsing remote installation manifest"); white()
|
|
||||||
end
|
|
||||||
|
|
||||||
return ok, manifest
|
return ok, manifest
|
||||||
end
|
end
|
||||||
@ -107,7 +109,7 @@ local function write_install_manifest(manifest, dependencies)
|
|||||||
local is_dependency = false
|
local is_dependency = false
|
||||||
for _, dependency in pairs(dependencies) do
|
for _, dependency in pairs(dependencies) do
|
||||||
if (key == "bootloader" and dependency == "system") or key == dependency then
|
if (key == "bootloader" and dependency == "system") or key == dependency then
|
||||||
is_dependency = true; break
|
is_dependency = true;break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if key == app or key == "comms" or is_dependency then versions[key] = value end
|
if key == app or key == "comms" or is_dependency then versions[key] = value end
|
||||||
@ -120,6 +122,78 @@ local function write_install_manifest(manifest, dependencies)
|
|||||||
imfile.close()
|
imfile.close()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- recursively build a tree out of the file manifest
|
||||||
|
local function gen_tree(manifest)
|
||||||
|
local function _tree_add(tree, split)
|
||||||
|
if #split > 1 then
|
||||||
|
local name = table.remove(split, 1)
|
||||||
|
if tree[name] == nil then tree[name] = {} end
|
||||||
|
table.insert(tree[name], _tree_add(tree[name], split))
|
||||||
|
else return split[1] end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local list, tree = {}, {}
|
||||||
|
|
||||||
|
-- make a list of each and every file
|
||||||
|
for _, files in pairs(manifest.files) do for i = 1, #files do table.insert(list, files[i]) end end
|
||||||
|
|
||||||
|
for i = 1, #list do
|
||||||
|
local split = {}
|
||||||
|
string.gsub(list[i], "([^/]+)", function(c) split[#split + 1] = c end)
|
||||||
|
if #split == 1 then table.insert(tree, list[i])
|
||||||
|
else table.insert(tree, _tree_add(tree, split)) end
|
||||||
|
end
|
||||||
|
|
||||||
|
return tree
|
||||||
|
end
|
||||||
|
|
||||||
|
local function _in_array(val, array)
|
||||||
|
for _, v in pairs(array) do if v == val then return true end end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local function _clean_dir(dir, tree)
|
||||||
|
if tree == nil then tree = {} end
|
||||||
|
local ls = fs.list(dir)
|
||||||
|
for _, val in pairs(ls) do
|
||||||
|
local path = dir .. "/" .. val
|
||||||
|
if fs.isDir(path) then
|
||||||
|
_clean_dir(path, tree[val])
|
||||||
|
if #fs.list(path) == 0 then fs.delete(path);println("deleted " .. path) end
|
||||||
|
elseif not _in_array(val, tree) then
|
||||||
|
fs.delete(path)
|
||||||
|
println("deleted " .. path)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- go through app/common directories to delete unused files
|
||||||
|
local function clean(manifest)
|
||||||
|
local root_ext = false
|
||||||
|
local tree = gen_tree(manifest)
|
||||||
|
|
||||||
|
table.insert(tree, "install_manifest.json")
|
||||||
|
table.insert(tree, "ccmsi.lua")
|
||||||
|
table.insert(tree, "log.txt")
|
||||||
|
|
||||||
|
lgray()
|
||||||
|
|
||||||
|
local ls = fs.list("/")
|
||||||
|
for _, val in pairs(ls) do
|
||||||
|
if fs.isDir(val) then
|
||||||
|
if tree[val] ~= nil then _clean_dir("/" .. val, tree[val]) end
|
||||||
|
if #fs.list(val) == 0 then fs.delete(val);println("deleted " .. val) end
|
||||||
|
elseif not _in_array(val, tree) then
|
||||||
|
root_ext = true
|
||||||
|
yellow();println(val .. " not used")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
white()
|
||||||
|
if root_ext then println("Files in root directory won't be automatically deleted.") end
|
||||||
|
end
|
||||||
|
|
||||||
-- get and validate command line options
|
-- get and validate command line options
|
||||||
|
|
||||||
println("-- CC Mekanism SCADA Installer " .. CCMSI_VERSION .. " --")
|
println("-- CC Mekanism SCADA Installer " .. CCMSI_VERSION .. " --")
|
||||||
@ -136,33 +210,33 @@ if #opts == 0 or opts[1] == "help" then
|
|||||||
println(" update - update files EXCEPT for config/logs")
|
println(" update - update files EXCEPT for config/logs")
|
||||||
println(" remove - delete files EXCEPT for config/logs")
|
println(" remove - delete files EXCEPT for config/logs")
|
||||||
println(" purge - delete files INCLUDING config/logs")
|
println(" purge - delete files INCLUDING config/logs")
|
||||||
white(); println("<app>"); lgray()
|
white();println("<app>");lgray()
|
||||||
println(" reactor-plc - reactor PLC firmware")
|
println(" reactor-plc - reactor PLC firmware")
|
||||||
println(" rtu - RTU firmware")
|
println(" rtu - RTU firmware")
|
||||||
println(" supervisor - supervisor server application")
|
println(" supervisor - supervisor server application")
|
||||||
println(" coordinator - coordinator application")
|
println(" coordinator - coordinator application")
|
||||||
println(" pocket - pocket application")
|
println(" pocket - pocket application")
|
||||||
white(); println("<branch>"); yellow()
|
white();println("<branch>");yellow()
|
||||||
println(" second parameter when used with check")
|
println(" second parameter when used with check")
|
||||||
lgray(); println(" main (default) | latest | devel"); white()
|
lgray();println(" main (default) | latest | devel");white()
|
||||||
return
|
return
|
||||||
else
|
else
|
||||||
mode = get_opt(opts[1], { "check", "install", "update", "remove", "purge" })
|
mode = get_opt(opts[1], { "check", "install", "update", "remove", "purge" })
|
||||||
if mode == nil then
|
if mode == nil then
|
||||||
red(); println("Unrecognized mode."); white()
|
red();println("Unrecognized mode.");white()
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
app = get_opt(opts[2], { "reactor-plc", "rtu", "supervisor", "coordinator", "pocket" })
|
app = get_opt(opts[2], { "reactor-plc", "rtu", "supervisor", "coordinator", "pocket" })
|
||||||
if app == nil and mode ~= "check" then
|
if app == nil and mode ~= "check" then
|
||||||
red(); println("Unrecognized application."); white()
|
red();println("Unrecognized application.");white()
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
-- determine target
|
-- determine target
|
||||||
if mode == "check" then target = opts[2] else target = opts[3] end
|
if mode == "check" then target = opts[2] else target = opts[3] end
|
||||||
if (target ~= "main") and (target ~= "latest") and (target ~= "devel") then
|
if (target ~= "main") and (target ~= "latest") and (target ~= "devel") then
|
||||||
if (target and target ~= "") then yellow(); println("Unknown target, defaulting to 'main'"); white() end
|
if (target and target ~= "") then yellow();println("Unknown target, defaulting to 'main'");white() end
|
||||||
target = "main"
|
target = "main"
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -179,7 +253,7 @@ if mode == "check" then
|
|||||||
|
|
||||||
local local_ok, local_manifest = read_local_manifest()
|
local local_ok, local_manifest = read_local_manifest()
|
||||||
if not local_ok then
|
if not local_ok then
|
||||||
yellow(); println("failed to load local installation information"); white()
|
yellow();println("failed to load local installation information");white()
|
||||||
local_manifest = { versions = { installer = CCMSI_VERSION } }
|
local_manifest = { versions = { installer = CCMSI_VERSION } }
|
||||||
else
|
else
|
||||||
local_manifest.versions.installer = CCMSI_VERSION
|
local_manifest.versions.installer = CCMSI_VERSION
|
||||||
@ -190,16 +264,16 @@ if mode == "check" then
|
|||||||
term.setTextColor(colors.purple)
|
term.setTextColor(colors.purple)
|
||||||
print(string.format("%-14s", "[" .. key .. "]"))
|
print(string.format("%-14s", "[" .. key .. "]"))
|
||||||
if key == "installer" or (local_ok and (local_manifest.versions[key] ~= nil)) then
|
if key == "installer" or (local_ok and (local_manifest.versions[key] ~= nil)) then
|
||||||
blue(); print(local_manifest.versions[key])
|
blue();print(local_manifest.versions[key])
|
||||||
if value ~= local_manifest.versions[key] then
|
if value ~= local_manifest.versions[key] then
|
||||||
white(); print(" (")
|
white();print(" (")
|
||||||
term.setTextColor(colors.cyan)
|
term.setTextColor(colors.cyan)
|
||||||
print(value); white(); println(" available)")
|
print(value);white();println(" available)")
|
||||||
else green(); println(" (up to date)") end
|
else green();println(" (up to date)") end
|
||||||
else
|
else
|
||||||
lgray(); print("not installed"); white(); print(" (latest ")
|
lgray();print("not installed");white();print(" (latest ")
|
||||||
term.setTextColor(colors.cyan)
|
term.setTextColor(colors.cyan)
|
||||||
print(value); white(); println(")")
|
print(value);white();println(")")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
elseif mode == "install" or mode == "update" then
|
elseif mode == "install" or mode == "update" then
|
||||||
@ -218,7 +292,7 @@ elseif mode == "install" or mode == "update" then
|
|||||||
local local_ok, local_manifest = read_local_manifest()
|
local local_ok, local_manifest = read_local_manifest()
|
||||||
if not local_ok then
|
if not local_ok then
|
||||||
if mode == "update" then
|
if mode == "update" then
|
||||||
red(); println("failed to load local installation information, cannot update"); white()
|
red();println("failed to load local installation information, cannot update");white()
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
@ -229,13 +303,13 @@ elseif mode == "install" or mode == "update" then
|
|||||||
ver.lockbox.v_local = local_manifest.versions.lockbox
|
ver.lockbox.v_local = local_manifest.versions.lockbox
|
||||||
|
|
||||||
if local_manifest.versions[app] == nil then
|
if local_manifest.versions[app] == nil then
|
||||||
red(); println("another application is already installed, please purge it before installing a new application"); white()
|
red();println("another application is already installed, please purge it before installing a new application");white()
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local_manifest.versions.installer = CCMSI_VERSION
|
local_manifest.versions.installer = CCMSI_VERSION
|
||||||
if manifest.versions.installer ~= CCMSI_VERSION then
|
if manifest.versions.installer ~= CCMSI_VERSION then
|
||||||
yellow(); println("a newer version of the installer is available, it is recommended to download it"); white()
|
yellow();println("a newer version of the installer is available, it is recommended to download it");white()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -265,7 +339,7 @@ elseif mode == "install" or mode == "update" then
|
|||||||
show_pkg_change("comms", ver.comms.v_local, ver.comms.v_remote)
|
show_pkg_change("comms", ver.comms.v_local, ver.comms.v_remote)
|
||||||
ver.comms.changed = ver.comms.v_local ~= ver.comms.v_remote
|
ver.comms.changed = ver.comms.v_local ~= ver.comms.v_remote
|
||||||
if ver.comms.changed and ver.comms.v_local ~= nil then
|
if ver.comms.changed and ver.comms.v_local ~= nil then
|
||||||
print("[comms] "); yellow(); println("other devices on the network will require an update"); white()
|
print("[comms] ");yellow();println("other devices on the network will require an update");white()
|
||||||
end
|
end
|
||||||
|
|
||||||
-- display graphics version change information
|
-- display graphics version change information
|
||||||
@ -277,7 +351,7 @@ elseif mode == "install" or mode == "update" then
|
|||||||
ver.lockbox.changed = ver.lockbox.v_local ~= ver.lockbox.v_remote
|
ver.lockbox.changed = ver.lockbox.v_local ~= ver.lockbox.v_remote
|
||||||
|
|
||||||
-- ask for confirmation
|
-- ask for confirmation
|
||||||
if not ask_y_n("Continue?", false) then return end
|
if not ask_y_n("Continue", false) then return end
|
||||||
|
|
||||||
--------------------------
|
--------------------------
|
||||||
-- START INSTALL/UPDATE --
|
-- START INSTALL/UPDATE --
|
||||||
@ -302,7 +376,7 @@ elseif mode == "install" or mode == "update" then
|
|||||||
-- check space constraints
|
-- check space constraints
|
||||||
if space_available < space_required then
|
if space_available < space_required then
|
||||||
single_file_mode = true
|
single_file_mode = true
|
||||||
yellow(); println("WARNING: Insufficient space available for a full download!"); white()
|
yellow();println("WARNING: Insufficient space available for a full download!");white()
|
||||||
println("Files can be downloaded one by one, so if you are replacing a current install this will not be a problem unless installation fails.")
|
println("Files can be downloaded one by one, so if you are replacing a current install this will not be a problem unless installation fails.")
|
||||||
if mode == "update" then println("If installation still fails, delete this device's log file or uninstall the app (not purge) and try again.") end
|
if mode == "update" then println("If installation still fails, delete this device's log file or uninstall the app (not purge) and try again.") end
|
||||||
if not ask_y_n("Do you wish to continue?", false) then
|
if not ask_y_n("Do you wish to continue?", false) then
|
||||||
@ -343,7 +417,7 @@ elseif mode == "install" or mode == "update" then
|
|||||||
local dl, err = http.get(repo_path .. file)
|
local dl, err = http.get(repo_path .. file)
|
||||||
|
|
||||||
if dl == nil then
|
if dl == nil then
|
||||||
red(); println("GET HTTP Error " .. err)
|
red();println("GET HTTP Error " .. err)
|
||||||
success = false
|
success = false
|
||||||
break
|
break
|
||||||
else
|
else
|
||||||
@ -384,10 +458,13 @@ elseif mode == "install" or mode == "update" then
|
|||||||
if mode == "install" then
|
if mode == "install" then
|
||||||
println("Installation completed successfully.")
|
println("Installation completed successfully.")
|
||||||
else println("Update completed successfully.") end
|
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
|
else
|
||||||
if mode == "install" then
|
if mode == "install" then
|
||||||
red(); println("Installation failed.")
|
red();println("Installation failed.")
|
||||||
else orange(); println("Update failed, existing files unmodified.") end
|
else orange();println("Update failed, existing files unmodified.") end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
-- go through all files and replace one by one
|
-- go through all files and replace one by one
|
||||||
@ -405,7 +482,7 @@ elseif mode == "install" or mode == "update" then
|
|||||||
local dl, err = http.get(repo_path .. file)
|
local dl, err = http.get(repo_path .. file)
|
||||||
|
|
||||||
if dl == nil then
|
if dl == nil then
|
||||||
red(); println("GET HTTP Error " .. err)
|
red();println("GET HTTP Error " .. err)
|
||||||
success = false
|
success = false
|
||||||
break
|
break
|
||||||
else
|
else
|
||||||
@ -424,6 +501,9 @@ elseif mode == "install" or mode == "update" then
|
|||||||
if mode == "install" then
|
if mode == "install" then
|
||||||
println("Installation completed successfully.")
|
println("Installation completed successfully.")
|
||||||
else println("Update completed successfully.") end
|
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
|
else
|
||||||
red()
|
red()
|
||||||
if mode == "install" then
|
if mode == "install" then
|
||||||
@ -434,10 +514,10 @@ elseif mode == "install" or mode == "update" then
|
|||||||
elseif mode == "remove" or mode == "purge" then
|
elseif mode == "remove" or mode == "purge" then
|
||||||
local ok, manifest = read_local_manifest()
|
local ok, manifest = read_local_manifest()
|
||||||
if not ok then
|
if not ok then
|
||||||
red(); println("Error parsing local installation manifest."); white()
|
red();println("Error parsing local installation manifest.");white()
|
||||||
return
|
return
|
||||||
elseif mode == "remove" and manifest.versions[app] == nil then
|
elseif mode == "remove" and manifest.versions[app] == nil then
|
||||||
red(); println(app .. " is not installed, cannot remove."); white()
|
red();println(app .. " is not installed, cannot remove.");white()
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -449,7 +529,10 @@ elseif mode == "remove" or mode == "purge" then
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- ask for confirmation
|
-- ask for confirmation
|
||||||
if not ask_y_n("Continue?", false) then return end
|
if not ask_y_n("Continue", false) then return end
|
||||||
|
|
||||||
|
-- delete unused files first
|
||||||
|
clean(manifest)
|
||||||
|
|
||||||
local file_list = manifest.files
|
local file_list = manifest.files
|
||||||
local dependencies = manifest.depends[app]
|
local dependencies = manifest.depends[app]
|
||||||
@ -469,9 +552,9 @@ elseif mode == "remove" or mode == "purge" then
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
if not log_deleted then
|
if not log_deleted then
|
||||||
red(); println("failed to delete log file")
|
red();println("failed to delete log file")
|
||||||
white(); println("press enter to continue...")
|
white();println("press any key to continue...")
|
||||||
read(); lgray()
|
any_key();lgray()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -480,10 +563,7 @@ elseif mode == "remove" or mode == "purge" then
|
|||||||
local files = file_list[dependency]
|
local files = file_list[dependency]
|
||||||
for _, file in pairs(files) do
|
for _, file in pairs(files) do
|
||||||
if mode == "purge" or file ~= config_file then
|
if mode == "purge" or file ~= config_file then
|
||||||
if fs.exists(file) then
|
if fs.exists(file) then fs.delete(file);println("deleted " .. file) end
|
||||||
fs.delete(file)
|
|
||||||
println("deleted " .. file)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -508,8 +588,7 @@ elseif mode == "remove" or mode == "purge" then
|
|||||||
end
|
end
|
||||||
|
|
||||||
if folder ~= app and fs.isDir(folder) then
|
if folder ~= app and fs.isDir(folder) then
|
||||||
fs.delete(folder)
|
fs.delete(folder);println("deleted app subdirectory " .. folder)
|
||||||
println("deleted app subdirectory " .. folder)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -527,7 +606,7 @@ elseif mode == "remove" or mode == "purge" then
|
|||||||
imfile.close()
|
imfile.close()
|
||||||
end
|
end
|
||||||
|
|
||||||
green(); println("Done!")
|
green();println("Done!")
|
||||||
end
|
end
|
||||||
|
|
||||||
white()
|
white()
|
||||||
|
@ -2,6 +2,7 @@ local comms = require("scada-common.comms")
|
|||||||
local log = require("scada-common.log")
|
local log = require("scada-common.log")
|
||||||
local ppm = require("scada-common.ppm")
|
local ppm = require("scada-common.ppm")
|
||||||
local util = require("scada-common.util")
|
local util = require("scada-common.util")
|
||||||
|
local types = require("scada-common.types")
|
||||||
|
|
||||||
local iocontrol = require("coordinator.iocontrol")
|
local iocontrol = require("coordinator.iocontrol")
|
||||||
local process = require("coordinator.process")
|
local process = require("coordinator.process")
|
||||||
@ -12,7 +13,6 @@ local dialog = require("coordinator.ui.dialog")
|
|||||||
|
|
||||||
local print = util.print
|
local print = util.print
|
||||||
local println = util.println
|
local println = util.println
|
||||||
local println_ts = util.println_ts
|
|
||||||
|
|
||||||
local PROTOCOL = comms.PROTOCOL
|
local PROTOCOL = comms.PROTOCOL
|
||||||
local DEVICE_TYPE = comms.DEVICE_TYPE
|
local DEVICE_TYPE = comms.DEVICE_TYPE
|
||||||
@ -22,6 +22,8 @@ local SCADA_CRDN_TYPE = comms.SCADA_CRDN_TYPE
|
|||||||
local UNIT_COMMAND = comms.UNIT_COMMAND
|
local UNIT_COMMAND = comms.UNIT_COMMAND
|
||||||
local FAC_COMMAND = comms.FAC_COMMAND
|
local FAC_COMMAND = comms.FAC_COMMAND
|
||||||
|
|
||||||
|
local LINK_TIMEOUT = 60.0
|
||||||
|
|
||||||
local coordinator = {}
|
local coordinator = {}
|
||||||
|
|
||||||
-- request the user to select a monitor
|
-- request the user to select a monitor
|
||||||
@ -227,9 +229,12 @@ function coordinator.comms(version, nic, crd_channel, svr_channel, pkt_channel,
|
|||||||
sv_seq_num = 0,
|
sv_seq_num = 0,
|
||||||
sv_r_seq_num = nil,
|
sv_r_seq_num = nil,
|
||||||
sv_config_err = false,
|
sv_config_err = false,
|
||||||
connected = false,
|
|
||||||
last_est_ack = ESTABLISH_ACK.ALLOW,
|
last_est_ack = ESTABLISH_ACK.ALLOW,
|
||||||
last_api_est_acks = {}
|
last_api_est_acks = {},
|
||||||
|
est_start = 0,
|
||||||
|
est_last = 0,
|
||||||
|
est_tick_waiting = nil,
|
||||||
|
est_task_done = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
comms.set_trusted_range(range)
|
comms.set_trusted_range(range)
|
||||||
@ -295,77 +300,78 @@ function coordinator.comms(version, nic, crd_channel, svr_channel, pkt_channel,
|
|||||||
---@class coord_comms
|
---@class coord_comms
|
||||||
local public = {}
|
local public = {}
|
||||||
|
|
||||||
|
-- try to connect to the supervisor if not already linked
|
||||||
|
---@param abort boolean? true to print out cancel info if not linked (use on program terminate)
|
||||||
|
---@return boolean ok, boolean start_ui
|
||||||
|
function public.try_connect(abort)
|
||||||
|
local ok = true
|
||||||
|
local start_ui = false
|
||||||
|
|
||||||
|
if not self.sv_linked then
|
||||||
|
if self.est_tick_waiting == nil then
|
||||||
|
self.est_start = util.time_s()
|
||||||
|
self.est_last = self.est_start
|
||||||
|
|
||||||
|
self.est_tick_waiting, self.est_task_done =
|
||||||
|
coordinator.log_comms_connecting("attempting to connect to configured supervisor on channel " .. svr_channel)
|
||||||
|
|
||||||
|
_send_establish()
|
||||||
|
else
|
||||||
|
self.est_tick_waiting(math.max(0, LINK_TIMEOUT - (util.time_s() - self.est_start)))
|
||||||
|
end
|
||||||
|
|
||||||
|
if abort or (util.time_s() - self.est_start) >= LINK_TIMEOUT then
|
||||||
|
self.est_task_done(false)
|
||||||
|
|
||||||
|
if abort then
|
||||||
|
coordinator.log_comms("supervisor connection attempt cancelled by user")
|
||||||
|
elseif self.sv_config_err then
|
||||||
|
coordinator.log_comms("supervisor cooling configuration invalid, check supervisor config file")
|
||||||
|
elseif not self.sv_linked then
|
||||||
|
if self.last_est_ack == ESTABLISH_ACK.DENY then
|
||||||
|
coordinator.log_comms("supervisor connection attempt denied")
|
||||||
|
elseif self.last_est_ack == ESTABLISH_ACK.COLLISION then
|
||||||
|
coordinator.log_comms("supervisor connection failed due to collision")
|
||||||
|
elseif self.last_est_ack == ESTABLISH_ACK.BAD_VERSION then
|
||||||
|
coordinator.log_comms("supervisor connection failed due to version mismatch")
|
||||||
|
else
|
||||||
|
coordinator.log_comms("supervisor connection failed with no valid response")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
ok = false
|
||||||
|
elseif self.sv_config_err then
|
||||||
|
coordinator.log_comms("supervisor cooling configuration invalid, check supervisor config file")
|
||||||
|
ok = false
|
||||||
|
elseif (util.time_s() - self.est_last) > 1.0 then
|
||||||
|
_send_establish()
|
||||||
|
self.est_last = util.time_s()
|
||||||
|
end
|
||||||
|
elseif self.est_tick_waiting ~= nil then
|
||||||
|
self.est_task_done(true)
|
||||||
|
self.est_tick_waiting = nil
|
||||||
|
self.est_task_done = nil
|
||||||
|
start_ui = true
|
||||||
|
end
|
||||||
|
|
||||||
|
return ok, start_ui
|
||||||
|
end
|
||||||
|
|
||||||
-- close the connection to the server
|
-- close the connection to the server
|
||||||
function public.close()
|
function public.close()
|
||||||
sv_watchdog.cancel()
|
sv_watchdog.cancel()
|
||||||
self.sv_addr = comms.BROADCAST
|
self.sv_addr = comms.BROADCAST
|
||||||
self.sv_linked = false
|
self.sv_linked = false
|
||||||
self.sv_r_seq_num = nil
|
self.sv_r_seq_num = nil
|
||||||
|
iocontrol.fp_link_state(types.PANEL_LINK_STATE.DISCONNECTED)
|
||||||
_send_sv(PROTOCOL.SCADA_MGMT, SCADA_MGMT_TYPE.CLOSE, {})
|
_send_sv(PROTOCOL.SCADA_MGMT, SCADA_MGMT_TYPE.CLOSE, {})
|
||||||
end
|
end
|
||||||
|
|
||||||
-- attempt to connect to the subervisor
|
|
||||||
---@nodiscard
|
|
||||||
---@param timeout_s number timeout in seconds
|
|
||||||
---@param tick_dmesg_waiting function callback to tick dmesg waiting
|
|
||||||
---@param task_done function callback to show done on dmesg
|
|
||||||
---@return boolean sv_linked true if connected, false otherwise
|
|
||||||
--- EVENT_CONSUMER: this function consumes events
|
|
||||||
function public.sv_connect(timeout_s, tick_dmesg_waiting, task_done)
|
|
||||||
local clock = util.new_clock(1)
|
|
||||||
local start = util.time_s()
|
|
||||||
local terminated = false
|
|
||||||
|
|
||||||
_send_establish()
|
|
||||||
|
|
||||||
clock.start()
|
|
||||||
|
|
||||||
while (util.time_s() - start) < timeout_s and (not self.sv_linked) and (not self.sv_config_err) do
|
|
||||||
local event, p1, p2, p3, p4, p5 = util.pull_event()
|
|
||||||
|
|
||||||
if event == "timer" and clock.is_clock(p1) then
|
|
||||||
-- timed out attempt, try again
|
|
||||||
tick_dmesg_waiting(math.max(0, timeout_s - (util.time_s() - start)))
|
|
||||||
_send_establish()
|
|
||||||
clock.start()
|
|
||||||
elseif event == "timer" then
|
|
||||||
-- keep checking watchdog timers
|
|
||||||
apisessions.check_all_watchdogs(p1)
|
|
||||||
elseif event == "modem_message" then
|
|
||||||
-- handle message
|
|
||||||
local packet = public.parse_packet(p1, p2, p3, p4, p5)
|
|
||||||
public.handle_packet(packet)
|
|
||||||
elseif event == "terminate" then
|
|
||||||
terminated = true
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
task_done(self.sv_linked)
|
|
||||||
|
|
||||||
if terminated then
|
|
||||||
coordinator.log_comms("supervisor connection attempt cancelled by user")
|
|
||||||
elseif self.sv_config_err then
|
|
||||||
coordinator.log_comms("supervisor cooling configuration invalid, check supervisor config file")
|
|
||||||
elseif not self.sv_linked then
|
|
||||||
if self.last_est_ack == ESTABLISH_ACK.DENY then
|
|
||||||
coordinator.log_comms("supervisor connection attempt denied")
|
|
||||||
elseif self.last_est_ack == ESTABLISH_ACK.COLLISION then
|
|
||||||
coordinator.log_comms("supervisor connection failed due to collision")
|
|
||||||
elseif self.last_est_ack == ESTABLISH_ACK.BAD_VERSION then
|
|
||||||
coordinator.log_comms("supervisor connection failed due to version mismatch")
|
|
||||||
else
|
|
||||||
coordinator.log_comms("supervisor connection failed with no valid response")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return self.sv_linked
|
|
||||||
end
|
|
||||||
|
|
||||||
-- send a facility command
|
-- send a facility command
|
||||||
---@param cmd FAC_COMMAND command
|
---@param cmd FAC_COMMAND command
|
||||||
function public.send_fac_command(cmd)
|
---@param option any? optional option options for the optional options (like waste mode)
|
||||||
_send_sv(PROTOCOL.SCADA_CRDN, SCADA_CRDN_TYPE.FAC_CMD, { cmd })
|
function public.send_fac_command(cmd, option)
|
||||||
|
_send_sv(PROTOCOL.SCADA_CRDN, SCADA_CRDN_TYPE.FAC_CMD, { cmd, option })
|
||||||
end
|
end
|
||||||
|
|
||||||
-- send the auto process control configuration with a start command
|
-- send the auto process control configuration with a start command
|
||||||
@ -379,7 +385,7 @@ function coordinator.comms(version, nic, crd_channel, svr_channel, pkt_channel,
|
|||||||
-- send a unit command
|
-- send a unit command
|
||||||
---@param cmd UNIT_COMMAND command
|
---@param cmd UNIT_COMMAND command
|
||||||
---@param unit integer unit ID
|
---@param unit integer unit ID
|
||||||
---@param option any? optional option options for the optional options (like burn rate) (does option still look like a word?)
|
---@param option any? optional option options for the optional options (like burn rate)
|
||||||
function public.send_unit_command(cmd, unit, option)
|
function public.send_unit_command(cmd, unit, option)
|
||||||
_send_sv(PROTOCOL.SCADA_CRDN, SCADA_CRDN_TYPE.UNIT_CMD, { cmd, unit, option })
|
_send_sv(PROTOCOL.SCADA_CRDN, SCADA_CRDN_TYPE.UNIT_CMD, { cmd, unit, option })
|
||||||
end
|
end
|
||||||
@ -424,7 +430,10 @@ function coordinator.comms(version, nic, crd_channel, svr_channel, pkt_channel,
|
|||||||
|
|
||||||
-- handle a packet
|
-- handle a packet
|
||||||
---@param packet mgmt_frame|crdn_frame|capi_frame|nil
|
---@param packet mgmt_frame|crdn_frame|capi_frame|nil
|
||||||
|
---@return boolean close_ui
|
||||||
function public.handle_packet(packet)
|
function public.handle_packet(packet)
|
||||||
|
local was_linked = self.sv_linked
|
||||||
|
|
||||||
if packet ~= nil then
|
if packet ~= nil then
|
||||||
local l_chan = packet.scada_frame.local_channel()
|
local l_chan = packet.scada_frame.local_channel()
|
||||||
local r_chan = packet.scada_frame.remote_channel()
|
local r_chan = packet.scada_frame.remote_channel()
|
||||||
@ -434,7 +443,9 @@ function coordinator.comms(version, nic, crd_channel, svr_channel, pkt_channel,
|
|||||||
if l_chan ~= crd_channel then
|
if l_chan ~= crd_channel then
|
||||||
log.debug("received packet on unconfigured channel " .. l_chan, true)
|
log.debug("received packet on unconfigured channel " .. l_chan, true)
|
||||||
elseif r_chan == pkt_channel then
|
elseif r_chan == pkt_channel then
|
||||||
if protocol == PROTOCOL.COORD_API then
|
if not self.sv_linked then
|
||||||
|
log.debug("discarding pocket API packet before linked to supervisor")
|
||||||
|
elseif protocol == PROTOCOL.COORD_API then
|
||||||
---@cast packet capi_frame
|
---@cast packet capi_frame
|
||||||
-- look for an associated session
|
-- look for an associated session
|
||||||
local session = apisessions.find_session(src_addr)
|
local session = apisessions.find_session(src_addr)
|
||||||
@ -473,7 +484,6 @@ function coordinator.comms(version, nic, crd_channel, svr_channel, pkt_channel,
|
|||||||
elseif dev_type == DEVICE_TYPE.PKT then
|
elseif dev_type == DEVICE_TYPE.PKT then
|
||||||
-- pocket linking request
|
-- pocket linking request
|
||||||
local id = apisessions.establish_session(src_addr, firmware_v)
|
local id = apisessions.establish_session(src_addr, firmware_v)
|
||||||
println(util.c("[API] pocket (", firmware_v, ") [@", src_addr, "] \xbb connected"))
|
|
||||||
coordinator.log_comms(util.c("API_ESTABLISH: pocket (", firmware_v, ") [@", src_addr, "] connected with session ID ", id))
|
coordinator.log_comms(util.c("API_ESTABLISH: pocket (", firmware_v, ") [@", src_addr, "] connected with session ID ", id))
|
||||||
|
|
||||||
_send_api_establish_ack(packet.scada_frame, ESTABLISH_ACK.ALLOW)
|
_send_api_establish_ack(packet.scada_frame, ESTABLISH_ACK.ALLOW)
|
||||||
@ -496,12 +506,12 @@ function coordinator.comms(version, nic, crd_channel, svr_channel, pkt_channel,
|
|||||||
-- check sequence number
|
-- check sequence number
|
||||||
if self.sv_r_seq_num == nil then
|
if self.sv_r_seq_num == nil then
|
||||||
self.sv_r_seq_num = packet.scada_frame.seq_num()
|
self.sv_r_seq_num = packet.scada_frame.seq_num()
|
||||||
elseif self.connected and ((self.sv_r_seq_num + 1) ~= packet.scada_frame.seq_num()) then
|
elseif self.sv_linked and ((self.sv_r_seq_num + 1) ~= packet.scada_frame.seq_num()) then
|
||||||
log.warning("sequence out-of-order: last = " .. self.sv_r_seq_num .. ", new = " .. packet.scada_frame.seq_num())
|
log.warning("sequence out-of-order: last = " .. self.sv_r_seq_num .. ", new = " .. packet.scada_frame.seq_num())
|
||||||
return
|
return false
|
||||||
elseif self.sv_linked and src_addr ~= self.sv_addr then
|
elseif self.sv_linked and src_addr ~= self.sv_addr then
|
||||||
log.debug("received packet from unknown computer " .. src_addr .. " while linked; channel in use by another system?")
|
log.debug("received packet from unknown computer " .. src_addr .. " while linked; channel in use by another system?")
|
||||||
return
|
return false
|
||||||
else
|
else
|
||||||
self.sv_r_seq_num = packet.scada_frame.seq_num()
|
self.sv_r_seq_num = packet.scada_frame.seq_num()
|
||||||
end
|
end
|
||||||
@ -563,6 +573,10 @@ function coordinator.comms(version, nic, crd_channel, svr_channel, pkt_channel,
|
|||||||
end
|
end
|
||||||
elseif cmd == FAC_COMMAND.ACK_ALL_ALARMS then
|
elseif cmd == FAC_COMMAND.ACK_ALL_ALARMS then
|
||||||
iocontrol.get_db().facility.ack_alarms_ack(ack)
|
iocontrol.get_db().facility.ack_alarms_ack(ack)
|
||||||
|
elseif cmd == FAC_COMMAND.SET_WASTE_MODE then
|
||||||
|
process.waste_ack_handle(packet.data[2])
|
||||||
|
elseif cmd == FAC_COMMAND.SET_PU_FB then
|
||||||
|
process.pu_fb_ack_handle(packet.data[2])
|
||||||
else
|
else
|
||||||
log.debug(util.c("received facility command ack with unknown command ", cmd))
|
log.debug(util.c("received facility command ack with unknown command ", cmd))
|
||||||
end
|
end
|
||||||
@ -627,70 +641,7 @@ function coordinator.comms(version, nic, crd_channel, svr_channel, pkt_channel,
|
|||||||
end
|
end
|
||||||
elseif protocol == PROTOCOL.SCADA_MGMT then
|
elseif protocol == PROTOCOL.SCADA_MGMT then
|
||||||
---@cast packet mgmt_frame
|
---@cast packet mgmt_frame
|
||||||
if packet.type == SCADA_MGMT_TYPE.ESTABLISH then
|
if self.sv_linked then
|
||||||
-- connection with supervisor established
|
|
||||||
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 > 1 then
|
|
||||||
-- get configuration
|
|
||||||
|
|
||||||
---@class facility_conf
|
|
||||||
local conf = {
|
|
||||||
num_units = config[1], ---@type integer
|
|
||||||
defs = {} -- boilers and turbines
|
|
||||||
}
|
|
||||||
|
|
||||||
if (#config - 1) == (conf.num_units * 2) then
|
|
||||||
-- record sequence of pairs of [#boilers, #turbines] per unit
|
|
||||||
for i = 2, #config do
|
|
||||||
table.insert(conf.defs, config[i])
|
|
||||||
end
|
|
||||||
|
|
||||||
-- init io controller
|
|
||||||
iocontrol.init(conf, public)
|
|
||||||
|
|
||||||
self.sv_addr = src_addr
|
|
||||||
self.sv_linked = true
|
|
||||||
self.sv_config_err = false
|
|
||||||
else
|
|
||||||
self.sv_config_err = true
|
|
||||||
log.warning("invalid supervisor configuration definitions received, establish failed")
|
|
||||||
end
|
|
||||||
else
|
|
||||||
log.debug("invalid supervisor configuration table received, establish failed")
|
|
||||||
end
|
|
||||||
else
|
|
||||||
log.debug("SCADA_MGMT establish packet reply (len = 2) unsupported")
|
|
||||||
end
|
|
||||||
|
|
||||||
self.last_est_ack = est_ack
|
|
||||||
elseif packet.length == 1 then
|
|
||||||
local est_ack = packet.data[1]
|
|
||||||
|
|
||||||
if est_ack == ESTABLISH_ACK.DENY then
|
|
||||||
if self.last_est_ack ~= est_ack then
|
|
||||||
log.info("supervisor connection denied")
|
|
||||||
end
|
|
||||||
elseif est_ack == ESTABLISH_ACK.COLLISION then
|
|
||||||
if self.last_est_ack ~= est_ack then
|
|
||||||
log.warning("supervisor connection denied due to collision")
|
|
||||||
end
|
|
||||||
elseif est_ack == ESTABLISH_ACK.BAD_VERSION then
|
|
||||||
if self.last_est_ack ~= est_ack then
|
|
||||||
log.warning("supervisor comms version mismatch")
|
|
||||||
end
|
|
||||||
else
|
|
||||||
log.debug("SCADA_MGMT establish packet reply (len = 1) unsupported")
|
|
||||||
end
|
|
||||||
|
|
||||||
self.last_est_ack = est_ack
|
|
||||||
else
|
|
||||||
log.debug("SCADA_MGMT establish packet length mismatch")
|
|
||||||
end
|
|
||||||
elseif self.sv_linked then
|
|
||||||
if packet.type == SCADA_MGMT_TYPE.KEEP_ALIVE then
|
if packet.type == SCADA_MGMT_TYPE.KEEP_ALIVE then
|
||||||
-- keep alive request received, echo back
|
-- keep alive request received, echo back
|
||||||
if packet.length == 1 then
|
if packet.length == 1 then
|
||||||
@ -715,11 +666,83 @@ function coordinator.comms(version, nic, crd_channel, svr_channel, pkt_channel,
|
|||||||
self.sv_addr = comms.BROADCAST
|
self.sv_addr = comms.BROADCAST
|
||||||
self.sv_linked = false
|
self.sv_linked = false
|
||||||
self.sv_r_seq_num = nil
|
self.sv_r_seq_num = nil
|
||||||
println_ts("server connection closed by remote host")
|
iocontrol.fp_link_state(types.PANEL_LINK_STATE.DISCONNECTED)
|
||||||
log.info("server connection closed by remote host")
|
log.info("server connection closed by remote host")
|
||||||
else
|
else
|
||||||
log.debug("received unknown SCADA_MGMT packet type " .. packet.type)
|
log.debug("received unknown SCADA_MGMT packet type " .. packet.type)
|
||||||
end
|
end
|
||||||
|
elseif packet.type == SCADA_MGMT_TYPE.ESTABLISH then
|
||||||
|
-- connection with supervisor established
|
||||||
|
if packet.length == 2 then
|
||||||
|
local est_ack = packet.data[1]
|
||||||
|
local config = packet.data[2]
|
||||||
|
|
||||||
|
if est_ack == ESTABLISH_ACK.ALLOW then
|
||||||
|
-- reset to disconnected before validating
|
||||||
|
iocontrol.fp_link_state(types.PANEL_LINK_STATE.DISCONNECTED)
|
||||||
|
|
||||||
|
if type(config) == "table" and #config > 1 then
|
||||||
|
-- get configuration
|
||||||
|
|
||||||
|
---@class facility_conf
|
||||||
|
local conf = {
|
||||||
|
num_units = config[1], ---@type integer
|
||||||
|
defs = {} -- boilers and turbines
|
||||||
|
}
|
||||||
|
|
||||||
|
if (#config - 1) == (conf.num_units * 2) then
|
||||||
|
-- record sequence of pairs of [#boilers, #turbines] per unit
|
||||||
|
for i = 2, #config do
|
||||||
|
table.insert(conf.defs, config[i])
|
||||||
|
end
|
||||||
|
|
||||||
|
-- init io controller
|
||||||
|
iocontrol.init(conf, public)
|
||||||
|
|
||||||
|
self.sv_addr = src_addr
|
||||||
|
self.sv_linked = true
|
||||||
|
self.sv_r_seq_num = nil
|
||||||
|
self.sv_config_err = false
|
||||||
|
|
||||||
|
iocontrol.fp_link_state(types.PANEL_LINK_STATE.LINKED)
|
||||||
|
else
|
||||||
|
self.sv_config_err = true
|
||||||
|
log.warning("invalid supervisor configuration definitions received, establish failed")
|
||||||
|
end
|
||||||
|
else
|
||||||
|
log.debug("invalid supervisor configuration table received, establish failed")
|
||||||
|
end
|
||||||
|
else
|
||||||
|
log.debug("SCADA_MGMT establish packet reply (len = 2) unsupported")
|
||||||
|
end
|
||||||
|
|
||||||
|
self.last_est_ack = est_ack
|
||||||
|
elseif packet.length == 1 then
|
||||||
|
local est_ack = packet.data[1]
|
||||||
|
|
||||||
|
if est_ack == ESTABLISH_ACK.DENY then
|
||||||
|
if self.last_est_ack ~= est_ack then
|
||||||
|
iocontrol.fp_link_state(types.PANEL_LINK_STATE.DENIED)
|
||||||
|
log.info("supervisor connection denied")
|
||||||
|
end
|
||||||
|
elseif est_ack == ESTABLISH_ACK.COLLISION then
|
||||||
|
if self.last_est_ack ~= est_ack then
|
||||||
|
iocontrol.fp_link_state(types.PANEL_LINK_STATE.COLLISION)
|
||||||
|
log.warning("supervisor connection denied due to collision")
|
||||||
|
end
|
||||||
|
elseif est_ack == ESTABLISH_ACK.BAD_VERSION then
|
||||||
|
if self.last_est_ack ~= est_ack then
|
||||||
|
iocontrol.fp_link_state(types.PANEL_LINK_STATE.BAD_VERSION)
|
||||||
|
log.warning("supervisor comms version mismatch")
|
||||||
|
end
|
||||||
|
else
|
||||||
|
log.debug("SCADA_MGMT establish packet reply (len = 1) unsupported")
|
||||||
|
end
|
||||||
|
|
||||||
|
self.last_est_ack = est_ack
|
||||||
|
else
|
||||||
|
log.debug("SCADA_MGMT establish packet length mismatch")
|
||||||
|
end
|
||||||
else
|
else
|
||||||
log.debug("discarding non-link SCADA_MGMT packet before linked")
|
log.debug("discarding non-link SCADA_MGMT packet before linked")
|
||||||
end
|
end
|
||||||
@ -730,6 +753,8 @@ function coordinator.comms(version, nic, crd_channel, svr_channel, pkt_channel,
|
|||||||
log.debug("received packet for unknown channel " .. r_chan, true)
|
log.debug("received packet for unknown channel " .. r_chan, true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
return was_linked and not self.sv_linked
|
||||||
end
|
end
|
||||||
|
|
||||||
-- check if the coordinator is still linked to the supervisor
|
-- check if the coordinator is still linked to the supervisor
|
||||||
|
@ -10,9 +10,15 @@ local util = require("scada-common.util")
|
|||||||
local process = require("coordinator.process")
|
local process = require("coordinator.process")
|
||||||
local sounder = require("coordinator.sounder")
|
local sounder = require("coordinator.sounder")
|
||||||
|
|
||||||
|
local pgi = require("coordinator.ui.pgi")
|
||||||
|
|
||||||
local ALARM_STATE = types.ALARM_STATE
|
local ALARM_STATE = types.ALARM_STATE
|
||||||
local PROCESS = types.PROCESS
|
local PROCESS = types.PROCESS
|
||||||
|
|
||||||
|
-- nominal RTT is ping (0ms to 10ms usually) + 500ms for CRD main loop tick
|
||||||
|
local WARN_RTT = 1000 -- 2x as long as expected w/ 0 ping
|
||||||
|
local HIGH_RTT = 1500 -- 3.33x as long as expected w/ 0 ping
|
||||||
|
|
||||||
local iocontrol = {}
|
local iocontrol = {}
|
||||||
|
|
||||||
---@class ioctl
|
---@class ioctl
|
||||||
@ -27,6 +33,19 @@ local function __generic_ack(success) end
|
|||||||
|
|
||||||
-- luacheck: unused args
|
-- luacheck: unused args
|
||||||
|
|
||||||
|
-- initialize front panel PSIL
|
||||||
|
---@param firmware_v string coordinator version
|
||||||
|
---@param comms_v string comms version
|
||||||
|
function iocontrol.init_fp(firmware_v, comms_v)
|
||||||
|
---@class ioctl_front_panel
|
||||||
|
io.fp = {
|
||||||
|
ps = psil.create()
|
||||||
|
}
|
||||||
|
|
||||||
|
io.fp.ps.publish("version", firmware_v)
|
||||||
|
io.fp.ps.publish("comms_version", comms_v)
|
||||||
|
end
|
||||||
|
|
||||||
-- initialize the coordinator IO controller
|
-- initialize the coordinator IO controller
|
||||||
---@param conf facility_conf configuration
|
---@param conf facility_conf configuration
|
||||||
---@param comms coord_comms comms reference
|
---@param comms coord_comms comms reference
|
||||||
@ -52,6 +71,10 @@ function iocontrol.init(conf, comms)
|
|||||||
gen_fault = false
|
gen_fault = false
|
||||||
},
|
},
|
||||||
|
|
||||||
|
---@type WASTE_PRODUCT
|
||||||
|
auto_current_waste_product = types.WASTE_PRODUCT.PLUTONIUM,
|
||||||
|
auto_pu_fallback_active = false,
|
||||||
|
|
||||||
radiation = types.new_zero_radiation_reading(),
|
radiation = types.new_zero_radiation_reading(),
|
||||||
|
|
||||||
save_cfg_ack = __generic_ack,
|
save_cfg_ack = __generic_ack,
|
||||||
@ -65,16 +88,21 @@ function iocontrol.init(conf, comms)
|
|||||||
induction_ps_tbl = {},
|
induction_ps_tbl = {},
|
||||||
induction_data_tbl = {},
|
induction_data_tbl = {},
|
||||||
|
|
||||||
|
sps_ps_tbl = {},
|
||||||
|
sps_data_tbl = {},
|
||||||
|
|
||||||
|
tank_ps_tbl = {},
|
||||||
|
tank_data_tbl = {},
|
||||||
|
|
||||||
env_d_ps = psil.create(),
|
env_d_ps = psil.create(),
|
||||||
env_d_data = {}
|
env_d_data = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
-- create induction tables (currently only 1 is supported)
|
-- create induction and SPS tables (currently only 1 of each is supported)
|
||||||
for _ = 1, conf.num_units do
|
table.insert(io.facility.induction_ps_tbl, psil.create())
|
||||||
local data = {} ---@type imatrix_session_db
|
table.insert(io.facility.induction_data_tbl, {})
|
||||||
table.insert(io.facility.induction_ps_tbl, psil.create())
|
table.insert(io.facility.sps_ps_tbl, psil.create())
|
||||||
table.insert(io.facility.induction_data_tbl, data)
|
table.insert(io.facility.sps_data_tbl, {})
|
||||||
end
|
|
||||||
|
|
||||||
io.units = {}
|
io.units = {}
|
||||||
for i = 1, conf.num_units do
|
for i = 1, conf.num_units do
|
||||||
@ -87,11 +115,15 @@ function iocontrol.init(conf, comms)
|
|||||||
|
|
||||||
num_boilers = 0,
|
num_boilers = 0,
|
||||||
num_turbines = 0,
|
num_turbines = 0,
|
||||||
|
num_snas = 0,
|
||||||
|
|
||||||
control_state = false,
|
control_state = false,
|
||||||
burn_rate_cmd = 0.0,
|
burn_rate_cmd = 0.0,
|
||||||
waste_control = 0,
|
|
||||||
radiation = types.new_zero_radiation_reading(),
|
radiation = types.new_zero_radiation_reading(),
|
||||||
|
sna_prod_rate = 0.0,
|
||||||
|
|
||||||
|
waste_mode = types.WASTE_MODE.MANUAL_PLUTONIUM,
|
||||||
|
waste_product = types.WASTE_PRODUCT.PLUTONIUM,
|
||||||
|
|
||||||
-- auto control group
|
-- auto control group
|
||||||
a_group = 0,
|
a_group = 0,
|
||||||
@ -100,10 +132,10 @@ function iocontrol.init(conf, comms)
|
|||||||
scram = function () process.scram(i) end,
|
scram = function () process.scram(i) end,
|
||||||
reset_rps = function () process.reset_rps(i) end,
|
reset_rps = function () process.reset_rps(i) end,
|
||||||
ack_alarms = function () process.ack_all_alarms(i) end,
|
ack_alarms = function () process.ack_all_alarms(i) end,
|
||||||
set_burn = function (rate) process.set_rate(i, rate) end, ---@param rate number burn rate
|
set_burn = function (rate) process.set_rate(i, rate) end, ---@param rate number burn rate
|
||||||
set_waste = function (mode) process.set_waste(i, mode) end, ---@param mode integer waste processing mode
|
set_waste = function (mode) process.set_unit_waste(i, mode) end, ---@param mode WASTE_MODE waste processing mode
|
||||||
|
|
||||||
set_group = function (grp) process.set_group(i, grp) end, ---@param grp integer|0 group ID or 0
|
set_group = function (grp) process.set_group(i, grp) end, ---@param grp integer|0 group ID or 0 for manual
|
||||||
|
|
||||||
start_ack = __generic_ack,
|
start_ack = __generic_ack,
|
||||||
scram_ack = __generic_ack,
|
scram_ack = __generic_ack,
|
||||||
@ -152,7 +184,10 @@ function iocontrol.init(conf, comms)
|
|||||||
boiler_data_tbl = {},
|
boiler_data_tbl = {},
|
||||||
|
|
||||||
turbine_ps_tbl = {},
|
turbine_ps_tbl = {},
|
||||||
turbine_data_tbl = {}
|
turbine_data_tbl = {},
|
||||||
|
|
||||||
|
tank_ps_tbl = {},
|
||||||
|
tank_data_tbl = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
-- create boiler tables
|
-- create boiler tables
|
||||||
@ -179,6 +214,92 @@ function iocontrol.init(conf, comms)
|
|||||||
process.init(io, comms)
|
process.init(io, comms)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--#region Front Panel PSIL
|
||||||
|
|
||||||
|
-- toggle heartbeat indicator
|
||||||
|
function iocontrol.heartbeat() io.fp.ps.toggle("heartbeat") end
|
||||||
|
|
||||||
|
-- report presence of the wireless modem
|
||||||
|
---@param has_modem boolean
|
||||||
|
function iocontrol.fp_has_modem(has_modem) io.fp.ps.publish("has_modem", has_modem) end
|
||||||
|
|
||||||
|
-- report presence of the speaker
|
||||||
|
---@param has_speaker boolean
|
||||||
|
function iocontrol.fp_has_speaker(has_speaker) io.fp.ps.publish("has_speaker", has_speaker) end
|
||||||
|
|
||||||
|
-- report supervisor link state
|
||||||
|
---@param state integer
|
||||||
|
function iocontrol.fp_link_state(state) io.fp.ps.publish("link_state", state) end
|
||||||
|
|
||||||
|
-- report monitor connection state
|
||||||
|
---@param id integer unit ID or 0 for main
|
||||||
|
function iocontrol.fp_monitor_state(id, connected)
|
||||||
|
local name = "main_monitor"
|
||||||
|
if id > 0 then name = "unit_monitor_" .. id end
|
||||||
|
io.fp.ps.publish(name, connected)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- report PKT firmware version and PKT session connection state
|
||||||
|
---@param session_id integer PKT session
|
||||||
|
---@param fw string firmware version
|
||||||
|
---@param s_addr integer PKT computer ID
|
||||||
|
function iocontrol.fp_pkt_connected(session_id, fw, s_addr)
|
||||||
|
io.fp.ps.publish("pkt_" .. session_id .. "_fw", fw)
|
||||||
|
io.fp.ps.publish("pkt_" .. session_id .. "_addr", util.sprintf("@ C% 3d", s_addr))
|
||||||
|
pgi.create_pkt_entry(session_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- report PKT session disconnected
|
||||||
|
---@param session_id integer PKT session
|
||||||
|
function iocontrol.fp_pkt_disconnected(session_id)
|
||||||
|
pgi.delete_pkt_entry(session_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- transmit PKT session RTT
|
||||||
|
---@param session_id integer PKT session
|
||||||
|
---@param rtt integer round trip time
|
||||||
|
function iocontrol.fp_pkt_rtt(session_id, rtt)
|
||||||
|
io.fp.ps.publish("pkt_" .. session_id .. "_rtt", rtt)
|
||||||
|
|
||||||
|
if rtt > HIGH_RTT then
|
||||||
|
io.fp.ps.publish("pkt_" .. session_id .. "_rtt_color", colors.red)
|
||||||
|
elseif rtt > WARN_RTT then
|
||||||
|
io.fp.ps.publish("pkt_" .. session_id .. "_rtt_color", colors.yellow_hc)
|
||||||
|
else
|
||||||
|
io.fp.ps.publish("pkt_" .. session_id .. "_rtt_color", colors.green)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
|
--#region Builds
|
||||||
|
|
||||||
|
-- record and publish multiblock RTU build data
|
||||||
|
---@param id integer
|
||||||
|
---@param entry table
|
||||||
|
---@param data_tbl table
|
||||||
|
---@param ps_tbl table
|
||||||
|
---@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)
|
||||||
|
local exists = type(data_tbl[id]) == "table"
|
||||||
|
if exists or create then
|
||||||
|
if not exists then
|
||||||
|
ps_tbl[id] = psil.create()
|
||||||
|
data_tbl[id] = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
data_tbl[id].formed = entry[1] ---@type boolean
|
||||||
|
data_tbl[id].build = entry[2] ---@type table
|
||||||
|
|
||||||
|
ps_tbl[id].publish("formed", entry[1])
|
||||||
|
|
||||||
|
for key, val in pairs(data_tbl[id].build) do ps_tbl[id].publish(key, val) end
|
||||||
|
end
|
||||||
|
|
||||||
|
return exists or (create == true)
|
||||||
|
end
|
||||||
|
|
||||||
-- populate facility structure builds
|
-- populate facility structure builds
|
||||||
---@param build table
|
---@param build table
|
||||||
---@return boolean valid
|
---@return boolean valid
|
||||||
@ -191,21 +312,29 @@ function iocontrol.record_facility_builds(build)
|
|||||||
-- induction matricies
|
-- induction matricies
|
||||||
if type(build.induction) == "table" then
|
if type(build.induction) == "table" then
|
||||||
for id, matrix in pairs(build.induction) do
|
for id, matrix in pairs(build.induction) do
|
||||||
if type(fac.induction_data_tbl[id]) == "table" then
|
if not _record_multiblock_build(id, matrix, fac.induction_data_tbl, fac.induction_ps_tbl) then
|
||||||
fac.induction_data_tbl[id].formed = matrix[1] ---@type boolean
|
|
||||||
fac.induction_data_tbl[id].build = matrix[2] ---@type table
|
|
||||||
|
|
||||||
fac.induction_ps_tbl[id].publish("formed", matrix[1])
|
|
||||||
|
|
||||||
for key, val in pairs(fac.induction_data_tbl[id].build) do
|
|
||||||
fac.induction_ps_tbl[id].publish(key, val)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
log.debug(util.c("iocontrol.record_facility_builds: invalid induction matrix id ", id))
|
log.debug(util.c("iocontrol.record_facility_builds: invalid induction matrix id ", id))
|
||||||
valid = false
|
valid = false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- SPS
|
||||||
|
if type(build.sps) == "table" then
|
||||||
|
for id, sps in pairs(build.sps) do
|
||||||
|
if not _record_multiblock_build(id, sps, fac.sps_data_tbl, fac.sps_ps_tbl) then
|
||||||
|
log.debug(util.c("iocontrol.record_facility_builds: invalid SPS id ", id))
|
||||||
|
valid = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- dynamic tanks
|
||||||
|
if type(build.tanks) == "table" then
|
||||||
|
for id, tank in pairs(build.tanks) do
|
||||||
|
_record_multiblock_build(id, tank, fac.tank_data_tbl, fac.tank_ps_tbl, true)
|
||||||
|
end
|
||||||
|
end
|
||||||
else
|
else
|
||||||
log.debug("facility builds not a table")
|
log.debug("facility builds not a table")
|
||||||
valid = false
|
valid = false
|
||||||
@ -249,16 +378,7 @@ function iocontrol.record_unit_builds(builds)
|
|||||||
-- boiler builds
|
-- boiler builds
|
||||||
if type(build.boilers) == "table" then
|
if type(build.boilers) == "table" then
|
||||||
for b_id, boiler in pairs(build.boilers) do
|
for b_id, boiler in pairs(build.boilers) do
|
||||||
if type(unit.boiler_data_tbl[b_id]) == "table" then
|
if not _record_multiblock_build(b_id, boiler, unit.boiler_data_tbl, unit.boiler_ps_tbl) then
|
||||||
unit.boiler_data_tbl[b_id].formed = boiler[1] ---@type boolean
|
|
||||||
unit.boiler_data_tbl[b_id].build = boiler[2] ---@type table
|
|
||||||
|
|
||||||
unit.boiler_ps_tbl[b_id].publish("formed", boiler[1])
|
|
||||||
|
|
||||||
for key, val in pairs(unit.boiler_data_tbl[b_id].build) do
|
|
||||||
unit.boiler_ps_tbl[b_id].publish(key, val)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
log.debug(util.c(log_header, "invalid boiler id ", b_id))
|
log.debug(util.c(log_header, "invalid boiler id ", b_id))
|
||||||
valid = false
|
valid = false
|
||||||
end
|
end
|
||||||
@ -268,27 +388,49 @@ function iocontrol.record_unit_builds(builds)
|
|||||||
-- turbine builds
|
-- turbine builds
|
||||||
if type(build.turbines) == "table" then
|
if type(build.turbines) == "table" then
|
||||||
for t_id, turbine in pairs(build.turbines) do
|
for t_id, turbine in pairs(build.turbines) do
|
||||||
if type(unit.turbine_data_tbl[t_id]) == "table" then
|
if not _record_multiblock_build(t_id, turbine, unit.turbine_data_tbl, unit.turbine_ps_tbl) then
|
||||||
unit.turbine_data_tbl[t_id].formed = turbine[1] ---@type boolean
|
|
||||||
unit.turbine_data_tbl[t_id].build = turbine[2] ---@type table
|
|
||||||
|
|
||||||
unit.turbine_ps_tbl[t_id].publish("formed", turbine[1])
|
|
||||||
|
|
||||||
for key, val in pairs(unit.turbine_data_tbl[t_id].build) do
|
|
||||||
unit.turbine_ps_tbl[t_id].publish(key, val)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
log.debug(util.c(log_header, "invalid turbine id ", t_id))
|
log.debug(util.c(log_header, "invalid turbine id ", t_id))
|
||||||
valid = false
|
valid = false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- dynamic tank builds
|
||||||
|
if type(build.tanks) == "table" then
|
||||||
|
for d_id, d_tank in pairs(build.tanks) do
|
||||||
|
_record_multiblock_build(d_id, d_tank, unit.tank_data_tbl, unit.tank_ps_tbl, true)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return valid
|
return valid
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
|
--#region Statuses
|
||||||
|
|
||||||
|
-- record and publish multiblock status data
|
||||||
|
---@param entry any
|
||||||
|
---@param data imatrix_session_db|sps_session_db|dynamicv_session_db|turbinev_session_db|boilerv_session_db
|
||||||
|
---@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
|
||||||
|
|
||||||
|
ps.publish("formed", data.formed)
|
||||||
|
ps.publish("faulted", is_faulted)
|
||||||
|
|
||||||
|
for key, val in pairs(data.state) do ps.publish(key, val) end
|
||||||
|
for key, val in pairs(data.tanks) do ps.publish(key, val) end
|
||||||
|
|
||||||
|
return is_faulted
|
||||||
|
end
|
||||||
|
|
||||||
-- update facility status
|
-- update facility status
|
||||||
---@param status table
|
---@param status table
|
||||||
---@return boolean valid
|
---@return boolean valid
|
||||||
@ -306,7 +448,7 @@ function iocontrol.update_facility_status(status)
|
|||||||
|
|
||||||
local ctl_status = status[1]
|
local ctl_status = status[1]
|
||||||
|
|
||||||
if type(ctl_status) == "table" and #ctl_status == 14 then
|
if type(ctl_status) == "table" and #ctl_status == 16 then
|
||||||
fac.all_sys_ok = ctl_status[1]
|
fac.all_sys_ok = ctl_status[1]
|
||||||
fac.auto_ready = ctl_status[2]
|
fac.auto_ready = ctl_status[2]
|
||||||
|
|
||||||
@ -354,6 +496,12 @@ function iocontrol.update_facility_status(status)
|
|||||||
io.units[i].unit_ps.publish("auto_group", names[group_map[i] + 1])
|
io.units[i].unit_ps.publish("auto_group", names[group_map[i] + 1])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
fac.auto_current_waste_product = ctl_status[15]
|
||||||
|
fac.auto_pu_fallback_active = ctl_status[16]
|
||||||
|
|
||||||
|
fac.ps.publish("current_waste_product", fac.auto_current_waste_product)
|
||||||
|
fac.ps.publish("pu_fallback_active", fac.auto_pu_fallback_active)
|
||||||
else
|
else
|
||||||
log.debug(log_header .. "control status not a table or length mismatch")
|
log.debug(log_header .. "control status not a table or length mismatch")
|
||||||
valid = false
|
valid = false
|
||||||
@ -390,36 +538,23 @@ function iocontrol.update_facility_status(status)
|
|||||||
|
|
||||||
for id, matrix in pairs(rtu_statuses.induction) do
|
for id, matrix in pairs(rtu_statuses.induction) do
|
||||||
if type(fac.induction_data_tbl[id]) == "table" then
|
if type(fac.induction_data_tbl[id]) == "table" then
|
||||||
local rtu_faulted = matrix[1] ---@type boolean
|
local data = fac.induction_data_tbl[id] ---@type imatrix_session_db
|
||||||
fac.induction_data_tbl[id].formed = matrix[2] ---@type boolean
|
local ps = fac.induction_ps_tbl[id] ---@type psil
|
||||||
fac.induction_data_tbl[id].state = matrix[3] ---@type table
|
|
||||||
fac.induction_data_tbl[id].tanks = matrix[4] ---@type table
|
|
||||||
|
|
||||||
local data = fac.induction_data_tbl[id] ---@type imatrix_session_db
|
local rtu_faulted = _record_multiblock_status(matrix, data, ps)
|
||||||
|
|
||||||
fac.induction_ps_tbl[id].publish("formed", data.formed)
|
if rtu_faulted then
|
||||||
fac.induction_ps_tbl[id].publish("faulted", rtu_faulted)
|
ps.publish("computed_status", 3) -- faulted
|
||||||
|
elseif data.formed then
|
||||||
if data.formed then
|
if data.tanks.energy_fill >= 0.99 then
|
||||||
if rtu_faulted then
|
ps.publish("computed_status", 6) -- full
|
||||||
fac.induction_ps_tbl[id].publish("computed_status", 3) -- faulted
|
|
||||||
elseif data.tanks.energy_fill >= 0.99 then
|
|
||||||
fac.induction_ps_tbl[id].publish("computed_status", 6) -- full
|
|
||||||
elseif data.tanks.energy_fill <= 0.01 then
|
elseif data.tanks.energy_fill <= 0.01 then
|
||||||
fac.induction_ps_tbl[id].publish("computed_status", 5) -- empty
|
ps.publish("computed_status", 5) -- empty
|
||||||
else
|
else
|
||||||
fac.induction_ps_tbl[id].publish("computed_status", 4) -- on-line
|
ps.publish("computed_status", 4) -- on-line
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
fac.induction_ps_tbl[id].publish("computed_status", 2) -- not formed
|
ps.publish("computed_status", 2) -- not formed
|
||||||
end
|
|
||||||
|
|
||||||
for key, val in pairs(fac.induction_data_tbl[id].state) do
|
|
||||||
fac.induction_ps_tbl[id].publish(key, val)
|
|
||||||
end
|
|
||||||
|
|
||||||
for key, val in pairs(fac.induction_data_tbl[id].tanks) do
|
|
||||||
fac.induction_ps_tbl[id].publish(key, val)
|
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
log.debug(util.c(log_header, "invalid induction matrix id ", id))
|
log.debug(util.c(log_header, "invalid induction matrix id ", id))
|
||||||
@ -430,6 +565,82 @@ function iocontrol.update_facility_status(status)
|
|||||||
valid = false
|
valid = false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- SPS statuses
|
||||||
|
if type(rtu_statuses.sps) == "table" then
|
||||||
|
for id = 1, #fac.sps_ps_tbl do
|
||||||
|
if rtu_statuses.sps[id] == nil then
|
||||||
|
-- disconnected
|
||||||
|
fac.sps_ps_tbl[id].publish("computed_status", 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
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 rtu_faulted = _record_multiblock_status(sps, data, ps)
|
||||||
|
|
||||||
|
if rtu_faulted then
|
||||||
|
ps.publish("computed_status", 3) -- faulted
|
||||||
|
elseif data.formed then
|
||||||
|
if data.state.process_rate > 0 then
|
||||||
|
ps.publish("computed_status", 5) -- active
|
||||||
|
else
|
||||||
|
ps.publish("computed_status", 4) -- idle
|
||||||
|
end
|
||||||
|
else
|
||||||
|
ps.publish("computed_status", 2) -- not formed
|
||||||
|
end
|
||||||
|
|
||||||
|
io.facility.ps.publish("am_rate", data.state.process_rate * 1000)
|
||||||
|
else
|
||||||
|
log.debug(util.c(log_header, "invalid sps id ", id))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
log.debug(log_header .. "sps list not a table")
|
||||||
|
valid = false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- dynamic tank statuses
|
||||||
|
if type(rtu_statuses.tanks) == "table" then
|
||||||
|
for id = 1, #fac.tank_ps_tbl do
|
||||||
|
if rtu_statuses.tanks[id] == nil then
|
||||||
|
-- disconnected
|
||||||
|
fac.tank_ps_tbl[id].publish("computed_status", 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
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 rtu_faulted = _record_multiblock_status(tank, data, ps)
|
||||||
|
|
||||||
|
if rtu_faulted then
|
||||||
|
ps.publish("computed_status", 3) -- faulted
|
||||||
|
elseif data.formed then
|
||||||
|
if data.tanks.fill >= 0.99 then
|
||||||
|
ps.publish("computed_status", 6) -- full
|
||||||
|
elseif data.tanks.fill < 0.20 then
|
||||||
|
ps.publish("computed_status", 5) -- low
|
||||||
|
else
|
||||||
|
ps.publish("computed_status", 4) -- on-line
|
||||||
|
end
|
||||||
|
else
|
||||||
|
ps.publish("computed_status", 2) -- not formed
|
||||||
|
end
|
||||||
|
else
|
||||||
|
log.debug(util.c(log_header, "invalid dynamic tank id ", id))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
log.debug(log_header .. "dyanmic tank list not a table")
|
||||||
|
valid = false
|
||||||
|
end
|
||||||
|
|
||||||
-- environment detector status
|
-- environment detector status
|
||||||
if type(rtu_statuses.rad_mon) == "table" then
|
if type(rtu_statuses.rad_mon) == "table" then
|
||||||
if #rtu_statuses.rad_mon > 0 then
|
if #rtu_statuses.rad_mon > 0 then
|
||||||
@ -472,6 +683,9 @@ function iocontrol.update_unit_statuses(statuses)
|
|||||||
valid = false
|
valid = false
|
||||||
else
|
else
|
||||||
local burn_rate_sum = 0.0
|
local burn_rate_sum = 0.0
|
||||||
|
local sna_count_sum = 0
|
||||||
|
local pu_rate = 0.0
|
||||||
|
local po_rate = 0.0
|
||||||
|
|
||||||
-- get all unit statuses
|
-- get all unit statuses
|
||||||
for i = 1, #statuses do
|
for i = 1, #statuses do
|
||||||
@ -480,6 +694,8 @@ function iocontrol.update_unit_statuses(statuses)
|
|||||||
local unit = io.units[i] ---@type ioctl_unit
|
local unit = io.units[i] ---@type ioctl_unit
|
||||||
local status = statuses[i]
|
local status = statuses[i]
|
||||||
|
|
||||||
|
local burn_rate = 0.0
|
||||||
|
|
||||||
if type(status) ~= "table" or #status ~= 5 then
|
if type(status) ~= "table" or #status ~= 5 then
|
||||||
log.debug(log_header .. "invalid status entry in unit statuses (not a table or invalid length)")
|
log.debug(log_header .. "invalid status entry in unit statuses (not a table or invalid length)")
|
||||||
valid = false
|
valid = false
|
||||||
@ -515,7 +731,8 @@ function iocontrol.update_unit_statuses(statuses)
|
|||||||
|
|
||||||
-- if status hasn't been received, mek_status = {}
|
-- if status hasn't been received, mek_status = {}
|
||||||
if type(unit.reactor_data.mek_status.act_burn_rate) == "number" then
|
if type(unit.reactor_data.mek_status.act_burn_rate) == "number" then
|
||||||
burn_rate_sum = burn_rate_sum + unit.reactor_data.mek_status.act_burn_rate
|
burn_rate = unit.reactor_data.mek_status.act_burn_rate
|
||||||
|
burn_rate_sum = burn_rate_sum + burn_rate
|
||||||
end
|
end
|
||||||
|
|
||||||
if unit.reactor_data.mek_status.status then
|
if unit.reactor_data.mek_status.status then
|
||||||
@ -571,34 +788,21 @@ function iocontrol.update_unit_statuses(statuses)
|
|||||||
|
|
||||||
for id, boiler in pairs(rtu_statuses.boilers) do
|
for id, boiler in pairs(rtu_statuses.boilers) do
|
||||||
if type(unit.boiler_data_tbl[id]) == "table" then
|
if type(unit.boiler_data_tbl[id]) == "table" then
|
||||||
local rtu_faulted = boiler[1] ---@type boolean
|
local data = unit.boiler_data_tbl[id] ---@type boilerv_session_db
|
||||||
unit.boiler_data_tbl[id].formed = boiler[2] ---@type boolean
|
local ps = unit.boiler_ps_tbl[id] ---@type psil
|
||||||
unit.boiler_data_tbl[id].state = boiler[3] ---@type table
|
|
||||||
unit.boiler_data_tbl[id].tanks = boiler[4] ---@type table
|
|
||||||
|
|
||||||
local data = unit.boiler_data_tbl[id] ---@type boilerv_session_db
|
local rtu_faulted = _record_multiblock_status(boiler, data, ps)
|
||||||
|
|
||||||
unit.boiler_ps_tbl[id].publish("formed", data.formed)
|
|
||||||
unit.boiler_ps_tbl[id].publish("faulted", rtu_faulted)
|
|
||||||
|
|
||||||
if rtu_faulted then
|
if rtu_faulted then
|
||||||
unit.boiler_ps_tbl[id].publish("computed_status", 3) -- faulted
|
ps.publish("computed_status", 3) -- faulted
|
||||||
elseif data.formed then
|
elseif data.formed then
|
||||||
if data.state.boil_rate > 0 then
|
if data.state.boil_rate > 0 then
|
||||||
unit.boiler_ps_tbl[id].publish("computed_status", 5) -- active
|
ps.publish("computed_status", 5) -- active
|
||||||
else
|
else
|
||||||
unit.boiler_ps_tbl[id].publish("computed_status", 4) -- idle
|
ps.publish("computed_status", 4) -- idle
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
unit.boiler_ps_tbl[id].publish("computed_status", 2) -- not formed
|
ps.publish("computed_status", 2) -- not formed
|
||||||
end
|
|
||||||
|
|
||||||
for key, val in pairs(unit.boiler_data_tbl[id].state) do
|
|
||||||
unit.boiler_ps_tbl[id].publish(key, val)
|
|
||||||
end
|
|
||||||
|
|
||||||
for key, val in pairs(unit.boiler_data_tbl[id].tanks) do
|
|
||||||
unit.boiler_ps_tbl[id].publish(key, val)
|
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
log.debug(util.c(log_header, "invalid boiler id ", id))
|
log.debug(util.c(log_header, "invalid boiler id ", id))
|
||||||
@ -621,36 +825,23 @@ function iocontrol.update_unit_statuses(statuses)
|
|||||||
|
|
||||||
for id, turbine in pairs(rtu_statuses.turbines) do
|
for id, turbine in pairs(rtu_statuses.turbines) do
|
||||||
if type(unit.turbine_data_tbl[id]) == "table" then
|
if type(unit.turbine_data_tbl[id]) == "table" then
|
||||||
local rtu_faulted = turbine[1] ---@type boolean
|
|
||||||
unit.turbine_data_tbl[id].formed = turbine[2] ---@type boolean
|
|
||||||
unit.turbine_data_tbl[id].state = turbine[3] ---@type table
|
|
||||||
unit.turbine_data_tbl[id].tanks = turbine[4] ---@type table
|
|
||||||
|
|
||||||
local data = unit.turbine_data_tbl[id] ---@type turbinev_session_db
|
local data = unit.turbine_data_tbl[id] ---@type turbinev_session_db
|
||||||
|
local ps = unit.turbine_ps_tbl[id] ---@type psil
|
||||||
|
|
||||||
unit.turbine_ps_tbl[id].publish("formed", data.formed)
|
local rtu_faulted = _record_multiblock_status(turbine, data, ps)
|
||||||
unit.turbine_ps_tbl[id].publish("faulted", rtu_faulted)
|
|
||||||
|
|
||||||
if rtu_faulted then
|
if rtu_faulted then
|
||||||
unit.turbine_ps_tbl[id].publish("computed_status", 3) -- faulted
|
ps.publish("computed_status", 3) -- faulted
|
||||||
elseif data.formed then
|
elseif data.formed then
|
||||||
if data.tanks.energy_fill >= 0.99 then
|
if data.tanks.energy_fill >= 0.99 then
|
||||||
unit.turbine_ps_tbl[id].publish("computed_status", 6) -- trip
|
ps.publish("computed_status", 6) -- trip
|
||||||
elseif data.state.flow_rate < 100 then
|
elseif data.state.flow_rate < 100 then
|
||||||
unit.turbine_ps_tbl[id].publish("computed_status", 4) -- idle
|
ps.publish("computed_status", 4) -- idle
|
||||||
else
|
else
|
||||||
unit.turbine_ps_tbl[id].publish("computed_status", 5) -- active
|
ps.publish("computed_status", 5) -- active
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
unit.turbine_ps_tbl[id].publish("computed_status", 2) -- not formed
|
ps.publish("computed_status", 2) -- not formed
|
||||||
end
|
|
||||||
|
|
||||||
for key, val in pairs(unit.turbine_data_tbl[id].state) do
|
|
||||||
unit.turbine_ps_tbl[id].publish(key, val)
|
|
||||||
end
|
|
||||||
|
|
||||||
for key, val in pairs(unit.turbine_data_tbl[id].tanks) do
|
|
||||||
unit.turbine_ps_tbl[id].publish(key, val)
|
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
log.debug(util.c(log_header, "invalid turbine id ", id))
|
log.debug(util.c(log_header, "invalid turbine id ", id))
|
||||||
@ -662,6 +853,58 @@ function iocontrol.update_unit_statuses(statuses)
|
|||||||
valid = false
|
valid = false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- dynamic tank statuses
|
||||||
|
if type(rtu_statuses.tanks) == "table" then
|
||||||
|
for id = 1, #unit.tank_ps_tbl do
|
||||||
|
if rtu_statuses.tanks[i] == nil then
|
||||||
|
-- disconnected
|
||||||
|
unit.tank_ps_tbl[id].publish("computed_status", 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
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 rtu_faulted = _record_multiblock_status(tank, data, ps)
|
||||||
|
|
||||||
|
if rtu_faulted then
|
||||||
|
ps.publish("computed_status", 3) -- faulted
|
||||||
|
elseif data.formed then
|
||||||
|
if data.tanks.fill >= 0.99 then
|
||||||
|
ps.publish("computed_status", 6) -- full
|
||||||
|
elseif data.tanks.fill < 0.20 then
|
||||||
|
ps.publish("computed_status", 5) -- low
|
||||||
|
else
|
||||||
|
ps.publish("computed_status", 5) -- active
|
||||||
|
end
|
||||||
|
else
|
||||||
|
ps.publish("computed_status", 2) -- not formed
|
||||||
|
end
|
||||||
|
else
|
||||||
|
log.debug(util.c(log_header, "invalid dynamic tank id ", id))
|
||||||
|
valid = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
log.debug(log_header .. "dynamic tank list not a table")
|
||||||
|
valid = false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- solar neutron activator status info
|
||||||
|
if type(rtu_statuses.sna) == "table" then
|
||||||
|
unit.num_snas = rtu_statuses.sna[1] ---@type integer
|
||||||
|
unit.sna_prod_rate = rtu_statuses.sna[2] ---@type number
|
||||||
|
|
||||||
|
unit.unit_ps.publish("sna_prod_rate", unit.sna_prod_rate)
|
||||||
|
|
||||||
|
sna_count_sum = sna_count_sum + unit.num_snas
|
||||||
|
else
|
||||||
|
log.debug(log_header .. "sna statistic list not a table")
|
||||||
|
valid = false
|
||||||
|
end
|
||||||
|
|
||||||
-- environment detector status
|
-- environment detector status
|
||||||
if type(rtu_statuses.rad_mon) == "table" then
|
if type(rtu_statuses.rad_mon) == "table" then
|
||||||
if #rtu_statuses.rad_mon > 0 then
|
if #rtu_statuses.rad_mon > 0 then
|
||||||
@ -739,12 +982,17 @@ function iocontrol.update_unit_statuses(statuses)
|
|||||||
local unit_state = status[5]
|
local unit_state = status[5]
|
||||||
|
|
||||||
if type(unit_state) == "table" then
|
if type(unit_state) == "table" then
|
||||||
if #unit_state == 5 then
|
if #unit_state == 6 then
|
||||||
|
unit.waste_mode = unit_state[5]
|
||||||
|
unit.waste_product = unit_state[6]
|
||||||
|
|
||||||
unit.unit_ps.publish("U_StatusLine1", unit_state[1])
|
unit.unit_ps.publish("U_StatusLine1", unit_state[1])
|
||||||
unit.unit_ps.publish("U_StatusLine2", unit_state[2])
|
unit.unit_ps.publish("U_StatusLine2", unit_state[2])
|
||||||
unit.unit_ps.publish("U_WasteMode", unit_state[3])
|
unit.unit_ps.publish("U_AutoReady", unit_state[3])
|
||||||
unit.unit_ps.publish("U_AutoReady", unit_state[4])
|
unit.unit_ps.publish("U_AutoDegraded", unit_state[4])
|
||||||
unit.unit_ps.publish("U_AutoDegraded", unit_state[5])
|
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)
|
||||||
else
|
else
|
||||||
log.debug(log_header .. "unit state length mismatch")
|
log.debug(log_header .. "unit state length mismatch")
|
||||||
valid = false
|
valid = false
|
||||||
@ -753,10 +1001,18 @@ function iocontrol.update_unit_statuses(statuses)
|
|||||||
log.debug(log_header .. "unit state not a table")
|
log.debug(log_header .. "unit state not a table")
|
||||||
valid = false
|
valid = false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- determine waste production for this unit, add to statistics
|
||||||
|
local is_pu = unit.waste_product == types.WASTE_PRODUCT.PLUTONIUM
|
||||||
|
pu_rate = pu_rate + util.trinary(is_pu, burn_rate / 10.0, 0.0)
|
||||||
|
po_rate = po_rate + util.trinary(not is_pu, math.min(burn_rate / 10.0, unit.sna_prod_rate), 0.0)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
io.facility.ps.publish("burn_sum", burn_rate_sum)
|
io.facility.ps.publish("burn_sum", burn_rate_sum)
|
||||||
|
io.facility.ps.publish("sna_count", sna_count_sum)
|
||||||
|
io.facility.ps.publish("pu_rate", pu_rate)
|
||||||
|
io.facility.ps.publish("po_rate", po_rate)
|
||||||
|
|
||||||
-- update alarm sounder
|
-- update alarm sounder
|
||||||
sounder.eval(io.units)
|
sounder.eval(io.units)
|
||||||
@ -765,6 +1021,8 @@ function iocontrol.update_unit_statuses(statuses)
|
|||||||
return valid
|
return valid
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
-- get the IO controller database
|
-- get the IO controller database
|
||||||
function iocontrol.get_db() return io end
|
function iocontrol.get_db() return io end
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@ local FAC_COMMAND = comms.FAC_COMMAND
|
|||||||
local UNIT_COMMAND = comms.UNIT_COMMAND
|
local UNIT_COMMAND = comms.UNIT_COMMAND
|
||||||
|
|
||||||
local PROCESS = types.PROCESS
|
local PROCESS = types.PROCESS
|
||||||
|
local PRODUCT = types.WASTE_PRODUCT
|
||||||
|
|
||||||
---@class process_controller
|
---@class process_controller
|
||||||
local process = {}
|
local process = {}
|
||||||
@ -24,7 +25,9 @@ local self = {
|
|||||||
burn_target = 0.0,
|
burn_target = 0.0,
|
||||||
charge_target = 0.0,
|
charge_target = 0.0,
|
||||||
gen_target = 0.0,
|
gen_target = 0.0,
|
||||||
limits = {}
|
limits = {},
|
||||||
|
waste_product = PRODUCT.PLUTONIUM,
|
||||||
|
pu_fallback = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,19 +51,23 @@ function process.init(iocontrol, coord_comms)
|
|||||||
log.error("process.init(): failed to load coordinator settings file")
|
log.error("process.init(): failed to load coordinator settings file")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- facility auto control configuration
|
||||||
local config = settings.get("PROCESS") ---@type coord_auto_config|nil
|
local config = settings.get("PROCESS") ---@type coord_auto_config|nil
|
||||||
|
|
||||||
if type(config) == "table" then
|
if type(config) == "table" then
|
||||||
self.config.mode = config.mode
|
self.config.mode = config.mode
|
||||||
self.config.burn_target = config.burn_target
|
self.config.burn_target = config.burn_target
|
||||||
self.config.charge_target = config.charge_target
|
self.config.charge_target = config.charge_target
|
||||||
self.config.gen_target = config.gen_target
|
self.config.gen_target = config.gen_target
|
||||||
self.config.limits = config.limits
|
self.config.limits = config.limits
|
||||||
|
self.config.waste_product = config.waste_product
|
||||||
|
self.config.pu_fallback = config.pu_fallback
|
||||||
|
|
||||||
self.io.facility.ps.publish("process_mode", self.config.mode)
|
self.io.facility.ps.publish("process_mode", self.config.mode)
|
||||||
self.io.facility.ps.publish("process_burn_target", self.config.burn_target)
|
self.io.facility.ps.publish("process_burn_target", self.config.burn_target)
|
||||||
self.io.facility.ps.publish("process_charge_target", self.config.charge_target)
|
self.io.facility.ps.publish("process_charge_target", self.config.charge_target)
|
||||||
self.io.facility.ps.publish("process_gen_target", self.config.gen_target)
|
self.io.facility.ps.publish("process_gen_target", self.config.gen_target)
|
||||||
|
self.io.facility.ps.publish("process_waste_product", self.config.waste_product)
|
||||||
|
self.io.facility.ps.publish("process_pu_fallback", self.config.pu_fallback)
|
||||||
|
|
||||||
for id = 1, math.min(#self.config.limits, self.io.facility.num_units) do
|
for id = 1, math.min(#self.config.limits, self.io.facility.num_units) do
|
||||||
local unit = self.io.units[id] ---@type ioctl_unit
|
local unit = self.io.units[id] ---@type ioctl_unit
|
||||||
@ -70,18 +77,18 @@ function process.init(iocontrol, coord_comms)
|
|||||||
log.info("PROCESS: loaded auto control settings from coord.settings")
|
log.info("PROCESS: loaded auto control settings from coord.settings")
|
||||||
end
|
end
|
||||||
|
|
||||||
local waste_mode = settings.get("WASTE_MODES") ---@type table|nil
|
-- unit waste states
|
||||||
|
local waste_modes = settings.get("WASTE_MODES") ---@type table|nil
|
||||||
if type(waste_mode) == "table" then
|
if type(waste_modes) == "table" then
|
||||||
for id, mode in pairs(waste_mode) do
|
for id, mode in pairs(waste_modes) do
|
||||||
self.comms.send_unit_command(UNIT_COMMAND.SET_WASTE, id, mode)
|
self.comms.send_unit_command(UNIT_COMMAND.SET_WASTE, id, mode)
|
||||||
end
|
end
|
||||||
|
|
||||||
log.info("PROCESS: loaded waste mode settings from coord.settings")
|
log.info("PROCESS: loaded unit waste mode settings from coord.settings")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- unit priority groups
|
||||||
local prio_groups = settings.get("PRIORITY_GROUPS") ---@type table|nil
|
local prio_groups = settings.get("PRIORITY_GROUPS") ---@type table|nil
|
||||||
|
|
||||||
if type(prio_groups) == "table" then
|
if type(prio_groups) == "table" then
|
||||||
for id, group in pairs(prio_groups) do
|
for id, group in pairs(prio_groups) do
|
||||||
self.comms.send_unit_command(UNIT_COMMAND.SET_GROUP, id, group)
|
self.comms.send_unit_command(UNIT_COMMAND.SET_GROUP, id, group)
|
||||||
@ -137,7 +144,7 @@ end
|
|||||||
-- set waste mode
|
-- set waste mode
|
||||||
---@param id integer unit ID
|
---@param id integer unit ID
|
||||||
---@param mode integer waste mode
|
---@param mode integer waste mode
|
||||||
function process.set_waste(id, mode)
|
function process.set_unit_waste(id, mode)
|
||||||
-- publish so that if it fails then it gets reset
|
-- publish so that if it fails then it gets reset
|
||||||
self.io.units[id].unit_ps.publish("U_WasteMode", mode)
|
self.io.units[id].unit_ps.publish("U_WasteMode", mode)
|
||||||
|
|
||||||
@ -153,7 +160,7 @@ function process.set_waste(id, mode)
|
|||||||
settings.set("WASTE_MODES", waste_mode)
|
settings.set("WASTE_MODES", waste_mode)
|
||||||
|
|
||||||
if not settings.save("/coord.settings") then
|
if not settings.save("/coord.settings") then
|
||||||
log.error("process.set_waste(): failed to save coordinator settings file")
|
log.error("process.set_unit_waste(): failed to save coordinator settings file")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -204,6 +211,24 @@ end
|
|||||||
-- AUTO PROCESS CONTROL --
|
-- AUTO PROCESS CONTROL --
|
||||||
--------------------------
|
--------------------------
|
||||||
|
|
||||||
|
-- write auto process control to config file
|
||||||
|
local function _write_auto_config()
|
||||||
|
-- attempt to load settings
|
||||||
|
if not settings.load("/coord.settings") then
|
||||||
|
log.warning("process._write_auto_config(): failed to load coordinator settings file")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- save config
|
||||||
|
settings.set("PROCESS", self.config)
|
||||||
|
local saved = settings.save("/coord.settings")
|
||||||
|
|
||||||
|
if not saved then
|
||||||
|
log.warning("process._write_auto_config(): failed to save coordinator settings file")
|
||||||
|
end
|
||||||
|
|
||||||
|
return not not saved
|
||||||
|
end
|
||||||
|
|
||||||
-- stop automatic process control
|
-- stop automatic process control
|
||||||
function process.stop_auto()
|
function process.stop_auto()
|
||||||
self.comms.send_fac_command(FAC_COMMAND.STOP)
|
self.comms.send_fac_command(FAC_COMMAND.STOP)
|
||||||
@ -216,6 +241,30 @@ function process.start_auto()
|
|||||||
log.debug("PROCESS: START AUTO CTL")
|
log.debug("PROCESS: START AUTO CTL")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- set automatic process control waste mode
|
||||||
|
---@param product WASTE_PRODUCT waste product for auto control
|
||||||
|
function process.set_process_waste(product)
|
||||||
|
self.comms.send_fac_command(FAC_COMMAND.SET_WASTE_MODE, product)
|
||||||
|
|
||||||
|
log.debug(util.c("PROCESS: SET WASTE ", product))
|
||||||
|
|
||||||
|
-- update config table and save
|
||||||
|
self.config.waste_product = product
|
||||||
|
_write_auto_config()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- set automatic process control plutonium fallback
|
||||||
|
---@param enabled boolean whether to enable plutonium fallback
|
||||||
|
function process.set_pu_fallback(enabled)
|
||||||
|
self.comms.send_fac_command(FAC_COMMAND.SET_PU_FB, enabled)
|
||||||
|
|
||||||
|
log.debug(util.c("PROCESS: SET PU FALLBACK ", enabled))
|
||||||
|
|
||||||
|
-- update config table and save
|
||||||
|
self.config.pu_fallback = enabled
|
||||||
|
_write_auto_config()
|
||||||
|
end
|
||||||
|
|
||||||
-- save process control settings
|
-- save process control settings
|
||||||
---@param mode PROCESS control mode
|
---@param mode PROCESS control mode
|
||||||
---@param burn_target number burn rate target
|
---@param burn_target number burn rate target
|
||||||
@ -223,29 +272,17 @@ end
|
|||||||
---@param gen_target number generation rate target
|
---@param gen_target number generation rate target
|
||||||
---@param limits table unit burn rate limits
|
---@param limits table unit burn rate limits
|
||||||
function process.save(mode, burn_target, charge_target, gen_target, limits)
|
function process.save(mode, burn_target, charge_target, gen_target, limits)
|
||||||
-- attempt to load settings
|
log.debug("PROCESS: SAVE")
|
||||||
if not settings.load("/coord.settings") then
|
|
||||||
log.warning("process.save(): failed to load coordinator settings file")
|
|
||||||
end
|
|
||||||
|
|
||||||
-- config table
|
-- update config table
|
||||||
self.config = {
|
self.config.mode = mode
|
||||||
mode = mode,
|
self.config.burn_target = burn_target
|
||||||
burn_target = burn_target,
|
self.config.charge_target = charge_target
|
||||||
charge_target = charge_target,
|
self.config.gen_target = gen_target
|
||||||
gen_target = gen_target,
|
self.config.limits = limits
|
||||||
limits = limits
|
|
||||||
}
|
|
||||||
|
|
||||||
-- save config
|
-- save config
|
||||||
settings.set("PROCESS", self.config)
|
self.io.facility.save_cfg_ack(_write_auto_config())
|
||||||
local saved = settings.save("/coord.settings")
|
|
||||||
|
|
||||||
if not saved then
|
|
||||||
log.warning("process.save(): failed to save coordinator settings file")
|
|
||||||
end
|
|
||||||
|
|
||||||
self.io.facility.save_cfg_ack(saved)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- handle a start command acknowledgement
|
-- handle a start command acknowledgement
|
||||||
@ -258,16 +295,33 @@ function process.start_ack_handle(response)
|
|||||||
self.config.charge_target = response[4]
|
self.config.charge_target = response[4]
|
||||||
self.config.gen_target = response[5]
|
self.config.gen_target = response[5]
|
||||||
|
|
||||||
for i = 1, #response[6] do
|
for i = 1, math.min(#response[6], self.io.facility.num_units) do
|
||||||
self.config.limits[i] = response[6][i]
|
self.config.limits[i] = response[6][i]
|
||||||
|
|
||||||
|
local unit = self.io.units[i] ---@type ioctl_unit
|
||||||
|
unit.unit_ps.publish("burn_limit", self.config.limits[i])
|
||||||
end
|
end
|
||||||
|
|
||||||
self.io.facility.ps.publish("auto_mode", self.config.mode)
|
self.io.facility.ps.publish("process_mode", self.config.mode)
|
||||||
self.io.facility.ps.publish("burn_target", self.config.burn_target)
|
self.io.facility.ps.publish("process_burn_target", self.config.burn_target)
|
||||||
self.io.facility.ps.publish("charge_target", self.config.charge_target)
|
self.io.facility.ps.publish("process_charge_target", self.config.charge_target)
|
||||||
self.io.facility.ps.publish("gen_target", self.config.gen_target)
|
self.io.facility.ps.publish("process_gen_target", self.config.gen_target)
|
||||||
|
|
||||||
self.io.facility.start_ack(ack)
|
self.io.facility.start_ack(ack)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- record waste product state after attempting to change it
|
||||||
|
---@param response WASTE_PRODUCT supervisor waste product state
|
||||||
|
function process.waste_ack_handle(response)
|
||||||
|
self.config.waste_product = response
|
||||||
|
self.io.facility.ps.publish("process_waste_product", response)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- record plutonium fallback state after attempting to change it
|
||||||
|
---@param response boolean supervisor plutonium fallback state
|
||||||
|
function process.pu_fb_ack_handle(response)
|
||||||
|
self.config.pu_fallback = response
|
||||||
|
self.io.facility.ps.publish("process_pu_fallback", response)
|
||||||
|
end
|
||||||
|
|
||||||
return process
|
return process
|
||||||
|
@ -5,8 +5,12 @@
|
|||||||
local log = require("scada-common.log")
|
local log = require("scada-common.log")
|
||||||
local util = require("scada-common.util")
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
local style = require("coordinator.ui.style")
|
local iocontrol = require("coordinator.iocontrol")
|
||||||
|
|
||||||
|
local style = require("coordinator.ui.style")
|
||||||
|
local pgi = require("coordinator.ui.pgi")
|
||||||
|
|
||||||
|
local panel_view = require("coordinator.ui.layout.front_panel")
|
||||||
local main_view = require("coordinator.ui.layout.main_view")
|
local main_view = require("coordinator.ui.layout.main_view")
|
||||||
local unit_view = require("coordinator.ui.layout.unit_view")
|
local unit_view = require("coordinator.ui.layout.unit_view")
|
||||||
|
|
||||||
@ -21,7 +25,9 @@ local engine = {
|
|||||||
monitors = nil, ---@type monitors_struct|nil
|
monitors = nil, ---@type monitors_struct|nil
|
||||||
dmesg_window = nil, ---@type table|nil
|
dmesg_window = nil, ---@type table|nil
|
||||||
ui_ready = false,
|
ui_ready = false,
|
||||||
|
fp_ready = false,
|
||||||
ui = {
|
ui = {
|
||||||
|
front_panel = nil, ---@type graphics_element|nil
|
||||||
main_display = nil, ---@type graphics_element|nil
|
main_display = nil, ---@type graphics_element|nil
|
||||||
unit_displays = {}
|
unit_displays = {}
|
||||||
}
|
}
|
||||||
@ -46,24 +52,10 @@ end
|
|||||||
---@param monitors monitors_struct
|
---@param monitors monitors_struct
|
||||||
function renderer.set_displays(monitors)
|
function renderer.set_displays(monitors)
|
||||||
engine.monitors = monitors
|
engine.monitors = monitors
|
||||||
end
|
|
||||||
|
|
||||||
-- check if the renderer is configured to use a given monitor peripheral
|
-- report to front panel as connected
|
||||||
---@nodiscard
|
iocontrol.fp_monitor_state(0, true)
|
||||||
---@param periph table peripheral
|
for i = 1, #engine.monitors.unit_displays do iocontrol.fp_monitor_state(i, true) end
|
||||||
---@return boolean is_used
|
|
||||||
function renderer.is_monitor_used(periph)
|
|
||||||
if engine.monitors ~= nil then
|
|
||||||
if engine.monitors.primary == periph then
|
|
||||||
return true
|
|
||||||
else
|
|
||||||
for _, monitor in ipairs(engine.monitors.unit_displays) do
|
|
||||||
if monitor == periph then return true end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return false
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- init all displays in use by the renderer
|
-- init all displays in use by the renderer
|
||||||
@ -75,6 +67,17 @@ function renderer.init_displays()
|
|||||||
for _, monitor in ipairs(engine.monitors.unit_displays) do
|
for _, monitor in ipairs(engine.monitors.unit_displays) do
|
||||||
_init_display(monitor)
|
_init_display(monitor)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- init terminal
|
||||||
|
term.setTextColor(colors.white)
|
||||||
|
term.setBackgroundColor(colors.black)
|
||||||
|
term.clear()
|
||||||
|
term.setCursorPos(1, 1)
|
||||||
|
|
||||||
|
-- set overridden colors
|
||||||
|
for i = 1, #style.fp.colors do
|
||||||
|
term.setPaletteColor(style.fp.colors[i].c, style.fp.colors[i].hex)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- check main display width
|
-- check main display width
|
||||||
@ -109,6 +112,51 @@ function renderer.init_dmesg()
|
|||||||
log.direct_dmesg(engine.dmesg_window)
|
log.direct_dmesg(engine.dmesg_window)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- start the coordinator front panel
|
||||||
|
function renderer.start_fp()
|
||||||
|
if not engine.fp_ready then
|
||||||
|
-- show front panel view on terminal
|
||||||
|
engine.ui.front_panel = DisplayBox{window=term.native(),fg_bg=style.fp.root}
|
||||||
|
panel_view(engine.ui.front_panel, #engine.monitors.unit_displays)
|
||||||
|
|
||||||
|
-- start flasher callback task
|
||||||
|
flasher.run()
|
||||||
|
|
||||||
|
-- report front panel as ready
|
||||||
|
engine.fp_ready = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- close out the front panel
|
||||||
|
function renderer.close_fp()
|
||||||
|
if engine.fp_ready then
|
||||||
|
if not engine.ui_ready then
|
||||||
|
-- stop blinking indicators
|
||||||
|
flasher.clear()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- disable PGI
|
||||||
|
pgi.unlink()
|
||||||
|
|
||||||
|
-- hide to stop animation callbacks and clear root UI elements
|
||||||
|
engine.ui.front_panel.hide()
|
||||||
|
engine.ui.front_panel = nil
|
||||||
|
engine.fp_ready = false
|
||||||
|
|
||||||
|
-- restore colors
|
||||||
|
for i = 1, #style.colors do
|
||||||
|
local r, g, b = term.nativePaletteColor(style.colors[i].c)
|
||||||
|
term.setPaletteColor(style.colors[i].c, r, g, b)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- reset terminal
|
||||||
|
term.setTextColor(colors.white)
|
||||||
|
term.setBackgroundColor(colors.black)
|
||||||
|
term.clear()
|
||||||
|
term.setCursorPos(1, 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- start the coordinator GUI
|
-- start the coordinator GUI
|
||||||
function renderer.start_ui()
|
function renderer.start_ui()
|
||||||
if not engine.ui_ready then
|
if not engine.ui_ready then
|
||||||
@ -116,13 +164,15 @@ function renderer.start_ui()
|
|||||||
engine.dmesg_window.setVisible(false)
|
engine.dmesg_window.setVisible(false)
|
||||||
|
|
||||||
-- show main view on main monitor
|
-- show main view on main monitor
|
||||||
engine.ui.main_display = DisplayBox{window=engine.monitors.primary,fg_bg=style.root}
|
if engine.monitors.primary ~= nil then
|
||||||
main_view(engine.ui.main_display)
|
engine.ui.main_display = DisplayBox{window=engine.monitors.primary,fg_bg=style.root}
|
||||||
|
main_view(engine.ui.main_display)
|
||||||
|
end
|
||||||
|
|
||||||
-- show unit views on unit displays
|
-- show unit views on unit displays
|
||||||
for i = 1, #engine.monitors.unit_displays do
|
for idx, display in pairs(engine.monitors.unit_displays) do
|
||||||
engine.ui.unit_displays[i] = DisplayBox{window=engine.monitors.unit_displays[i],fg_bg=style.root}
|
engine.ui.unit_displays[idx] = DisplayBox{window=display,fg_bg=style.root}
|
||||||
unit_view(engine.ui.unit_displays[i], i)
|
unit_view(engine.ui.unit_displays[idx], idx)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- start flasher callback task
|
-- start flasher callback task
|
||||||
@ -135,12 +185,14 @@ end
|
|||||||
|
|
||||||
-- close out the UI
|
-- close out the UI
|
||||||
function renderer.close_ui()
|
function renderer.close_ui()
|
||||||
-- stop blinking indicators
|
if not engine.fp_ready then
|
||||||
flasher.clear()
|
-- stop blinking indicators
|
||||||
|
flasher.clear()
|
||||||
|
end
|
||||||
|
|
||||||
-- delete element trees
|
-- delete element trees
|
||||||
if engine.ui.main_display ~= nil then engine.ui.main_display.delete() end
|
if engine.ui.main_display ~= nil then engine.ui.main_display.delete() end
|
||||||
for _, display in ipairs(engine.ui.unit_displays) do display.delete() end
|
for _, display in pairs(engine.ui.unit_displays) do display.delete() end
|
||||||
|
|
||||||
-- report ui as not ready
|
-- report ui as not ready
|
||||||
engine.ui_ready = false
|
engine.ui_ready = false
|
||||||
@ -157,22 +209,121 @@ function renderer.close_ui()
|
|||||||
engine.dmesg_window.redraw()
|
engine.dmesg_window.redraw()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- is the front panel ready?
|
||||||
|
---@nodiscard
|
||||||
|
---@return boolean ready
|
||||||
|
function renderer.fp_ready() return engine.fp_ready end
|
||||||
|
|
||||||
-- is the UI ready?
|
-- is the UI ready?
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
---@return boolean ready
|
---@return boolean ready
|
||||||
function renderer.ui_ready() return engine.ui_ready end
|
function renderer.ui_ready() return engine.ui_ready end
|
||||||
|
|
||||||
|
-- handle a monitor peripheral being disconnected
|
||||||
|
---@param device table monitor
|
||||||
|
---@return boolean is_used if the monitor is one of the configured monitors
|
||||||
|
function renderer.handle_disconnect(device)
|
||||||
|
local is_used = false
|
||||||
|
|
||||||
|
if engine.monitors ~= nil then
|
||||||
|
if engine.monitors.primary == device then
|
||||||
|
if engine.ui.main_display ~= nil then
|
||||||
|
-- delete element tree and clear root UI elements
|
||||||
|
engine.ui.main_display.delete()
|
||||||
|
end
|
||||||
|
|
||||||
|
is_used = true
|
||||||
|
engine.monitors.primary = nil
|
||||||
|
engine.ui.main_display = nil
|
||||||
|
|
||||||
|
iocontrol.fp_monitor_state(0, false)
|
||||||
|
else
|
||||||
|
for idx, monitor in pairs(engine.monitors.unit_displays) do
|
||||||
|
if monitor == device then
|
||||||
|
if engine.ui.unit_displays[idx] ~= nil then
|
||||||
|
engine.ui.unit_displays[idx].delete()
|
||||||
|
end
|
||||||
|
|
||||||
|
is_used = true
|
||||||
|
engine.monitors.unit_displays[idx] = nil
|
||||||
|
engine.ui.unit_displays[idx] = nil
|
||||||
|
|
||||||
|
iocontrol.fp_monitor_state(idx, false)
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return is_used
|
||||||
|
end
|
||||||
|
|
||||||
|
-- handle a monitor peripheral being reconnected
|
||||||
|
---@param name string monitor name
|
||||||
|
---@param device table monitor
|
||||||
|
---@return boolean is_used if the monitor is one of the configured monitors
|
||||||
|
function renderer.handle_reconnect(name, device)
|
||||||
|
local is_used = false
|
||||||
|
|
||||||
|
if engine.monitors ~= nil then
|
||||||
|
if engine.monitors.primary_name == name then
|
||||||
|
is_used = true
|
||||||
|
_init_display(device)
|
||||||
|
engine.monitors.primary = device
|
||||||
|
|
||||||
|
local disp_x, disp_y = engine.monitors.primary.getSize()
|
||||||
|
engine.dmesg_window.reposition(1, 1, disp_x, disp_y, engine.monitors.primary)
|
||||||
|
|
||||||
|
if engine.ui_ready and (engine.ui.main_display == nil) then
|
||||||
|
engine.dmesg_window.setVisible(false)
|
||||||
|
|
||||||
|
engine.ui.main_display = DisplayBox{window=device,fg_bg=style.root}
|
||||||
|
main_view(engine.ui.main_display)
|
||||||
|
else
|
||||||
|
engine.dmesg_window.setVisible(true)
|
||||||
|
engine.dmesg_window.redraw()
|
||||||
|
end
|
||||||
|
|
||||||
|
iocontrol.fp_monitor_state(0, true)
|
||||||
|
else
|
||||||
|
for idx, monitor in ipairs(engine.monitors.unit_name_map) do
|
||||||
|
if monitor == name then
|
||||||
|
is_used = true
|
||||||
|
_init_display(device)
|
||||||
|
engine.monitors.unit_displays[idx] = device
|
||||||
|
|
||||||
|
if engine.ui_ready and (engine.ui.unit_displays[idx] == nil) then
|
||||||
|
engine.ui.unit_displays[idx] = DisplayBox{window=device,fg_bg=style.root}
|
||||||
|
unit_view(engine.ui.unit_displays[idx], idx)
|
||||||
|
end
|
||||||
|
|
||||||
|
iocontrol.fp_monitor_state(idx, true)
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return is_used
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
-- handle a touch event
|
-- handle a touch event
|
||||||
---@param event mouse_interaction|nil
|
---@param event mouse_interaction|nil
|
||||||
function renderer.handle_mouse(event)
|
function renderer.handle_mouse(event)
|
||||||
if engine.ui_ready and event ~= nil then
|
if event ~= nil then
|
||||||
if event.monitor == engine.monitors.primary_name then
|
if engine.fp_ready and event.monitor == "terminal" then
|
||||||
engine.ui.main_display.handle_mouse(event)
|
engine.ui.front_panel.handle_mouse(event)
|
||||||
else
|
elseif engine.ui_ready then
|
||||||
for id, monitor in ipairs(engine.monitors.unit_name_map) do
|
if event.monitor == engine.monitors.primary_name then
|
||||||
if event.monitor == monitor then
|
engine.ui.main_display.handle_mouse(event)
|
||||||
local layout = engine.ui.unit_displays[id] ---@type graphics_element
|
else
|
||||||
layout.handle_mouse(event)
|
for id, monitor in ipairs(engine.monitors.unit_name_map) do
|
||||||
|
if event.monitor == monitor then
|
||||||
|
local layout = engine.ui.unit_displays[id] ---@type graphics_element
|
||||||
|
layout.handle_mouse(event)
|
||||||
|
break
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
|
|
||||||
local log = require("scada-common.log")
|
local log = require("scada-common.log")
|
||||||
local mqueue = require("scada-common.mqueue")
|
local mqueue = require("scada-common.mqueue")
|
||||||
local util = require("scada-common.util")
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
local config = require("coordinator.config")
|
local config = require("coordinator.config")
|
||||||
|
local iocontrol = require("coordinator.iocontrol")
|
||||||
|
|
||||||
local pocket = require("coordinator.session.pocket")
|
local pocket = require("coordinator.session.pocket")
|
||||||
|
|
||||||
local apisessions = {}
|
local apisessions = {}
|
||||||
|
|
||||||
@ -112,6 +113,7 @@ function apisessions.establish_session(source_addr, version)
|
|||||||
|
|
||||||
setmetatable(pkt_s, mt)
|
setmetatable(pkt_s, mt)
|
||||||
|
|
||||||
|
iocontrol.fp_pkt_connected(id, version, source_addr)
|
||||||
log.debug(util.c("[API] established new session: ", pkt_s))
|
log.debug(util.c("[API] established new session: ", pkt_s))
|
||||||
|
|
||||||
self.next_id = id + 1
|
self.next_id = id + 1
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
local comms = require("scada-common.comms")
|
local comms = require("scada-common.comms")
|
||||||
local log = require("scada-common.log")
|
local log = require("scada-common.log")
|
||||||
local mqueue = require("scada-common.mqueue")
|
local mqueue = require("scada-common.mqueue")
|
||||||
local util = require("scada-common.util")
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
|
local iocontrol = require("coordinator.iocontrol")
|
||||||
|
|
||||||
local pocket = {}
|
local pocket = {}
|
||||||
|
|
||||||
@ -9,8 +11,6 @@ local PROTOCOL = comms.PROTOCOL
|
|||||||
-- local CAPI_TYPE = comms.CAPI_TYPE
|
-- local CAPI_TYPE = comms.CAPI_TYPE
|
||||||
local SCADA_MGMT_TYPE = comms.SCADA_MGMT_TYPE
|
local SCADA_MGMT_TYPE = comms.SCADA_MGMT_TYPE
|
||||||
|
|
||||||
local println = util.println
|
|
||||||
|
|
||||||
-- retry time constants in ms
|
-- retry time constants in ms
|
||||||
-- local INITIAL_WAIT = 1500
|
-- local INITIAL_WAIT = 1500
|
||||||
-- local RETRY_PERIOD = 1000
|
-- local RETRY_PERIOD = 1000
|
||||||
@ -69,6 +69,7 @@ function pocket.new_session(id, s_addr, in_queue, out_queue, timeout)
|
|||||||
local function _close()
|
local function _close()
|
||||||
self.conn_watchdog.cancel()
|
self.conn_watchdog.cancel()
|
||||||
self.connected = false
|
self.connected = false
|
||||||
|
iocontrol.fp_pkt_disconnected(id)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- send a CAPI packet
|
-- send a CAPI packet
|
||||||
@ -140,6 +141,8 @@ function pocket.new_session(id, s_addr, in_queue, out_queue, timeout)
|
|||||||
|
|
||||||
-- log.debug(log_header .. "PKT RTT = " .. self.last_rtt .. "ms")
|
-- log.debug(log_header .. "PKT RTT = " .. self.last_rtt .. "ms")
|
||||||
-- log.debug(log_header .. "PKT TT = " .. (srv_now - api_send) .. "ms")
|
-- log.debug(log_header .. "PKT TT = " .. (srv_now - api_send) .. "ms")
|
||||||
|
|
||||||
|
iocontrol.fp_pkt_rtt(id, self.last_rtt)
|
||||||
else
|
else
|
||||||
log.debug(log_header .. "SCADA keep alive packet length mismatch")
|
log.debug(log_header .. "SCADA keep alive packet length mismatch")
|
||||||
end
|
end
|
||||||
@ -172,7 +175,6 @@ function pocket.new_session(id, s_addr, in_queue, out_queue, timeout)
|
|||||||
function public.close()
|
function public.close()
|
||||||
_close()
|
_close()
|
||||||
_send_mgmt(SCADA_MGMT_TYPE.CLOSE, {})
|
_send_mgmt(SCADA_MGMT_TYPE.CLOSE, {})
|
||||||
println("connection to pocket session " .. id .. " closed by server")
|
|
||||||
log.info(log_header .. "session closed by server")
|
log.info(log_header .. "session closed by server")
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -211,7 +213,6 @@ function pocket.new_session(id, s_addr, in_queue, out_queue, timeout)
|
|||||||
|
|
||||||
-- exit if connection was closed
|
-- exit if connection was closed
|
||||||
if not self.connected then
|
if not self.connected then
|
||||||
println("connection to pocket session " .. id .. " closed by remote host")
|
|
||||||
log.info(log_header .. "session closed by remote host")
|
log.info(log_header .. "session closed by remote host")
|
||||||
return self.connected
|
return self.connected
|
||||||
end
|
end
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
require("/initenv").init_env()
|
require("/initenv").init_env()
|
||||||
|
|
||||||
|
local comms = require("scada-common.comms")
|
||||||
local crash = require("scada-common.crash")
|
local crash = require("scada-common.crash")
|
||||||
local log = require("scada-common.log")
|
local log = require("scada-common.log")
|
||||||
local network = require("scada-common.network")
|
local network = require("scada-common.network")
|
||||||
@ -21,7 +22,7 @@ local sounder = require("coordinator.sounder")
|
|||||||
|
|
||||||
local apisessions = require("coordinator.session.apisessions")
|
local apisessions = require("coordinator.session.apisessions")
|
||||||
|
|
||||||
local COORDINATOR_VERSION = "v0.17.1"
|
local COORDINATOR_VERSION = "v0.21.0"
|
||||||
|
|
||||||
local println = util.println
|
local println = util.println
|
||||||
local println_ts = util.println_ts
|
local println_ts = util.println_ts
|
||||||
@ -30,7 +31,6 @@ local log_graphics = coordinator.log_graphics
|
|||||||
local log_sys = coordinator.log_sys
|
local log_sys = coordinator.log_sys
|
||||||
local log_boot = coordinator.log_boot
|
local log_boot = coordinator.log_boot
|
||||||
local log_comms = coordinator.log_comms
|
local log_comms = coordinator.log_comms
|
||||||
local log_comms_connecting = coordinator.log_comms_connecting
|
|
||||||
local log_crypto = coordinator.log_crypto
|
local log_crypto = coordinator.log_crypto
|
||||||
|
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
@ -80,6 +80,9 @@ local function main()
|
|||||||
-- mount connected devices
|
-- mount connected devices
|
||||||
ppm.mount_all()
|
ppm.mount_all()
|
||||||
|
|
||||||
|
-- report versions/init fp PSIL
|
||||||
|
iocontrol.init_fp(COORDINATOR_VERSION, comms.version)
|
||||||
|
|
||||||
-- setup monitors
|
-- setup monitors
|
||||||
local configured, monitors = coordinator.configure_monitors(config.NUM_UNITS)
|
local configured, monitors = coordinator.configure_monitors(config.NUM_UNITS)
|
||||||
if not configured or monitors == nil then
|
if not configured or monitors == nil then
|
||||||
@ -127,6 +130,7 @@ local function main()
|
|||||||
sounder.init(speaker, config.SOUNDER_VOLUME)
|
sounder.init(speaker, config.SOUNDER_VOLUME)
|
||||||
log_boot("tone generation took " .. (util.time_ms() - sounder_start) .. "ms")
|
log_boot("tone generation took " .. (util.time_ms() - sounder_start) .. "ms")
|
||||||
log_sys("annunciator alarm configured")
|
log_sys("annunciator alarm configured")
|
||||||
|
iocontrol.fp_has_speaker(true)
|
||||||
end
|
end
|
||||||
|
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
@ -148,6 +152,7 @@ local function main()
|
|||||||
return
|
return
|
||||||
else
|
else
|
||||||
log_comms("wireless modem connected")
|
log_comms("wireless modem connected")
|
||||||
|
iocontrol.fp_has_modem(true)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- create connection watchdog
|
-- create connection watchdog
|
||||||
@ -167,76 +172,54 @@ local function main()
|
|||||||
local loop_clock = util.new_clock(MAIN_CLOCK)
|
local loop_clock = util.new_clock(MAIN_CLOCK)
|
||||||
|
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
-- connect to the supervisor
|
-- start front panel & UI start function
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
|
|
||||||
-- attempt to connect to the supervisor or exit
|
log_graphics("starting front panel UI...")
|
||||||
local function init_connect_sv()
|
|
||||||
local tick_waiting, task_done = log_comms_connecting("attempting to connect to configured supervisor on channel " .. config.SVR_CHANNEL)
|
|
||||||
|
|
||||||
-- attempt to establish a connection with the supervisory computer
|
local fp_ok, fp_message = pcall(renderer.start_fp)
|
||||||
if not coord_comms.sv_connect(60, tick_waiting, task_done) then
|
if not fp_ok then
|
||||||
log_sys("supervisor connection failed, shutting down...")
|
renderer.close_fp()
|
||||||
log.fatal("failed to connect to supervisor")
|
log_graphics(util.c("front panel UI error: ", fp_message))
|
||||||
return false
|
println_ts("front panel UI creation failed")
|
||||||
end
|
log.fatal(util.c("front panel GUI render failed with error ", fp_message))
|
||||||
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
if not init_connect_sv() then
|
|
||||||
println("startup> failed to connect to supervisor")
|
|
||||||
log_sys("system shutdown")
|
|
||||||
return
|
return
|
||||||
else
|
else log_graphics("front panel ready") end
|
||||||
log_sys("supervisor connected, proceeding to UI start")
|
|
||||||
end
|
|
||||||
|
|
||||||
----------------------------------------
|
-- start up the main UI
|
||||||
-- start the UI
|
|
||||||
----------------------------------------
|
|
||||||
|
|
||||||
-- start up the UI
|
|
||||||
---@return boolean ui_ok started ok
|
---@return boolean ui_ok started ok
|
||||||
local function init_start_ui()
|
local function start_main_ui()
|
||||||
log_graphics("starting UI...")
|
log_graphics("starting main UI...")
|
||||||
|
|
||||||
local draw_start = util.time_ms()
|
local draw_start = util.time_ms()
|
||||||
|
|
||||||
local ui_ok, message = pcall(renderer.start_ui)
|
local ui_ok, ui_message = pcall(renderer.start_ui)
|
||||||
if not ui_ok then
|
if not ui_ok then
|
||||||
renderer.close_ui()
|
renderer.close_ui()
|
||||||
log_graphics(util.c("UI crashed: ", message))
|
log_graphics(util.c("main UI error: ", ui_message))
|
||||||
println_ts("UI crashed")
|
log.fatal(util.c("main GUI render failed with error ", ui_message))
|
||||||
log.fatal(util.c("GUI crashed with error ", message))
|
|
||||||
else
|
else
|
||||||
log_graphics("first UI draw took " .. (util.time_ms() - draw_start) .. "ms")
|
log_graphics("main UI draw took " .. (util.time_ms() - draw_start) .. "ms")
|
||||||
|
|
||||||
-- start clock
|
|
||||||
loop_clock.start()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return ui_ok
|
return ui_ok
|
||||||
end
|
end
|
||||||
|
|
||||||
local ui_ok = init_start_ui()
|
|
||||||
|
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
-- main event loop
|
-- main event loop
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
|
|
||||||
|
local link_failed = false
|
||||||
|
local ui_ok = true
|
||||||
local date_format = util.trinary(config.TIME_24_HOUR, "%X \x04 %A, %B %d %Y", "%r \x04 %A, %B %d %Y")
|
local date_format = util.trinary(config.TIME_24_HOUR, "%X \x04 %A, %B %d %Y", "%r \x04 %A, %B %d %Y")
|
||||||
|
|
||||||
if ui_ok then
|
-- start clock
|
||||||
-- start connection watchdog
|
loop_clock.start()
|
||||||
conn_watchdog.feed()
|
|
||||||
log.debug("startup> conn watchdog started")
|
|
||||||
|
|
||||||
log_sys("system started successfully")
|
log_sys("system started successfully")
|
||||||
end
|
|
||||||
|
|
||||||
-- main event loop
|
-- main event loop
|
||||||
while ui_ok do
|
while true do
|
||||||
local event, param1, param2, param3, param4, param5 = util.pull_event()
|
local event, param1, param2, param3, param4, param5 = util.pull_event()
|
||||||
|
|
||||||
-- handle event
|
-- handle event
|
||||||
@ -250,31 +233,32 @@ local function main()
|
|||||||
if nic.is_modem(device) then
|
if nic.is_modem(device) then
|
||||||
nic.disconnect()
|
nic.disconnect()
|
||||||
log_sys("comms modem disconnected")
|
log_sys("comms modem disconnected")
|
||||||
println_ts("wireless modem disconnected!")
|
|
||||||
|
|
||||||
-- close out UI
|
local other_modem = ppm.get_wireless_modem()
|
||||||
renderer.close_ui()
|
if other_modem then
|
||||||
|
log_sys("found another wireless modem, using it for comms")
|
||||||
|
nic.connect(other_modem)
|
||||||
|
else
|
||||||
|
-- close out main UI
|
||||||
|
renderer.close_ui()
|
||||||
|
|
||||||
-- alert user to status
|
-- alert user to status
|
||||||
log_sys("awaiting comms modem reconnect...")
|
log_sys("awaiting comms modem reconnect...")
|
||||||
|
|
||||||
|
iocontrol.fp_has_modem(false)
|
||||||
|
end
|
||||||
else
|
else
|
||||||
log_sys("non-comms modem disconnected")
|
log_sys("non-comms modem disconnected")
|
||||||
end
|
end
|
||||||
elseif type == "monitor" then
|
elseif type == "monitor" then
|
||||||
if renderer.is_monitor_used(device) then
|
if renderer.handle_disconnect(device) then
|
||||||
---@todo will be handled properly in #249
|
log_sys("lost a configured monitor")
|
||||||
-- "halt and catch fire" style handling
|
|
||||||
local msg = "lost a configured monitor, system will now exit"
|
|
||||||
println_ts(msg)
|
|
||||||
log_sys(msg)
|
|
||||||
break
|
|
||||||
else
|
else
|
||||||
log_sys("lost unused monitor, ignoring")
|
log_sys("lost an unused monitor")
|
||||||
end
|
end
|
||||||
elseif type == "speaker" then
|
elseif type == "speaker" then
|
||||||
local msg = "lost alarm sounder speaker"
|
log_sys("lost alarm sounder speaker")
|
||||||
println_ts(msg)
|
iocontrol.fp_has_speaker(false)
|
||||||
log_sys(msg)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
elseif event == "peripheral" then
|
elseif event == "peripheral" then
|
||||||
@ -282,33 +266,50 @@ local function main()
|
|||||||
|
|
||||||
if type ~= nil and device ~= nil then
|
if type ~= nil and device ~= nil then
|
||||||
if type == "modem" then
|
if type == "modem" then
|
||||||
if device.isWireless() then
|
if device.isWireless() and not nic.is_connected() then
|
||||||
-- reconnected modem
|
-- reconnected modem
|
||||||
nic.connect(device)
|
|
||||||
|
|
||||||
log_sys("comms modem reconnected")
|
log_sys("comms modem reconnected")
|
||||||
println_ts("wireless modem reconnected.")
|
nic.connect(device)
|
||||||
|
iocontrol.fp_has_modem(true)
|
||||||
-- re-init system
|
elseif device.isWireless() then
|
||||||
if not init_connect_sv() then break end
|
log.info("unused wireless modem reconnected")
|
||||||
ui_ok = init_start_ui()
|
|
||||||
else
|
else
|
||||||
log_sys("wired modem reconnected")
|
log_sys("wired modem reconnected")
|
||||||
end
|
end
|
||||||
-- elseif type == "monitor" then
|
elseif type == "monitor" then
|
||||||
---@todo will be handled properly in #249
|
if renderer.handle_reconnect(param1, device) then
|
||||||
-- not supported, system will exit on loss of in-use monitors
|
log_sys(util.c("configured monitor ", param1, " reconnected"))
|
||||||
|
else
|
||||||
|
log_sys(util.c("unused monitor ", param1, " connected"))
|
||||||
|
end
|
||||||
elseif type == "speaker" then
|
elseif type == "speaker" then
|
||||||
local msg = "alarm sounder speaker reconnected"
|
log_sys("alarm sounder speaker reconnected")
|
||||||
println_ts(msg)
|
|
||||||
log_sys(msg)
|
|
||||||
sounder.reconnect(device)
|
sounder.reconnect(device)
|
||||||
|
iocontrol.fp_has_speaker(true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
elseif event == "timer" then
|
elseif event == "timer" then
|
||||||
if loop_clock.is_clock(param1) then
|
if loop_clock.is_clock(param1) then
|
||||||
-- main loop tick
|
-- main loop tick
|
||||||
|
|
||||||
|
-- toggle heartbeat
|
||||||
|
iocontrol.heartbeat()
|
||||||
|
|
||||||
|
-- maintain connection
|
||||||
|
if nic.is_connected() then
|
||||||
|
local ok, start_ui = coord_comms.try_connect()
|
||||||
|
if not ok then
|
||||||
|
link_failed = true
|
||||||
|
log_sys("supervisor connection failed, shutting down...")
|
||||||
|
log.fatal("failed to connect to supervisor")
|
||||||
|
break
|
||||||
|
elseif start_ui then
|
||||||
|
log_sys("supervisor connected, proceeding to main UI start")
|
||||||
|
ui_ok = start_main_ui()
|
||||||
|
if not ui_ok then break end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- iterate sessions
|
-- iterate sessions
|
||||||
apisessions.iterate_all()
|
apisessions.iterate_all()
|
||||||
|
|
||||||
@ -316,25 +317,19 @@ local function main()
|
|||||||
apisessions.free_all_closed()
|
apisessions.free_all_closed()
|
||||||
|
|
||||||
-- update date and time string for main display
|
-- update date and time string for main display
|
||||||
iocontrol.get_db().facility.ps.publish("date_time", os.date(date_format))
|
if coord_comms.is_linked() then
|
||||||
|
iocontrol.get_db().facility.ps.publish("date_time", os.date(date_format))
|
||||||
|
end
|
||||||
|
|
||||||
loop_clock.start()
|
loop_clock.start()
|
||||||
elseif conn_watchdog.is_timer(param1) then
|
elseif conn_watchdog.is_timer(param1) then
|
||||||
-- supervisor watchdog timeout
|
-- supervisor watchdog timeout
|
||||||
local msg = "supervisor server timeout"
|
log_comms("supervisor server timeout")
|
||||||
log_comms(msg)
|
|
||||||
println_ts(msg)
|
|
||||||
|
|
||||||
-- close connection, UI, and stop sounder
|
-- close connection, main UI, and stop sounder
|
||||||
coord_comms.close()
|
coord_comms.close()
|
||||||
renderer.close_ui()
|
renderer.close_ui()
|
||||||
sounder.stop()
|
sounder.stop()
|
||||||
|
|
||||||
if nic.connected() then
|
|
||||||
-- try to re-connect to the supervisor
|
|
||||||
if not init_connect_sv() then break end
|
|
||||||
ui_ok = init_start_ui()
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
-- a non-clock/main watchdog timer event
|
-- a non-clock/main watchdog timer event
|
||||||
|
|
||||||
@ -347,25 +342,19 @@ local function main()
|
|||||||
elseif event == "modem_message" then
|
elseif event == "modem_message" then
|
||||||
-- got a packet
|
-- got a packet
|
||||||
local packet = coord_comms.parse_packet(param1, param2, param3, param4, param5)
|
local packet = coord_comms.parse_packet(param1, param2, param3, param4, param5)
|
||||||
coord_comms.handle_packet(packet)
|
|
||||||
|
|
||||||
-- check if it was a disconnect
|
-- handle then check if it was a disconnect
|
||||||
if not coord_comms.is_linked() then
|
if coord_comms.handle_packet(packet) then
|
||||||
log_comms("supervisor closed connection")
|
log_comms("supervisor closed connection")
|
||||||
|
|
||||||
-- close connection, UI, and stop sounder
|
-- close connection, main UI, and stop sounder
|
||||||
coord_comms.close()
|
coord_comms.close()
|
||||||
renderer.close_ui()
|
renderer.close_ui()
|
||||||
sounder.stop()
|
sounder.stop()
|
||||||
|
|
||||||
if nic.connected() then
|
|
||||||
-- try to re-connect to the supervisor
|
|
||||||
if not init_connect_sv() then break end
|
|
||||||
ui_ok = init_start_ui()
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
elseif event == "monitor_touch" then
|
elseif event == "monitor_touch" or event == "mouse_click" or event == "mouse_up" or
|
||||||
-- handle a monitor touch event
|
event == "mouse_drag" or event == "mouse_scroll" then
|
||||||
|
-- handle a mouse event
|
||||||
renderer.handle_mouse(core.events.new_mouse_event(event, param1, param2, param3))
|
renderer.handle_mouse(core.events.new_mouse_event(event, param1, param2, param3))
|
||||||
elseif event == "speaker_audio_empty" then
|
elseif event == "speaker_audio_empty" then
|
||||||
-- handle speaker buffer emptied
|
-- handle speaker buffer emptied
|
||||||
@ -374,10 +363,17 @@ local function main()
|
|||||||
|
|
||||||
-- check for termination request
|
-- check for termination request
|
||||||
if event == "terminate" or ppm.should_terminate() then
|
if event == "terminate" or ppm.should_terminate() then
|
||||||
println_ts("terminate requested, closing connections...")
|
-- handle supervisor connection
|
||||||
log_comms("terminate requested, closing supervisor connection...")
|
coord_comms.try_connect(true)
|
||||||
|
|
||||||
|
if coord_comms.is_linked() then
|
||||||
|
log_comms("terminate requested, closing supervisor connection...")
|
||||||
|
else link_failed = true end
|
||||||
|
|
||||||
coord_comms.close()
|
coord_comms.close()
|
||||||
log_comms("supervisor connection closed")
|
log_comms("supervisor connection closed")
|
||||||
|
|
||||||
|
-- handle API sessions
|
||||||
log_comms("closing api sessions...")
|
log_comms("closing api sessions...")
|
||||||
apisessions.close_all()
|
apisessions.close_all()
|
||||||
log_comms("api sessions closed")
|
log_comms("api sessions closed")
|
||||||
@ -386,15 +382,20 @@ local function main()
|
|||||||
end
|
end
|
||||||
|
|
||||||
renderer.close_ui()
|
renderer.close_ui()
|
||||||
|
renderer.close_fp()
|
||||||
sounder.stop()
|
sounder.stop()
|
||||||
log_sys("system shutdown")
|
log_sys("system shutdown")
|
||||||
|
|
||||||
|
if link_failed then println_ts("failed to connect to supervisor") end
|
||||||
|
if not ui_ok then println_ts("main UI creation failed") end
|
||||||
|
|
||||||
println_ts("exited")
|
println_ts("exited")
|
||||||
log.info("exited")
|
log.info("exited")
|
||||||
end
|
end
|
||||||
|
|
||||||
if not xpcall(main, crash.handler) then
|
if not xpcall(main, crash.handler) then
|
||||||
pcall(renderer.close_ui)
|
pcall(renderer.close_ui)
|
||||||
|
pcall(renderer.close_fp)
|
||||||
pcall(sounder.stop)
|
pcall(sounder.stop)
|
||||||
crash.exit()
|
crash.exit()
|
||||||
else
|
else
|
||||||
|
48
coordinator/ui/components/pkt_entry.lua
Normal file
48
coordinator/ui/components/pkt_entry.lua
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
--
|
||||||
|
-- Pocket Connection Entry
|
||||||
|
--
|
||||||
|
|
||||||
|
local iocontrol = require("coordinator.iocontrol")
|
||||||
|
|
||||||
|
local core = require("graphics.core")
|
||||||
|
|
||||||
|
local Div = require("graphics.elements.div")
|
||||||
|
local TextBox = require("graphics.elements.textbox")
|
||||||
|
|
||||||
|
local DataIndicator = require("graphics.elements.indicators.data")
|
||||||
|
|
||||||
|
local TEXT_ALIGN = core.TEXT_ALIGN
|
||||||
|
|
||||||
|
local cpair = core.cpair
|
||||||
|
|
||||||
|
-- create a pocket list entry
|
||||||
|
---@param parent graphics_element parent
|
||||||
|
---@param id integer PKT session ID
|
||||||
|
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 entry = Div{parent=root,x=2,y=1,height=3,fg_bg=cpair(colors.black,colors.white)}
|
||||||
|
|
||||||
|
local ps_prefix = "pkt_" .. id .. "_"
|
||||||
|
|
||||||
|
TextBox{parent=entry,x=1,y=1,text="",width=8,height=1,fg_bg=cpair(colors.black,colors.lightGray)}
|
||||||
|
local pkt_addr = TextBox{parent=entry,x=1,y=2,text="@ C ??",alignment=TEXT_ALIGN.CENTER,width=8,height=1,fg_bg=cpair(colors.black,colors.lightGray),nav_active=cpair(colors.gray,colors.black)}
|
||||||
|
TextBox{parent=entry,x=1,y=3,text="",width=8,height=1,fg_bg=cpair(colors.black,colors.lightGray)}
|
||||||
|
pkt_addr.register(ps, ps_prefix .. "addr", pkt_addr.set_value)
|
||||||
|
|
||||||
|
TextBox{parent=entry,x=10,y=2,text="FW:",width=3,height=1}
|
||||||
|
local pkt_fw_v = TextBox{parent=entry,x=14,y=2,text=" ------- ",width=20,height=1,fg_bg=cpair(colors.lightGray,colors.white)}
|
||||||
|
pkt_fw_v.register(ps, ps_prefix .. "fw", pkt_fw_v.set_value)
|
||||||
|
|
||||||
|
TextBox{parent=entry,x=35,y=2,text="RTT:",width=4,height=1}
|
||||||
|
local pkt_rtt = DataIndicator{parent=entry,x=40,y=2,label="",unit="",format="%5d",value=0,width=5,fg_bg=cpair(colors.lightGray,colors.white)}
|
||||||
|
TextBox{parent=entry,x=46,y=2,text="ms",width=4,height=1,fg_bg=cpair(colors.lightGray,colors.white)}
|
||||||
|
pkt_rtt.register(ps, ps_prefix .. "rtt", pkt_rtt.update)
|
||||||
|
pkt_rtt.register(ps, ps_prefix .. "rtt_color", pkt_rtt.recolor)
|
||||||
|
|
||||||
|
return root
|
||||||
|
end
|
||||||
|
|
||||||
|
return init
|
@ -15,8 +15,10 @@ local TextBox = require("graphics.elements.textbox")
|
|||||||
local DataIndicator = require("graphics.elements.indicators.data")
|
local DataIndicator = require("graphics.elements.indicators.data")
|
||||||
local IndicatorLight = require("graphics.elements.indicators.light")
|
local IndicatorLight = require("graphics.elements.indicators.light")
|
||||||
local RadIndicator = require("graphics.elements.indicators.rad")
|
local RadIndicator = require("graphics.elements.indicators.rad")
|
||||||
|
local StateIndicator = require("graphics.elements.indicators.state")
|
||||||
local TriIndicatorLight = require("graphics.elements.indicators.trilight")
|
local TriIndicatorLight = require("graphics.elements.indicators.trilight")
|
||||||
|
|
||||||
|
local Checkbox = require("graphics.elements.controls.checkbox")
|
||||||
local HazardButton = require("graphics.elements.controls.hazard_button")
|
local HazardButton = require("graphics.elements.controls.hazard_button")
|
||||||
local RadioButton = require("graphics.elements.controls.radio_button")
|
local RadioButton = require("graphics.elements.controls.radio_button")
|
||||||
local SpinboxNumeric = require("graphics.elements.controls.spinbox_numeric")
|
local SpinboxNumeric = require("graphics.elements.controls.spinbox_numeric")
|
||||||
@ -43,7 +45,7 @@ local function new_view(root, x, y)
|
|||||||
local lu_cpair = cpair(colors.gray, colors.gray)
|
local lu_cpair = cpair(colors.gray, colors.gray)
|
||||||
local dis_colors = cpair(colors.white, colors.lightGray)
|
local dis_colors = cpair(colors.white, colors.lightGray)
|
||||||
|
|
||||||
local main = Div{parent=root,width=104,height=24,x=x,y=y}
|
local main = Div{parent=root,width=128,height=24,x=x,y=y}
|
||||||
|
|
||||||
local scram = HazardButton{parent=main,x=1,y=1,text="FAC SCRAM",accent=colors.yellow,dis_colors=dis_colors,callback=process.fac_scram,fg_bg=hzd_fg_bg}
|
local scram = HazardButton{parent=main,x=1,y=1,text="FAC SCRAM",accent=colors.yellow,dis_colors=dis_colors,callback=process.fac_scram,fg_bg=hzd_fg_bg}
|
||||||
local ack_a = HazardButton{parent=main,x=16,y=1,text="ACK \x13",accent=colors.orange,dis_colors=dis_colors,callback=process.fac_ack_alarms,fg_bg=hzd_fg_bg}
|
local ack_a = HazardButton{parent=main,x=16,y=1,text="ACK \x13",accent=colors.orange,dis_colors=dis_colors,callback=process.fac_ack_alarms,fg_bg=hzd_fg_bg}
|
||||||
@ -52,12 +54,14 @@ local function new_view(root, x, y)
|
|||||||
facility.ack_alarms_ack = ack_a.on_response
|
facility.ack_alarms_ack = ack_a.on_response
|
||||||
|
|
||||||
local all_ok = IndicatorLight{parent=main,y=5,label="Unit Systems Online",colors=cpair(colors.green,colors.red)}
|
local all_ok = IndicatorLight{parent=main,y=5,label="Unit Systems Online",colors=cpair(colors.green,colors.red)}
|
||||||
local ind_mat = IndicatorLight{parent=main,label="Induction Matrix",colors=cpair(colors.green,colors.gray)}
|
|
||||||
local rad_mon = TriIndicatorLight{parent=main,label="Radiation Monitor",c1=colors.gray,c2=colors.yellow,c3=colors.green}
|
local rad_mon = TriIndicatorLight{parent=main,label="Radiation Monitor",c1=colors.gray,c2=colors.yellow,c3=colors.green}
|
||||||
|
local ind_mat = IndicatorLight{parent=main,label="Induction Matrix",colors=cpair(colors.green,colors.gray)}
|
||||||
|
local sps = IndicatorLight{parent=main,label="SPS Connected",colors=cpair(colors.green,colors.gray)}
|
||||||
|
|
||||||
all_ok.register(facility.ps, "all_sys_ok", all_ok.update)
|
all_ok.register(facility.ps, "all_sys_ok", all_ok.update)
|
||||||
ind_mat.register(facility.induction_ps_tbl[1], "computed_status", function (status) ind_mat.update(status > 1) end)
|
|
||||||
rad_mon.register(facility.ps, "rad_computed_status", rad_mon.update)
|
rad_mon.register(facility.ps, "rad_computed_status", rad_mon.update)
|
||||||
|
ind_mat.register(facility.induction_ps_tbl[1], "computed_status", function (status) ind_mat.update(status > 1) end)
|
||||||
|
sps.register(facility.sps_ps_tbl[1], "computed_status", function (status) sps.update(status > 1) end)
|
||||||
|
|
||||||
main.line_break()
|
main.line_break()
|
||||||
|
|
||||||
@ -99,7 +103,7 @@ local function new_view(root, x, y)
|
|||||||
-- process control --
|
-- process control --
|
||||||
---------------------
|
---------------------
|
||||||
|
|
||||||
local proc = Div{parent=main,width=78,height=24,x=27,y=1}
|
local proc = Div{parent=main,width=103,height=24,x=27,y=1}
|
||||||
|
|
||||||
-----------------------------
|
-----------------------------
|
||||||
-- process control targets --
|
-- process control targets --
|
||||||
@ -148,46 +152,77 @@ local function new_view(root, x, y)
|
|||||||
|
|
||||||
local rate_limits = {}
|
local rate_limits = {}
|
||||||
|
|
||||||
for i = 1, facility.num_units do
|
for i = 1, 4 do
|
||||||
local unit = units[i] ---@type ioctl_unit
|
local unit
|
||||||
|
local tag_fg_bg = cpair(colors.gray,colors.white)
|
||||||
|
local lim_fg_bg = cpair(colors.lightGray,colors.white)
|
||||||
|
local ctl_fg = colors.lightGray
|
||||||
|
local cur_fg_bg = cpair(colors.lightGray,colors.white)
|
||||||
|
local cur_lu = colors.lightGray
|
||||||
|
|
||||||
|
if i <= facility.num_units then
|
||||||
|
unit = units[i] ---@type ioctl_unit
|
||||||
|
tag_fg_bg = cpair(colors.black,colors.lightBlue)
|
||||||
|
lim_fg_bg = bw_fg_bg
|
||||||
|
ctl_fg = colors.gray
|
||||||
|
cur_fg_bg = cpair(colors.black,colors.brown)
|
||||||
|
cur_lu = colors.black
|
||||||
|
end
|
||||||
|
|
||||||
local _y = ((i - 1) * 5) + 1
|
local _y = ((i - 1) * 5) + 1
|
||||||
|
|
||||||
local unit_tag = Div{parent=limit_div,x=1,y=_y,width=8,height=4,fg_bg=cpair(colors.black,colors.lightBlue)}
|
local unit_tag = Div{parent=limit_div,x=1,y=_y,width=8,height=4,fg_bg=tag_fg_bg}
|
||||||
TextBox{parent=unit_tag,x=2,y=2,text="Unit "..i.." Limit",width=7,height=2}
|
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=cpair(colors.gray,colors.white)}
|
local lim_ctl = Div{parent=limit_div,x=9,y=_y,width=14,height=3,fg_bg=cpair(ctl_fg,colors.white)}
|
||||||
rate_limits[i] = SpinboxNumeric{parent=lim_ctl,x=2,y=1,whole_num_precision=4,fractional_precision=1,min=0.1,arrow_fg_bg=cpair(colors.gray,colors.white),fg_bg=bw_fg_bg}
|
local lim = SpinboxNumeric{parent=lim_ctl,x=2,y=1,whole_num_precision=4,fractional_precision=1,min=0.1,arrow_fg_bg=cpair(colors.gray,colors.white),fg_bg=lim_fg_bg}
|
||||||
TextBox{parent=lim_ctl,x=9,y=2,text="mB/t",width=4,height=1}
|
TextBox{parent=lim_ctl,x=9,y=2,text="mB/t",width=4,height=1}
|
||||||
|
|
||||||
rate_limits[i].register(unit.unit_ps, "max_burn", rate_limits[i].set_max)
|
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}
|
||||||
rate_limits[i].register(unit.unit_ps, "burn_limit", rate_limits[i].set_value)
|
|
||||||
|
|
||||||
local cur_burn = DataIndicator{parent=limit_div,x=9,y=_y+3,label="",format="%7.1f",value=0,unit="mB/t",commas=false,lu_colors=cpair(colors.black,colors.black),width=14,fg_bg=cpair(colors.black,colors.brown)}
|
if i <= facility.num_units then
|
||||||
|
rate_limits[i] = lim
|
||||||
|
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)
|
||||||
|
|
||||||
cur_burn.register(unit.unit_ps, "act_burn_rate", cur_burn.update)
|
cur_burn.register(unit.unit_ps, "act_burn_rate", cur_burn.update)
|
||||||
|
else
|
||||||
|
lim.disable()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-------------------
|
-------------------
|
||||||
-- unit statuses --
|
-- unit statuses --
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
local stat_div = Div{parent=proc,width=38,height=19,x=57,y=6}
|
local stat_div = Div{parent=proc,width=22,height=24,x=57,y=6}
|
||||||
|
|
||||||
for i = 1, facility.num_units do
|
for i = 1, 4 do
|
||||||
local unit = units[i] ---@type ioctl_unit
|
local tag_fg_bg = cpair(colors.gray,colors.white)
|
||||||
|
local ind_fg_bg = cpair(colors.lightGray,colors.white)
|
||||||
|
local ind_off = colors.lightGray
|
||||||
|
|
||||||
|
if i <= facility.num_units then
|
||||||
|
tag_fg_bg = cpair(colors.black,colors.cyan)
|
||||||
|
ind_fg_bg = bw_fg_bg
|
||||||
|
ind_off = colors.gray
|
||||||
|
end
|
||||||
|
|
||||||
local _y = ((i - 1) * 5) + 1
|
local _y = ((i - 1) * 5) + 1
|
||||||
|
|
||||||
local unit_tag = Div{parent=stat_div,x=1,y=_y,width=8,height=4,fg_bg=cpair(colors.black,colors.lightBlue)}
|
local unit_tag = Div{parent=stat_div,x=1,y=_y,width=8,height=4,fg_bg=tag_fg_bg}
|
||||||
TextBox{parent=unit_tag,x=2,y=2,text="Unit "..i.." Status",width=7,height=2}
|
TextBox{parent=unit_tag,x=2,y=2,text="Unit "..i.." Status",width=7,height=2}
|
||||||
|
|
||||||
local lights = Div{parent=stat_div,x=9,y=_y,width=12,height=4,fg_bg=bw_fg_bg}
|
local lights = Div{parent=stat_div,x=9,y=_y,width=14,height=4,fg_bg=ind_fg_bg}
|
||||||
local ready = IndicatorLight{parent=lights,x=2,y=2,label="Ready",colors=cpair(colors.green,colors.gray)}
|
local ready = IndicatorLight{parent=lights,x=2,y=2,label="Ready",colors=cpair(colors.green,ind_off)}
|
||||||
local degraded = IndicatorLight{parent=lights,x=2,y=3,label="Degraded",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS}
|
local degraded = IndicatorLight{parent=lights,x=2,y=3,label="Degraded",colors=cpair(colors.red,ind_off),flash=true,period=period.BLINK_250_MS}
|
||||||
|
|
||||||
ready.register(unit.unit_ps, "U_AutoReady", ready.update)
|
if i <= facility.num_units then
|
||||||
degraded.register(unit.unit_ps, "U_AutoDegraded", degraded.update)
|
local unit = units[i] ---@type ioctl_unit
|
||||||
|
|
||||||
|
ready.register(unit.unit_ps, "U_AutoReady", ready.update)
|
||||||
|
degraded.register(unit.unit_ps, "U_AutoDegraded", degraded.update)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-------------------------
|
-------------------------
|
||||||
@ -195,7 +230,7 @@ local function new_view(root, x, y)
|
|||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
local ctl_opts = { "Monitored Max Burn", "Combined Burn Rate", "Charge Level", "Generation Rate" }
|
local ctl_opts = { "Monitored Max Burn", "Combined Burn Rate", "Charge Level", "Generation Rate" }
|
||||||
local mode = RadioButton{parent=proc,x=34,y=1,options=ctl_opts,callback=function()end,radio_colors=cpair(colors.purple,colors.black),radio_bg=colors.gray}
|
local mode = RadioButton{parent=proc,x=34,y=1,options=ctl_opts,callback=function()end,radio_colors=cpair(colors.white,colors.black),radio_bg=colors.purple}
|
||||||
|
|
||||||
mode.register(facility.ps, "process_mode", mode.set_value)
|
mode.register(facility.ps, "process_mode", mode.set_value)
|
||||||
|
|
||||||
@ -261,6 +296,60 @@ local function new_view(root, x, y)
|
|||||||
for i = 1, #rate_limits do rate_limits[i].enable() end
|
for i = 1, #rate_limits do rate_limits[i].enable() end
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
------------------------------
|
||||||
|
-- waste production control --
|
||||||
|
------------------------------
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
TextBox{parent=waste_status,y=i,text="U"..i.." Waste",width=8,height=1}
|
||||||
|
local a_waste = IndicatorLight{parent=waste_status,x=10,y=i,label="Auto",colors=cpair(colors.white,colors.gray)}
|
||||||
|
local waste_m = StateIndicator{parent=waste_status,x=17,y=i,states=style.waste.states_abbrv,value=1,min_width=6}
|
||||||
|
|
||||||
|
a_waste.register(unit.unit_ps, "U_AutoWaste", a_waste.update)
|
||||||
|
waste_m.register(unit.unit_ps, "U_WasteProduct", waste_m.update)
|
||||||
|
end
|
||||||
|
|
||||||
|
local waste_sel = Div{parent=proc,width=21,height=24,x=81,y=1}
|
||||||
|
|
||||||
|
TextBox{parent=waste_sel,text=" ",width=21,height=1,x=1,y=1,fg_bg=cpair(colors.black,colors.brown)}
|
||||||
|
TextBox{parent=waste_sel,text="WASTE PRODUCTION",alignment=TEXT_ALIGN.CENTER,width=21,height=1,x=1,y=2,fg_bg=cpair(colors.lightGray,colors.brown)}
|
||||||
|
|
||||||
|
local rect = Rectangle{parent=waste_sel,border=border(1,colors.brown,true),width=21,height=22,x=1,y=3}
|
||||||
|
local status = StateIndicator{parent=rect,x=2,y=1,states=style.waste.states,value=1,min_width=17}
|
||||||
|
|
||||||
|
status.register(facility.ps, "current_waste_product", status.update)
|
||||||
|
|
||||||
|
local waste_prod = RadioButton{parent=rect,x=2,y=3,options=style.waste.options,callback=process.set_process_waste,radio_colors=cpair(colors.white,colors.black),radio_bg=colors.brown}
|
||||||
|
local pu_fallback = Checkbox{parent=rect,x=2,y=7,label="Pu Fallback",callback=process.set_pu_fallback,box_fg_bg=cpair(colors.green,colors.black)}
|
||||||
|
|
||||||
|
waste_prod.register(facility.ps, "process_waste_product", waste_prod.set_value)
|
||||||
|
pu_fallback.register(facility.ps, "process_pu_fallback", pu_fallback.set_value)
|
||||||
|
|
||||||
|
local fb_active = IndicatorLight{parent=rect,x=2,y=9,label="Fallback Active",colors=cpair(colors.white,colors.gray)}
|
||||||
|
|
||||||
|
fb_active.register(facility.ps, "pu_fallback_active", fb_active.update)
|
||||||
|
|
||||||
|
TextBox{parent=rect,x=2,y=11,text="Plutonium Rate",height=1,width=17,fg_bg=style.label}
|
||||||
|
local pu_rate = DataIndicator{parent=rect,x=2,label="",unit="mB/t",format="%12.2f",value=0,lu_colors=lu_cpair,fg_bg=bw_fg_bg,width=17}
|
||||||
|
|
||||||
|
TextBox{parent=rect,x=2,y=14,text="Polonium Rate",height=1,width=17,fg_bg=style.label}
|
||||||
|
local po_rate = DataIndicator{parent=rect,x=2,label="",unit="mB/t",format="%12.2f",value=0,lu_colors=lu_cpair,fg_bg=bw_fg_bg,width=17}
|
||||||
|
|
||||||
|
TextBox{parent=rect,x=2,y=17,text="Antimatter Rate",height=1,width=17,fg_bg=style.label}
|
||||||
|
local am_rate = DataIndicator{parent=rect,x=2,label="",unit="\xb5B/t",format="%12.2f",value=0,lu_colors=lu_cpair,fg_bg=bw_fg_bg,width=17}
|
||||||
|
|
||||||
|
pu_rate.register(facility.ps, "pu_rate", pu_rate.update)
|
||||||
|
po_rate.register(facility.ps, "po_rate", po_rate.update)
|
||||||
|
am_rate.register(facility.ps, "am_rate", am_rate.update)
|
||||||
|
|
||||||
|
local sna_count = DataIndicator{parent=rect,x=2,y=20,label="Linked SNAs:",format="%4d",value=0,lu_colors=lu_cpair,width=17}
|
||||||
|
|
||||||
|
sna_count.register(facility.ps, "sna_count", sna_count.update)
|
||||||
end
|
end
|
||||||
|
|
||||||
return new_view
|
return new_view
|
@ -33,41 +33,21 @@ local border = core.border
|
|||||||
|
|
||||||
local period = core.flasher.PERIOD
|
local period = core.flasher.PERIOD
|
||||||
|
|
||||||
local waste_opts = {
|
|
||||||
{
|
|
||||||
text = "Auto",
|
|
||||||
fg_bg = cpair(colors.black, colors.lightGray),
|
|
||||||
active_fg_bg = cpair(colors.white, colors.gray)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text = "Pu",
|
|
||||||
fg_bg = cpair(colors.black, colors.lightGray),
|
|
||||||
active_fg_bg = cpair(colors.black, colors.green)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text = "Po",
|
|
||||||
fg_bg = cpair(colors.black, colors.lightGray),
|
|
||||||
active_fg_bg = cpair(colors.black, colors.cyan)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text = "AM",
|
|
||||||
fg_bg = cpair(colors.black, colors.lightGray),
|
|
||||||
active_fg_bg = cpair(colors.black, colors.purple)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
-- create a unit view
|
-- create a unit view
|
||||||
---@param parent graphics_element parent
|
---@param parent graphics_element parent
|
||||||
---@param id integer
|
---@param id integer
|
||||||
local function init(parent, id)
|
local function init(parent, id)
|
||||||
local unit = iocontrol.get_db().units[id] ---@type ioctl_unit
|
local unit = iocontrol.get_db().units[id] ---@type ioctl_unit
|
||||||
local f_ps = iocontrol.get_db().facility.ps
|
local f_ps = iocontrol.get_db().facility.ps
|
||||||
|
|
||||||
|
local main = Div{parent=parent,x=1,y=1}
|
||||||
|
|
||||||
|
if unit == nil then return main end
|
||||||
|
|
||||||
local u_ps = unit.unit_ps
|
local u_ps = unit.unit_ps
|
||||||
local b_ps = unit.boiler_ps_tbl
|
local b_ps = unit.boiler_ps_tbl
|
||||||
local t_ps = unit.turbine_ps_tbl
|
local t_ps = unit.turbine_ps_tbl
|
||||||
|
|
||||||
local main = Div{parent=parent,x=1,y=1}
|
|
||||||
|
|
||||||
TextBox{parent=main,text="Reactor Unit #" .. id,alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=style.header}
|
TextBox{parent=main,text="Reactor Unit #" .. id,alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=style.header}
|
||||||
|
|
||||||
local bw_fg_bg = cpair(colors.black, colors.white)
|
local bw_fg_bg = cpair(colors.black, colors.white)
|
||||||
@ -398,7 +378,7 @@ local function init(parent, id)
|
|||||||
local waste_proc = Rectangle{parent=main,border=border(1,colors.brown,true),thin=true,width=33,height=3,x=46,y=49}
|
local waste_proc = Rectangle{parent=main,border=border(1,colors.brown,true),thin=true,width=33,height=3,x=46,y=49}
|
||||||
local waste_div = Div{parent=waste_proc,x=2,y=1,width=31,height=1}
|
local waste_div = Div{parent=waste_proc,x=2,y=1,width=31,height=1}
|
||||||
|
|
||||||
local waste_mode = MultiButton{parent=waste_div,x=1,y=1,options=waste_opts,callback=unit.set_waste,min_width=6}
|
local waste_mode = MultiButton{parent=waste_div,x=1,y=1,options=style.waste.unit_opts,callback=unit.set_waste,min_width=6}
|
||||||
|
|
||||||
waste_mode.register(u_ps, "U_WasteMode", waste_mode.set_value)
|
waste_mode.register(u_ps, "U_WasteMode", waste_mode.set_value)
|
||||||
|
|
||||||
|
121
coordinator/ui/layout/front_panel.lua
Normal file
121
coordinator/ui/layout/front_panel.lua
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
--
|
||||||
|
-- Coordinator Front Panel GUI
|
||||||
|
--
|
||||||
|
|
||||||
|
local types = require("scada-common.types")
|
||||||
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
|
local iocontrol = require("coordinator.iocontrol")
|
||||||
|
|
||||||
|
local pgi = require("coordinator.ui.pgi")
|
||||||
|
local style = require("coordinator.ui.style")
|
||||||
|
|
||||||
|
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 TabBar = require("graphics.elements.controls.tabbar")
|
||||||
|
|
||||||
|
local LED = require("graphics.elements.indicators.led")
|
||||||
|
local RGBLED = require("graphics.elements.indicators.ledrgb")
|
||||||
|
|
||||||
|
local TEXT_ALIGN = core.TEXT_ALIGN
|
||||||
|
|
||||||
|
local cpair = core.cpair
|
||||||
|
|
||||||
|
-- create new front panel view
|
||||||
|
---@param panel graphics_element 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
|
||||||
|
|
||||||
|
TextBox{parent=panel,y=1,text="SCADA COORDINATOR",alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=style.fp.header}
|
||||||
|
|
||||||
|
local page_div = Div{parent=panel,x=1,y=3}
|
||||||
|
|
||||||
|
--
|
||||||
|
-- system indicators
|
||||||
|
--
|
||||||
|
|
||||||
|
local main_page = Div{parent=page_div,x=1,y=1}
|
||||||
|
|
||||||
|
local system = Div{parent=main_page,width=14,height=17,x=2,y=2}
|
||||||
|
|
||||||
|
local status = LED{parent=system,label="STATUS",colors=cpair(colors.green,colors.red)}
|
||||||
|
local heartbeat = LED{parent=system,label="HEARTBEAT",colors=cpair(colors.green,colors.green_off)}
|
||||||
|
status.update(true)
|
||||||
|
system.line_break()
|
||||||
|
|
||||||
|
heartbeat.register(ps, "heartbeat", heartbeat.update)
|
||||||
|
|
||||||
|
local modem = LED{parent=system,label="MODEM",colors=cpair(colors.green,colors.green_off)}
|
||||||
|
local network = RGBLED{parent=system,label="NETWORK",colors={colors.green,colors.red,colors.orange,colors.yellow,colors.gray}}
|
||||||
|
network.update(types.PANEL_LINK_STATE.DISCONNECTED)
|
||||||
|
system.line_break()
|
||||||
|
|
||||||
|
modem.register(ps, "has_modem", modem.update)
|
||||||
|
network.register(ps, "link_state", network.update)
|
||||||
|
|
||||||
|
local speaker = LED{parent=system,label="SPEAKER",colors=cpair(colors.green,colors.green_off)}
|
||||||
|
speaker.register(ps, "has_speaker", speaker.update)
|
||||||
|
|
||||||
|
---@diagnostic disable-next-line: undefined-field
|
||||||
|
local comp_id = util.sprintf("(%d)", os.getComputerID())
|
||||||
|
TextBox{parent=system,x=9,y=4,width=6,height=1,text=comp_id,fg_bg=cpair(colors.lightGray,colors.ivory)}
|
||||||
|
|
||||||
|
local monitors = Div{parent=main_page,width=16,height=17,x=18,y=2}
|
||||||
|
|
||||||
|
local main_monitor = LED{parent=monitors,label="MAIN MONITOR",colors=cpair(colors.green,colors.green_off)}
|
||||||
|
main_monitor.register(ps, "main_monitor", main_monitor.update)
|
||||||
|
|
||||||
|
monitors.line_break()
|
||||||
|
|
||||||
|
for i = 1, num_units do
|
||||||
|
local unit_monitor = LED{parent=monitors,label="UNIT "..i.." MONITOR",colors=cpair(colors.green,colors.green_off)}
|
||||||
|
unit_monitor.register(ps, "unit_monitor_" .. i, unit_monitor.update)
|
||||||
|
end
|
||||||
|
|
||||||
|
--
|
||||||
|
-- about footer
|
||||||
|
--
|
||||||
|
|
||||||
|
local about = Div{parent=main_page,width=15,height=3,x=1,y=16,fg_bg=cpair(colors.lightGray,colors.ivory)}
|
||||||
|
local fw_v = TextBox{parent=about,x=1,y=1,text="FW: v00.00.00",alignment=TEXT_ALIGN.LEFT,height=1}
|
||||||
|
local comms_v = TextBox{parent=about,x=1,y=2,text="NT: v00.00.00",alignment=TEXT_ALIGN.LEFT,height=1}
|
||||||
|
|
||||||
|
fw_v.register(ps, "version", function (version) fw_v.set_value(util.c("FW: ", version)) end)
|
||||||
|
comms_v.register(ps, "comms_version", function (version) comms_v.set_value(util.c("NT: v", version)) end)
|
||||||
|
|
||||||
|
--
|
||||||
|
-- page handling
|
||||||
|
--
|
||||||
|
|
||||||
|
-- API page
|
||||||
|
|
||||||
|
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=cpair(colors.black,colors.ivory),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
|
||||||
|
|
||||||
|
-- assemble page panes
|
||||||
|
|
||||||
|
local panes = { main_page, api_page }
|
||||||
|
|
||||||
|
local page_pane = MultiPane{parent=page_div,x=1,y=1,panes=panes}
|
||||||
|
|
||||||
|
local tabs = {
|
||||||
|
{ name = "CRD", color = cpair(colors.black, colors.ivory) },
|
||||||
|
{ name = "API", color = cpair(colors.black, colors.ivory) },
|
||||||
|
}
|
||||||
|
|
||||||
|
TabBar{parent=panel,y=2,tabs=tabs,min_width=9,callback=page_pane.set_value,fg_bg=cpair(colors.black,colors.white)}
|
||||||
|
|
||||||
|
-- link pocket API list management to PGI
|
||||||
|
pgi.link_elements(api_list, pkt_entry)
|
||||||
|
end
|
||||||
|
|
||||||
|
return init
|
@ -9,7 +9,7 @@ local iocontrol = require("coordinator.iocontrol")
|
|||||||
local style = require("coordinator.ui.style")
|
local style = require("coordinator.ui.style")
|
||||||
|
|
||||||
local imatrix = require("coordinator.ui.components.imatrix")
|
local imatrix = require("coordinator.ui.components.imatrix")
|
||||||
local process_ctl = require("coordinator.ui.components.processctl")
|
local process_ctl = require("coordinator.ui.components.process_ctl")
|
||||||
local unit_overview = require("coordinator.ui.components.unit_overview")
|
local unit_overview = require("coordinator.ui.components.unit_overview")
|
||||||
|
|
||||||
local core = require("graphics.core")
|
local core = require("graphics.core")
|
||||||
|
58
coordinator/ui/pgi.lua
Normal file
58
coordinator/ui/pgi.lua
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
--
|
||||||
|
-- Protected Graphics Interface
|
||||||
|
--
|
||||||
|
|
||||||
|
local log = require("scada-common.log")
|
||||||
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
|
local pgi = {}
|
||||||
|
|
||||||
|
local data = {
|
||||||
|
pkt_list = nil, ---@type nil|graphics_element
|
||||||
|
pkt_entry = nil, ---@type function
|
||||||
|
-- session entries
|
||||||
|
s_entries = { pkt = {} }
|
||||||
|
}
|
||||||
|
|
||||||
|
-- link list boxes
|
||||||
|
---@param pkt_list graphics_element pocket list element
|
||||||
|
---@param pkt_entry function pocket entry constructor
|
||||||
|
function pgi.link_elements(pkt_list, pkt_entry)
|
||||||
|
data.pkt_list = pkt_list
|
||||||
|
data.pkt_entry = pkt_entry
|
||||||
|
end
|
||||||
|
|
||||||
|
-- unlink all fields, disabling the PGI
|
||||||
|
function pgi.unlink()
|
||||||
|
data.pkt_list = nil
|
||||||
|
data.pkt_entry = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- add a PKT entry to the PKT list
|
||||||
|
---@param session_id integer pocket session
|
||||||
|
function pgi.create_pkt_entry(session_id)
|
||||||
|
if data.pkt_list ~= nil and data.pkt_entry ~= nil then
|
||||||
|
local success, result = pcall(data.pkt_entry, data.pkt_list, session_id)
|
||||||
|
|
||||||
|
if success then
|
||||||
|
data.s_entries.pkt[session_id] = result
|
||||||
|
else
|
||||||
|
log.error(util.c("PGI: failed to create PKT entry (", result, ")"), true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- delete a PKT entry from the PKT list
|
||||||
|
---@param session_id integer pocket session
|
||||||
|
function pgi.delete_pkt_entry(session_id)
|
||||||
|
if data.s_entries.pkt[session_id] ~= nil then
|
||||||
|
local success, result = pcall(data.s_entries.pkt[session_id].delete)
|
||||||
|
data.s_entries.pkt[session_id] = nil
|
||||||
|
|
||||||
|
if not success then
|
||||||
|
log.error(util.c("PGI: failed to delete PKT entry (", result, ")"), true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return pgi
|
@ -10,6 +10,41 @@ local cpair = core.cpair
|
|||||||
|
|
||||||
-- GLOBAL --
|
-- GLOBAL --
|
||||||
|
|
||||||
|
-- add color mappings for front panel
|
||||||
|
colors.ivory = colors.pink
|
||||||
|
colors.yellow_hc = colors.purple
|
||||||
|
colors.red_off = colors.brown
|
||||||
|
colors.yellow_off = colors.magenta
|
||||||
|
colors.green_off = colors.lime
|
||||||
|
|
||||||
|
-- front panel styling
|
||||||
|
|
||||||
|
style.fp = {}
|
||||||
|
|
||||||
|
style.fp.root = cpair(colors.black, colors.ivory)
|
||||||
|
style.fp.header = cpair(colors.black, colors.lightGray)
|
||||||
|
|
||||||
|
style.fp.colors = {
|
||||||
|
{ c = colors.red, hex = 0xdf4949 }, -- RED ON
|
||||||
|
{ c = colors.orange, hex = 0xffb659 },
|
||||||
|
{ c = colors.yellow, hex = 0xf9fb53 }, -- YELLOW ON
|
||||||
|
{ c = colors.lime, hex = 0x16665a }, -- GREEN OFF
|
||||||
|
{ c = colors.green, hex = 0x6be551 }, -- GREEN ON
|
||||||
|
{ c = colors.cyan, hex = 0x34bac8 },
|
||||||
|
{ c = colors.lightBlue, hex = 0x6cc0f2 },
|
||||||
|
{ c = colors.blue, hex = 0x0096ff },
|
||||||
|
{ c = colors.purple, hex = 0xb156ee }, -- YELLOW HIGH CONTRAST
|
||||||
|
{ c = colors.pink, hex = 0xdcd9ca }, -- IVORY
|
||||||
|
{ c = colors.magenta, hex = 0x85862c }, -- YELLOW OFF
|
||||||
|
-- { c = colors.white, hex = 0xdcd9ca },
|
||||||
|
{ c = colors.lightGray, hex = 0xb1b8b3 },
|
||||||
|
{ c = colors.gray, hex = 0x575757 },
|
||||||
|
-- { c = colors.black, hex = 0x191919 },
|
||||||
|
{ c = colors.brown, hex = 0x672223 } -- RED OFF
|
||||||
|
}
|
||||||
|
|
||||||
|
-- main GUI styling
|
||||||
|
|
||||||
style.root = cpair(colors.black, colors.lightGray)
|
style.root = cpair(colors.black, colors.lightGray)
|
||||||
style.header = cpair(colors.white, colors.gray)
|
style.header = cpair(colors.white, colors.gray)
|
||||||
style.label = cpair(colors.gray, colors.lightGray)
|
style.label = cpair(colors.gray, colors.lightGray)
|
||||||
@ -151,7 +186,90 @@ style.imatrix = {
|
|||||||
{
|
{
|
||||||
color = cpair(colors.black, colors.yellow),
|
color = cpair(colors.black, colors.yellow),
|
||||||
text = "HIGH CHARGE"
|
text = "HIGH CHARGE"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
style.sps = {
|
||||||
|
-- SPS states
|
||||||
|
states = {
|
||||||
|
{
|
||||||
|
color = cpair(colors.black, colors.yellow),
|
||||||
|
text = "OFF-LINE"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
color = cpair(colors.black, colors.orange),
|
||||||
|
text = "NOT FORMED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
color = cpair(colors.black, colors.orange),
|
||||||
|
text = "RTU FAULT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
color = cpair(colors.black, colors.gray),
|
||||||
|
text = "IDLE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
color = cpair(colors.black, colors.green),
|
||||||
|
text = "ACTIVE"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
style.waste = {
|
||||||
|
-- auto waste processing states
|
||||||
|
states = {
|
||||||
|
{
|
||||||
|
color = cpair(colors.black, colors.green),
|
||||||
|
text = "PLUTONIUM"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
color = cpair(colors.black, colors.cyan),
|
||||||
|
text = "POLONIUM"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
color = cpair(colors.black, colors.purple),
|
||||||
|
text = "ANTI MATTER"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
states_abbrv = {
|
||||||
|
{
|
||||||
|
color = cpair(colors.black, colors.green),
|
||||||
|
text = "Pu"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
color = cpair(colors.black, colors.cyan),
|
||||||
|
text = "Po"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
color = cpair(colors.black, colors.purple),
|
||||||
|
text = "AM"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
-- process radio button options
|
||||||
|
options = { "Plutonium", "Polonium", "Antimatter" },
|
||||||
|
-- unit waste selection
|
||||||
|
unit_opts = {
|
||||||
|
{
|
||||||
|
text = "Auto",
|
||||||
|
fg_bg = cpair(colors.black, colors.lightGray),
|
||||||
|
active_fg_bg = cpair(colors.white, colors.gray)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text = "Pu",
|
||||||
|
fg_bg = cpair(colors.black, colors.lightGray),
|
||||||
|
active_fg_bg = cpair(colors.black, colors.green)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text = "Po",
|
||||||
|
fg_bg = cpair(colors.black, colors.lightGray),
|
||||||
|
active_fg_bg = cpair(colors.black, colors.cyan)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text = "AM",
|
||||||
|
fg_bg = cpair(colors.black, colors.lightGray),
|
||||||
|
active_fg_bg = cpair(colors.black, colors.purple)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ local flasher = require("graphics.flasher")
|
|||||||
|
|
||||||
local core = {}
|
local core = {}
|
||||||
|
|
||||||
core.version = "1.0.0"
|
core.version = "1.0.1"
|
||||||
|
|
||||||
core.flasher = flasher
|
core.flasher = flasher
|
||||||
core.events = events
|
core.events = events
|
||||||
|
@ -20,6 +20,7 @@ local element = {}
|
|||||||
|
|
||||||
---@alias graphics_args graphics_args_generic
|
---@alias graphics_args graphics_args_generic
|
||||||
---|waiting_args
|
---|waiting_args
|
||||||
|
---|checkbox_args
|
||||||
---|hazard_button_args
|
---|hazard_button_args
|
||||||
---|multi_button_args
|
---|multi_button_args
|
||||||
---|push_button_args
|
---|push_button_args
|
||||||
|
@ -8,7 +8,7 @@ local element = require("graphics.element")
|
|||||||
---@field parent graphics_element
|
---@field parent graphics_element
|
||||||
---@field id? string element id
|
---@field id? string element id
|
||||||
---@field x? integer 1 if omitted
|
---@field x? integer 1 if omitted
|
||||||
---@field y? integer 1 if omitted
|
---@field y? integer auto incremented if omitted
|
||||||
---@field fg_bg? cpair foreground/background colors
|
---@field fg_bg? cpair foreground/background colors
|
||||||
---@field hidden? boolean true to hide on initial draw
|
---@field hidden? boolean true to hide on initial draw
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ local element = require("graphics.element")
|
|||||||
---@field parent graphics_element
|
---@field parent graphics_element
|
||||||
---@field id? string element id
|
---@field id? string element id
|
||||||
---@field x? integer 1 if omitted
|
---@field x? integer 1 if omitted
|
||||||
---@field y? integer 1 if omitted
|
---@field y? integer auto incremented if omitted
|
||||||
---@field hidden? boolean true to hide on initial draw
|
---@field hidden? boolean true to hide on initial draw
|
||||||
|
|
||||||
-- new color map
|
-- new color map
|
||||||
|
85
graphics/elements/controls/checkbox.lua
Normal file
85
graphics/elements/controls/checkbox.lua
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
-- Checkbox Graphics Element
|
||||||
|
|
||||||
|
local core = require("graphics.core")
|
||||||
|
local element = require("graphics.element")
|
||||||
|
|
||||||
|
---@class checkbox_args
|
||||||
|
---@field label string checkbox text
|
||||||
|
---@field box_fg_bg cpair colors for checkbox
|
||||||
|
---@field callback function function to call on press
|
||||||
|
---@field parent graphics_element
|
||||||
|
---@field id? string element id
|
||||||
|
---@field x? integer 1 if omitted
|
||||||
|
---@field y? integer auto incremented if omitted
|
||||||
|
---@field fg_bg? cpair foreground/background colors
|
||||||
|
---@field hidden? boolean true to hide on initial draw
|
||||||
|
|
||||||
|
-- new checkbox control
|
||||||
|
---@param args checkbox_args
|
||||||
|
---@return graphics_element element, element_id id
|
||||||
|
local function checkbox(args)
|
||||||
|
assert(type(args.label) == "string", "graphics.elements.controls.checkbox: label is a required field")
|
||||||
|
assert(type(args.box_fg_bg) == "table", "graphics.elements.controls.checkbox: box_fg_bg is a required field")
|
||||||
|
assert(type(args.callback) == "function", "graphics.elements.controls.checkbox: callback is a required field")
|
||||||
|
|
||||||
|
args.height = 1
|
||||||
|
args.width = 3 + string.len(args.label)
|
||||||
|
|
||||||
|
-- create new graphics element base object
|
||||||
|
local e = element.new(args)
|
||||||
|
|
||||||
|
e.value = false
|
||||||
|
|
||||||
|
-- show the button state
|
||||||
|
local function draw()
|
||||||
|
e.window.setCursorPos(1, 1)
|
||||||
|
|
||||||
|
if e.value then
|
||||||
|
-- show as selected
|
||||||
|
e.window.setTextColor(args.box_fg_bg.bkg)
|
||||||
|
e.window.setBackgroundColor(args.box_fg_bg.fgd)
|
||||||
|
e.window.write("\x88")
|
||||||
|
e.window.setTextColor(args.box_fg_bg.fgd)
|
||||||
|
e.window.setBackgroundColor(e.fg_bg.bkg)
|
||||||
|
e.window.write("\x95")
|
||||||
|
else
|
||||||
|
-- show as unselected
|
||||||
|
e.window.setTextColor(e.fg_bg.bkg)
|
||||||
|
e.window.setBackgroundColor(args.box_fg_bg.bkg)
|
||||||
|
e.window.write("\x88")
|
||||||
|
e.window.setTextColor(args.box_fg_bg.bkg)
|
||||||
|
e.window.setBackgroundColor(e.fg_bg.bkg)
|
||||||
|
e.window.write("\x95")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- handle mouse interaction
|
||||||
|
---@param event mouse_interaction mouse event
|
||||||
|
function e.handle_mouse(event)
|
||||||
|
if e.enabled and core.events.was_clicked(event.type) then
|
||||||
|
e.value = not e.value
|
||||||
|
draw()
|
||||||
|
args.callback(e.value)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- set the value
|
||||||
|
---@param val integer new value
|
||||||
|
function e.set_value(val)
|
||||||
|
e.value = val
|
||||||
|
draw()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- write label text
|
||||||
|
e.window.setCursorPos(3, 1)
|
||||||
|
e.window.setTextColor(e.fg_bg.fgd)
|
||||||
|
e.window.setBackgroundColor(e.fg_bg.bkg)
|
||||||
|
e.window.write(args.label)
|
||||||
|
|
||||||
|
-- initial draw
|
||||||
|
draw()
|
||||||
|
|
||||||
|
return e.complete()
|
||||||
|
end
|
||||||
|
|
||||||
|
return checkbox
|
@ -14,7 +14,7 @@ local element = require("graphics.element")
|
|||||||
---@field parent graphics_element
|
---@field parent graphics_element
|
||||||
---@field id? string element id
|
---@field id? string element id
|
||||||
---@field x? integer 1 if omitted
|
---@field x? integer 1 if omitted
|
||||||
---@field y? integer 1 if omitted
|
---@field y? integer auto incremented if omitted
|
||||||
---@field fg_bg? cpair foreground/background colors
|
---@field fg_bg? cpair foreground/background colors
|
||||||
---@field hidden? boolean true to hide on initial draw
|
---@field hidden? boolean true to hide on initial draw
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ local element = require("graphics.element")
|
|||||||
---@field parent graphics_element
|
---@field parent graphics_element
|
||||||
---@field id? string element id
|
---@field id? string element id
|
||||||
---@field x? integer 1 if omitted
|
---@field x? integer 1 if omitted
|
||||||
---@field y? integer 1 if omitted
|
---@field y? integer auto incremented if omitted
|
||||||
---@field height? integer parent height if omitted
|
---@field height? integer parent height if omitted
|
||||||
---@field fg_bg? cpair foreground/background colors
|
---@field fg_bg? cpair foreground/background colors
|
||||||
---@field hidden? boolean true to hide on initial draw
|
---@field hidden? boolean true to hide on initial draw
|
||||||
|
@ -16,7 +16,7 @@ local CLICK_TYPE = core.events.CLICK_TYPE
|
|||||||
---@field parent graphics_element
|
---@field parent graphics_element
|
||||||
---@field id? string element id
|
---@field id? string element id
|
||||||
---@field x? integer 1 if omitted
|
---@field x? integer 1 if omitted
|
||||||
---@field y? integer 1 if omitted
|
---@field y? integer auto incremented if omitted
|
||||||
---@field height? integer parent height if omitted
|
---@field height? integer parent height if omitted
|
||||||
---@field fg_bg? cpair foreground/background colors
|
---@field fg_bg? cpair foreground/background colors
|
||||||
---@field hidden? boolean true to hide on initial draw
|
---@field hidden? boolean true to hide on initial draw
|
||||||
|
@ -13,7 +13,7 @@ local element = require("graphics.element")
|
|||||||
---@field parent graphics_element
|
---@field parent graphics_element
|
||||||
---@field id? string element id
|
---@field id? string element id
|
||||||
---@field x? integer 1 if omitted
|
---@field x? integer 1 if omitted
|
||||||
---@field y? integer 1 if omitted
|
---@field y? integer auto incremented if omitted
|
||||||
---@field fg_bg? cpair foreground/background colors
|
---@field fg_bg? cpair foreground/background colors
|
||||||
---@field hidden? boolean true to hide on initial draw
|
---@field hidden? boolean true to hide on initial draw
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ local CLICK_TYPE = core.events.CLICK_TYPE
|
|||||||
---@field parent graphics_element
|
---@field parent graphics_element
|
||||||
---@field id? string element id
|
---@field id? string element id
|
||||||
---@field x? integer 1 if omitted
|
---@field x? integer 1 if omitted
|
||||||
---@field y? integer 1 if omitted
|
---@field y? integer auto incremented if omitted
|
||||||
---@field height? integer parent height if omitted
|
---@field height? integer parent height if omitted
|
||||||
---@field fg_bg? cpair foreground/background colors
|
---@field fg_bg? cpair foreground/background colors
|
||||||
---@field hidden? boolean true to hide on initial draw
|
---@field hidden? boolean true to hide on initial draw
|
||||||
|
@ -16,7 +16,7 @@ local element = require("graphics.element")
|
|||||||
---@field parent graphics_element
|
---@field parent graphics_element
|
||||||
---@field id? string element id
|
---@field id? string element id
|
||||||
---@field x? integer 1 if omitted
|
---@field x? integer 1 if omitted
|
||||||
---@field y? integer 1 if omitted
|
---@field y? integer auto incremented if omitted
|
||||||
---@field fg_bg? cpair foreground/background colors
|
---@field fg_bg? cpair foreground/background colors
|
||||||
---@field hidden? boolean true to hide on initial draw
|
---@field hidden? boolean true to hide on initial draw
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ local element = require("graphics.element")
|
|||||||
---@field parent graphics_element
|
---@field parent graphics_element
|
||||||
---@field id? string element id
|
---@field id? string element id
|
||||||
---@field x? integer 1 if omitted
|
---@field x? integer 1 if omitted
|
||||||
---@field y? integer 1 if omitted
|
---@field y? integer auto incremented if omitted
|
||||||
---@field height? integer parent height if omitted
|
---@field height? integer parent height if omitted
|
||||||
---@field fg_bg? cpair foreground/background colors
|
---@field fg_bg? cpair foreground/background colors
|
||||||
---@field hidden? boolean true to hide on initial draw
|
---@field hidden? boolean true to hide on initial draw
|
||||||
|
@ -18,7 +18,7 @@ local element = require("graphics.element")
|
|||||||
---@field parent graphics_element
|
---@field parent graphics_element
|
||||||
---@field id? string element id
|
---@field id? string element id
|
||||||
---@field x? integer 1 if omitted
|
---@field x? integer 1 if omitted
|
||||||
---@field y? integer 1 if omitted
|
---@field y? integer auto incremented if omitted
|
||||||
---@field width? integer parent width if omitted
|
---@field width? integer parent width if omitted
|
||||||
---@field fg_bg? cpair foreground/background colors
|
---@field fg_bg? cpair foreground/background colors
|
||||||
---@field hidden? boolean true to hide on initial draw
|
---@field hidden? boolean true to hide on initial draw
|
||||||
|
@ -6,7 +6,7 @@ local element = require("graphics.element")
|
|||||||
---@field parent graphics_element
|
---@field parent graphics_element
|
||||||
---@field id? string element id
|
---@field id? string element id
|
||||||
---@field x? integer 1 if omitted
|
---@field x? integer 1 if omitted
|
||||||
---@field y? integer 1 if omitted
|
---@field y? integer auto incremented if omitted
|
||||||
---@field width? integer parent width if omitted
|
---@field width? integer parent width if omitted
|
||||||
---@field height? integer parent height if omitted
|
---@field height? integer parent height if omitted
|
||||||
---@field gframe? graphics_frame frame instead of x/y/width/height
|
---@field gframe? graphics_frame frame instead of x/y/width/height
|
||||||
|
@ -16,7 +16,7 @@ local flasher = require("graphics.flasher")
|
|||||||
---@field parent graphics_element
|
---@field parent graphics_element
|
||||||
---@field id? string element id
|
---@field id? string element id
|
||||||
---@field x? integer 1 if omitted
|
---@field x? integer 1 if omitted
|
||||||
---@field y? integer 1 if omitted
|
---@field y? integer auto incremented if omitted
|
||||||
---@field fg_bg? cpair foreground/background colors
|
---@field fg_bg? cpair foreground/background colors
|
||||||
---@field hidden? boolean true to hide on initial draw
|
---@field hidden? boolean true to hide on initial draw
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ local element = require("graphics.element")
|
|||||||
---@field parent graphics_element
|
---@field parent graphics_element
|
||||||
---@field id? string element id
|
---@field id? string element id
|
||||||
---@field x? integer 1 if omitted
|
---@field x? integer 1 if omitted
|
||||||
---@field y? integer 1 if omitted
|
---@field y? integer auto incremented if omitted
|
||||||
|
|
||||||
-- new core map box
|
-- new core map box
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
|
@ -14,7 +14,7 @@ local element = require("graphics.element")
|
|||||||
---@field parent graphics_element
|
---@field parent graphics_element
|
||||||
---@field id? string element id
|
---@field id? string element id
|
||||||
---@field x? integer 1 if omitted
|
---@field x? integer 1 if omitted
|
||||||
---@field y? integer 1 if omitted
|
---@field y? integer auto incremented if omitted
|
||||||
---@field width integer length
|
---@field width integer length
|
||||||
---@field fg_bg? cpair foreground/background colors
|
---@field fg_bg? cpair foreground/background colors
|
||||||
---@field hidden? boolean true to hide on initial draw
|
---@field hidden? boolean true to hide on initial draw
|
||||||
|
@ -10,7 +10,7 @@ local element = require("graphics.element")
|
|||||||
---@field parent graphics_element
|
---@field parent graphics_element
|
||||||
---@field id? string element id
|
---@field id? string element id
|
||||||
---@field x? integer 1 if omitted
|
---@field x? integer 1 if omitted
|
||||||
---@field y? integer 1 if omitted
|
---@field y? integer auto incremented if omitted
|
||||||
---@field width? integer parent width if omitted
|
---@field width? integer parent width if omitted
|
||||||
---@field height? integer parent height if omitted
|
---@field height? integer parent height if omitted
|
||||||
---@field gframe? graphics_frame frame instead of x/y/width/height
|
---@field gframe? graphics_frame frame instead of x/y/width/height
|
||||||
|
@ -16,7 +16,7 @@ local element = require("graphics.element")
|
|||||||
---@field parent graphics_element
|
---@field parent graphics_element
|
||||||
---@field id? string element id
|
---@field id? string element id
|
||||||
---@field x? integer 1 if omitted
|
---@field x? integer 1 if omitted
|
||||||
---@field y? integer 1 if omitted
|
---@field y? integer auto incremented if omitted
|
||||||
---@field fg_bg? cpair foreground/background colors
|
---@field fg_bg? cpair foreground/background colors
|
||||||
---@field hidden? boolean true to hide on initial draw
|
---@field hidden? boolean true to hide on initial draw
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ local flasher = require("graphics.flasher")
|
|||||||
---@field parent graphics_element
|
---@field parent graphics_element
|
||||||
---@field id? string element id
|
---@field id? string element id
|
||||||
---@field x? integer 1 if omitted
|
---@field x? integer 1 if omitted
|
||||||
---@field y? integer 1 if omitted
|
---@field y? integer auto incremented if omitted
|
||||||
---@field fg_bg? cpair foreground/background colors
|
---@field fg_bg? cpair foreground/background colors
|
||||||
---@field hidden? boolean true to hide on initial draw
|
---@field hidden? boolean true to hide on initial draw
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ local flasher = require("graphics.flasher")
|
|||||||
---@field parent graphics_element
|
---@field parent graphics_element
|
||||||
---@field id? string element id
|
---@field id? string element id
|
||||||
---@field x? integer 1 if omitted
|
---@field x? integer 1 if omitted
|
||||||
---@field y? integer 1 if omitted
|
---@field y? integer auto incremented if omitted
|
||||||
---@field fg_bg? cpair foreground/background colors
|
---@field fg_bg? cpair foreground/background colors
|
||||||
---@field hidden? boolean true to hide on initial draw
|
---@field hidden? boolean true to hide on initial draw
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ local element = require("graphics.element")
|
|||||||
---@field parent graphics_element
|
---@field parent graphics_element
|
||||||
---@field id? string element id
|
---@field id? string element id
|
||||||
---@field x? integer 1 if omitted
|
---@field x? integer 1 if omitted
|
||||||
---@field y? integer 1 if omitted
|
---@field y? integer auto incremented if omitted
|
||||||
---@field fg_bg? cpair foreground/background colors
|
---@field fg_bg? cpair foreground/background colors
|
||||||
---@field hidden? boolean true to hide on initial draw
|
---@field hidden? boolean true to hide on initial draw
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ local flasher = require("graphics.flasher")
|
|||||||
---@field parent graphics_element
|
---@field parent graphics_element
|
||||||
---@field id? string element id
|
---@field id? string element id
|
||||||
---@field x? integer 1 if omitted
|
---@field x? integer 1 if omitted
|
||||||
---@field y? integer 1 if omitted
|
---@field y? integer auto incremented if omitted
|
||||||
---@field fg_bg? cpair foreground/background colors
|
---@field fg_bg? cpair foreground/background colors
|
||||||
---@field hidden? boolean true to hide on initial draw
|
---@field hidden? boolean true to hide on initial draw
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ local element = require("graphics.element")
|
|||||||
---@field parent graphics_element
|
---@field parent graphics_element
|
||||||
---@field id? string element id
|
---@field id? string element id
|
||||||
---@field x? integer 1 if omitted
|
---@field x? integer 1 if omitted
|
||||||
---@field y? integer 1 if omitted
|
---@field y? integer auto incremented if omitted
|
||||||
---@field width integer length
|
---@field width integer length
|
||||||
---@field fg_bg? cpair foreground/background colors
|
---@field fg_bg? cpair foreground/background colors
|
||||||
---@field hidden? boolean true to hide on initial draw
|
---@field hidden? boolean true to hide on initial draw
|
||||||
|
@ -14,7 +14,7 @@ local element = require("graphics.element")
|
|||||||
---@field parent graphics_element
|
---@field parent graphics_element
|
||||||
---@field id? string element id
|
---@field id? string element id
|
||||||
---@field x? integer 1 if omitted
|
---@field x? integer 1 if omitted
|
||||||
---@field y? integer 1 if omitted
|
---@field y? integer auto incremented if omitted
|
||||||
---@field width integer length
|
---@field width integer length
|
||||||
---@field fg_bg? cpair foreground/background colors
|
---@field fg_bg? cpair foreground/background colors
|
||||||
---@field hidden? boolean true to hide on initial draw
|
---@field hidden? boolean true to hide on initial draw
|
||||||
|
@ -15,7 +15,7 @@ local element = require("graphics.element")
|
|||||||
---@field parent graphics_element
|
---@field parent graphics_element
|
||||||
---@field id? string element id
|
---@field id? string element id
|
||||||
---@field x? integer 1 if omitted
|
---@field x? integer 1 if omitted
|
||||||
---@field y? integer 1 if omitted
|
---@field y? integer auto incremented if omitted
|
||||||
---@field height? integer 1 if omitted, must be an odd number
|
---@field height? integer 1 if omitted, must be an odd number
|
||||||
---@field fg_bg? cpair foreground/background colors
|
---@field fg_bg? cpair foreground/background colors
|
||||||
---@field hidden? boolean true to hide on initial draw
|
---@field hidden? boolean true to hide on initial draw
|
||||||
|
@ -16,7 +16,7 @@ local flasher = require("graphics.flasher")
|
|||||||
---@field parent graphics_element
|
---@field parent graphics_element
|
||||||
---@field id? string element id
|
---@field id? string element id
|
||||||
---@field x? integer 1 if omitted
|
---@field x? integer 1 if omitted
|
||||||
---@field y? integer 1 if omitted
|
---@field y? integer auto incremented if omitted
|
||||||
---@field fg_bg? cpair foreground/background colors
|
---@field fg_bg? cpair foreground/background colors
|
||||||
---@field hidden? boolean true to hide on initial draw
|
---@field hidden? boolean true to hide on initial draw
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ local element = require("graphics.element")
|
|||||||
---@field parent graphics_element
|
---@field parent graphics_element
|
||||||
---@field id? string element id
|
---@field id? string element id
|
||||||
---@field x? integer 1 if omitted
|
---@field x? integer 1 if omitted
|
||||||
---@field y? integer 1 if omitted
|
---@field y? integer auto incremented if omitted
|
||||||
---@field width? integer parent width if omitted
|
---@field width? integer parent width if omitted
|
||||||
---@field height? integer parent height if omitted
|
---@field height? integer parent height if omitted
|
||||||
---@field gframe? graphics_frame frame instead of x/y/width/height
|
---@field gframe? graphics_frame frame instead of x/y/width/height
|
||||||
|
@ -15,7 +15,7 @@ local CLICK_TYPE = core.events.CLICK_TYPE
|
|||||||
---@field parent graphics_element
|
---@field parent graphics_element
|
||||||
---@field id? string element id
|
---@field id? string element id
|
||||||
---@field x? integer 1 if omitted
|
---@field x? integer 1 if omitted
|
||||||
---@field y? integer 1 if omitted
|
---@field y? integer auto incremented if omitted
|
||||||
---@field width? integer parent width if omitted
|
---@field width? integer parent width if omitted
|
||||||
---@field height? integer parent height if omitted
|
---@field height? integer parent height if omitted
|
||||||
---@field gframe? graphics_frame frame instead of x/y/width/height
|
---@field gframe? graphics_frame frame instead of x/y/width/height
|
||||||
|
@ -7,7 +7,7 @@ local element = require("graphics.element")
|
|||||||
---@field parent graphics_element
|
---@field parent graphics_element
|
||||||
---@field id? string element id
|
---@field id? string element id
|
||||||
---@field x? integer 1 if omitted
|
---@field x? integer 1 if omitted
|
||||||
---@field y? integer 1 if omitted
|
---@field y? integer auto incremented if omitted
|
||||||
---@field width? integer parent width if omitted
|
---@field width? integer parent width if omitted
|
||||||
---@field height? integer parent height if omitted
|
---@field height? integer parent height if omitted
|
||||||
---@field gframe? graphics_frame frame instead of x/y/width/height
|
---@field gframe? graphics_frame frame instead of x/y/width/height
|
||||||
|
@ -11,7 +11,7 @@ local element = require("graphics.element")
|
|||||||
---@field parent graphics_element
|
---@field parent graphics_element
|
||||||
---@field id? string element id
|
---@field id? string element id
|
||||||
---@field x? integer 1 if omitted
|
---@field x? integer 1 if omitted
|
||||||
---@field y? integer 1 if omitted
|
---@field y? integer auto incremented if omitted
|
||||||
---@field hidden? boolean true to hide on initial draw
|
---@field hidden? boolean true to hide on initial draw
|
||||||
|
|
||||||
-- new pipe network
|
-- new pipe network
|
||||||
|
@ -11,7 +11,7 @@ local element = require("graphics.element")
|
|||||||
---@field parent graphics_element
|
---@field parent graphics_element
|
||||||
---@field id? string element id
|
---@field id? string element id
|
||||||
---@field x? integer 1 if omitted
|
---@field x? integer 1 if omitted
|
||||||
---@field y? integer 1 if omitted
|
---@field y? integer auto incremented if omitted
|
||||||
---@field width? integer parent width if omitted
|
---@field width? integer parent width if omitted
|
||||||
---@field height? integer parent height if omitted
|
---@field height? integer parent height if omitted
|
||||||
---@field gframe? graphics_frame frame instead of x/y/width/height
|
---@field gframe? graphics_frame frame instead of x/y/width/height
|
||||||
|
@ -13,7 +13,7 @@ local TEXT_ALIGN = core.TEXT_ALIGN
|
|||||||
---@field parent graphics_element
|
---@field parent graphics_element
|
||||||
---@field id? string element id
|
---@field id? string element id
|
||||||
---@field x? integer 1 if omitted
|
---@field x? integer 1 if omitted
|
||||||
---@field y? integer 1 if omitted
|
---@field y? integer auto incremented if omitted
|
||||||
---@field width? integer parent width if omitted
|
---@field width? integer parent width if omitted
|
||||||
---@field height? integer parent height if omitted
|
---@field height? integer parent height if omitted
|
||||||
---@field gframe? graphics_frame frame instead of x/y/width/height
|
---@field gframe? graphics_frame frame instead of x/y/width/height
|
||||||
|
@ -11,7 +11,7 @@ local element = require("graphics.element")
|
|||||||
---@field parent graphics_element
|
---@field parent graphics_element
|
||||||
---@field id? string element id
|
---@field id? string element id
|
||||||
---@field x? integer 1 if omitted
|
---@field x? integer 1 if omitted
|
||||||
---@field y? integer 1 if omitted
|
---@field y? integer auto incremented if omitted
|
||||||
---@field width? integer parent width if omitted
|
---@field width? integer parent width if omitted
|
||||||
---@field height? integer parent height if omitted
|
---@field height? integer parent height if omitted
|
||||||
---@field gframe? graphics_frame frame instead of x/y/width/height
|
---@field gframe? graphics_frame frame instead of x/y/width/height
|
||||||
|
@ -43,8 +43,10 @@ end
|
|||||||
|
|
||||||
-- start/resume the flasher periodic
|
-- start/resume the flasher periodic
|
||||||
function flasher.run()
|
function flasher.run()
|
||||||
active = true
|
if not active then
|
||||||
callback_250ms()
|
active = true
|
||||||
|
callback_250ms()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- clear all blinking indicators and stop the flasher periodic
|
-- clear all blinking indicators and stop the flasher periodic
|
||||||
|
File diff suppressed because one or more lines are too long
@ -18,7 +18,7 @@ local coreio = require("pocket.coreio")
|
|||||||
local pocket = require("pocket.pocket")
|
local pocket = require("pocket.pocket")
|
||||||
local renderer = require("pocket.renderer")
|
local renderer = require("pocket.renderer")
|
||||||
|
|
||||||
local POCKET_VERSION = "alpha-v0.5.1"
|
local POCKET_VERSION = "alpha-v0.5.2"
|
||||||
|
|
||||||
local println = util.println
|
local println = util.println
|
||||||
local println_ts = util.println_ts
|
local println_ts = util.println_ts
|
||||||
@ -112,7 +112,7 @@ local function main()
|
|||||||
if not ui_ok then
|
if not ui_ok then
|
||||||
renderer.close_ui()
|
renderer.close_ui()
|
||||||
println(util.c("UI error: ", message))
|
println(util.c("UI error: ", message))
|
||||||
log.error(util.c("startup> GUI crashed with error ", message))
|
log.error(util.c("startup> GUI render failed with error ", message))
|
||||||
else
|
else
|
||||||
-- start clock
|
-- start clock
|
||||||
loop_clock.start()
|
loop_clock.start()
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
--
|
--
|
||||||
-- Main SCADA Coordinator GUI
|
-- Reactor PLC Front Panel GUI
|
||||||
--
|
--
|
||||||
|
|
||||||
local types = require("scada-common.types")
|
local types = require("scada-common.types")
|
||||||
@ -28,7 +28,7 @@ local TEXT_ALIGN = core.TEXT_ALIGN
|
|||||||
local cpair = core.cpair
|
local cpair = core.cpair
|
||||||
local border = core.border
|
local border = core.border
|
||||||
|
|
||||||
-- create new main view
|
-- create new front panel view
|
||||||
---@param panel graphics_element main displaybox
|
---@param panel graphics_element main displaybox
|
||||||
local function init(panel)
|
local function init(panel)
|
||||||
local header = TextBox{parent=panel,y=1,text="REACTOR PLC - UNIT ?",alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=style.header}
|
local header = TextBox{parent=panel,y=1,text="REACTOR PLC - UNIT ?",alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=style.header}
|
||||||
|
@ -12,6 +12,7 @@ local cpair = core.cpair
|
|||||||
|
|
||||||
-- remap global colors
|
-- remap global colors
|
||||||
colors.ivory = colors.pink
|
colors.ivory = colors.pink
|
||||||
|
colors.yellow_hc = colors.purple
|
||||||
colors.red_off = colors.brown
|
colors.red_off = colors.brown
|
||||||
colors.yellow_off = colors.magenta
|
colors.yellow_off = colors.magenta
|
||||||
colors.green_off = colors.lime
|
colors.green_off = colors.lime
|
||||||
@ -28,7 +29,7 @@ style.colors = {
|
|||||||
{ c = colors.cyan, hex = 0x34bac8 },
|
{ c = colors.cyan, hex = 0x34bac8 },
|
||||||
{ c = colors.lightBlue, hex = 0x6cc0f2 },
|
{ c = colors.lightBlue, hex = 0x6cc0f2 },
|
||||||
{ c = colors.blue, hex = 0x0096ff },
|
{ c = colors.blue, hex = 0x0096ff },
|
||||||
{ c = colors.purple, hex = 0xb156ee },
|
{ c = colors.purple, hex = 0xb156ee }, -- YELLOW HIGH CONTRAST
|
||||||
{ c = colors.pink, hex = 0xdcd9ca }, -- IVORY
|
{ c = colors.pink, hex = 0xdcd9ca }, -- IVORY
|
||||||
{ c = colors.magenta, hex = 0x85862c }, -- YELLOW OFF
|
{ c = colors.magenta, hex = 0x85862c }, -- YELLOW OFF
|
||||||
-- { c = colors.white, hex = 0xdcd9ca },
|
-- { c = colors.white, hex = 0xdcd9ca },
|
||||||
|
@ -929,47 +929,7 @@ function plc.comms(id, version, nic, plc_channel, svr_channel, range, reactor, r
|
|||||||
---@cast packet mgmt_frame
|
---@cast packet mgmt_frame
|
||||||
-- if linked, only accept packets from configured supervisor
|
-- if linked, only accept packets from configured supervisor
|
||||||
if self.linked then
|
if self.linked then
|
||||||
if packet.type == SCADA_MGMT_TYPE.ESTABLISH then
|
if packet.type == SCADA_MGMT_TYPE.KEEP_ALIVE then
|
||||||
-- link request confirmation
|
|
||||||
if packet.length == 1 then
|
|
||||||
log.debug("received unsolicited establish response")
|
|
||||||
|
|
||||||
local est_ack = packet.data[1]
|
|
||||||
|
|
||||||
if est_ack == ESTABLISH_ACK.ALLOW then
|
|
||||||
self.status_cache = nil
|
|
||||||
_send_struct()
|
|
||||||
public.send_status(plc_state.no_reactor, plc_state.reactor_formed)
|
|
||||||
log.debug("re-sent initial status data due to re-establish")
|
|
||||||
else
|
|
||||||
if est_ack == ESTABLISH_ACK.DENY then
|
|
||||||
println_ts("received unsolicited link denial, unlinking")
|
|
||||||
log.warning("unsolicited establish request denied")
|
|
||||||
elseif est_ack == ESTABLISH_ACK.COLLISION then
|
|
||||||
println_ts("received unsolicited link collision, unlinking")
|
|
||||||
log.warning("unsolicited establish request collision")
|
|
||||||
elseif est_ack == ESTABLISH_ACK.BAD_VERSION then
|
|
||||||
println_ts("received unsolicited link version mismatch, unlinking")
|
|
||||||
log.warning("unsolicited establish request version mismatch")
|
|
||||||
else
|
|
||||||
println_ts("invalid unsolicited link response")
|
|
||||||
log.debug("unsolicited unknown establish request response")
|
|
||||||
end
|
|
||||||
|
|
||||||
-- unlink
|
|
||||||
self.sv_addr = comms.BROADCAST
|
|
||||||
self.linked = false
|
|
||||||
end
|
|
||||||
|
|
||||||
-- clear this since this is for something that was unsolicited
|
|
||||||
self.last_est_ack = ESTABLISH_ACK.ALLOW
|
|
||||||
|
|
||||||
-- report link state
|
|
||||||
databus.tx_link_state(est_ack + 1)
|
|
||||||
else
|
|
||||||
log.debug("SCADA_MGMT establish packet length mismatch")
|
|
||||||
end
|
|
||||||
elseif packet.type == SCADA_MGMT_TYPE.KEEP_ALIVE then
|
|
||||||
-- keep alive request received, echo back
|
-- keep alive request received, echo back
|
||||||
if packet.length == 1 and type(packet.data[1]) == "number" then
|
if packet.length == 1 and type(packet.data[1]) == "number" then
|
||||||
local timestamp = packet.data[1]
|
local timestamp = packet.data[1]
|
||||||
|
@ -19,7 +19,7 @@ local plc = require("reactor-plc.plc")
|
|||||||
local renderer = require("reactor-plc.renderer")
|
local renderer = require("reactor-plc.renderer")
|
||||||
local threads = require("reactor-plc.threads")
|
local threads = require("reactor-plc.threads")
|
||||||
|
|
||||||
local R_PLC_VERSION = "v1.5.0"
|
local R_PLC_VERSION = "v1.5.5"
|
||||||
|
|
||||||
local println = util.println
|
local println = util.println
|
||||||
local println_ts = util.println_ts
|
local println_ts = util.println_ts
|
||||||
@ -190,7 +190,7 @@ local function main()
|
|||||||
renderer.close_ui()
|
renderer.close_ui()
|
||||||
println_ts(util.c("UI error: ", message))
|
println_ts(util.c("UI error: ", message))
|
||||||
println("init> running without front panel")
|
println("init> running without front panel")
|
||||||
log.error(util.c("GUI crashed with error ", message))
|
log.error(util.c("front panel GUI render failed with error ", message))
|
||||||
log.info("init> running in headless mode without front panel")
|
log.info("init> running in headless mode without front panel")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -77,7 +77,7 @@ function threads.thread__main(smem, init)
|
|||||||
loop_clock.start()
|
loop_clock.start()
|
||||||
|
|
||||||
-- send updated data
|
-- send updated data
|
||||||
if nic.connected() then
|
if nic.is_connected() then
|
||||||
if plc_comms.is_linked() then
|
if plc_comms.is_linked() then
|
||||||
smem.q.mq_comms_tx.push_command(MQ__COMM_CMD.SEND_STATUS)
|
smem.q.mq_comms_tx.push_command(MQ__COMM_CMD.SEND_STATUS)
|
||||||
else
|
else
|
||||||
@ -116,7 +116,7 @@ function threads.thread__main(smem, init)
|
|||||||
smem.q.mq_rps.push_command(MQ__RPS_CMD.SCRAM)
|
smem.q.mq_rps.push_command(MQ__RPS_CMD.SCRAM)
|
||||||
|
|
||||||
-- determine if we are still in a degraded state
|
-- determine if we are still in a degraded state
|
||||||
if (not networked) or nic.connected() then
|
if (not networked) or nic.is_connected() then
|
||||||
plc_state.degraded = false
|
plc_state.degraded = false
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -146,7 +146,7 @@ function threads.thread__main(smem, init)
|
|||||||
|
|
||||||
-- update indicators
|
-- update indicators
|
||||||
databus.tx_hw_status(plc_state)
|
databus.tx_hw_status(plc_state)
|
||||||
elseif event == "modem_message" and networked and plc_state.init_ok and nic.connected() then
|
elseif event == "modem_message" and networked and plc_state.init_ok and nic.is_connected() then
|
||||||
-- got a packet
|
-- got a packet
|
||||||
local packet = plc_comms.parse_packet(param1, param2, param3, param4, param5)
|
local packet = plc_comms.parse_packet(param1, param2, param3, param4, param5)
|
||||||
if packet ~= nil then
|
if packet ~= nil then
|
||||||
@ -177,16 +177,21 @@ function threads.thread__main(smem, init)
|
|||||||
nic.disconnect()
|
nic.disconnect()
|
||||||
|
|
||||||
println_ts("comms modem disconnected!")
|
println_ts("comms modem disconnected!")
|
||||||
log.error("comms modem disconnected")
|
log.warning("comms modem disconnected")
|
||||||
|
|
||||||
plc_state.no_modem = true
|
local other_modem = ppm.get_wireless_modem()
|
||||||
|
if other_modem then
|
||||||
|
log.info("found another wireless modem, using it for comms")
|
||||||
|
nic.connect(other_modem)
|
||||||
|
else
|
||||||
|
plc_state.no_modem = true
|
||||||
|
plc_state.degraded = true
|
||||||
|
|
||||||
if plc_state.init_ok then
|
if plc_state.init_ok then
|
||||||
-- try to scram reactor if it is still connected
|
-- try to scram reactor if it is still connected
|
||||||
smem.q.mq_rps.push_command(MQ__RPS_CMD.DEGRADED_SCRAM)
|
smem.q.mq_rps.push_command(MQ__RPS_CMD.DEGRADED_SCRAM)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
plc_state.degraded = true
|
|
||||||
else
|
else
|
||||||
log.warning("non-comms modem disconnected")
|
log.warning("non-comms modem disconnected")
|
||||||
end
|
end
|
||||||
@ -230,7 +235,7 @@ function threads.thread__main(smem, init)
|
|||||||
rps.reset()
|
rps.reset()
|
||||||
end
|
end
|
||||||
elseif networked and type == "modem" then
|
elseif networked and type == "modem" then
|
||||||
if device.isWireless() then
|
if device.isWireless() and not nic.is_connected() then
|
||||||
-- reconnected modem
|
-- reconnected modem
|
||||||
plc_dev.modem = device
|
plc_dev.modem = device
|
||||||
plc_state.no_modem = false
|
plc_state.no_modem = false
|
||||||
@ -244,6 +249,8 @@ function threads.thread__main(smem, init)
|
|||||||
if not plc_state.no_reactor then
|
if not plc_state.no_reactor then
|
||||||
plc_state.degraded = false
|
plc_state.degraded = false
|
||||||
end
|
end
|
||||||
|
elseif device.isWireless() then
|
||||||
|
log.info("unused wireless modem reconnected")
|
||||||
else
|
else
|
||||||
log.info("wired modem reconnected")
|
log.info("wired modem reconnected")
|
||||||
end
|
end
|
||||||
|
@ -2,7 +2,7 @@ local rtu = require("rtu.rtu")
|
|||||||
|
|
||||||
local boilerv_rtu = {}
|
local boilerv_rtu = {}
|
||||||
|
|
||||||
-- create new boiler (mek 10.1+) device
|
-- create new boiler device
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
---@param boiler table
|
---@param boiler table
|
||||||
---@return rtu_device interface, boolean faulted
|
---@return rtu_device interface, boolean faulted
|
||||||
|
48
rtu/dev/dynamicv_rtu.lua
Normal file
48
rtu/dev/dynamicv_rtu.lua
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
local rtu = require("rtu.rtu")
|
||||||
|
|
||||||
|
local dynamicv_rtu = {}
|
||||||
|
|
||||||
|
-- create new dynamic tank device
|
||||||
|
---@nodiscard
|
||||||
|
---@param dynamic_tank table
|
||||||
|
---@return rtu_device interface, boolean faulted
|
||||||
|
function dynamicv_rtu.new(dynamic_tank)
|
||||||
|
local unit = rtu.init_unit()
|
||||||
|
|
||||||
|
-- disable auto fault clearing
|
||||||
|
dynamic_tank.__p_clear_fault()
|
||||||
|
dynamic_tank.__p_disable_afc()
|
||||||
|
|
||||||
|
-- discrete inputs --
|
||||||
|
unit.connect_di(dynamic_tank.isFormed)
|
||||||
|
|
||||||
|
-- coils --
|
||||||
|
unit.connect_coil(function () dynamic_tank.incrementContainerEditMode() end, function () end)
|
||||||
|
unit.connect_coil(function () dynamic_tank.decrementContainerEditMode() end, function () end)
|
||||||
|
|
||||||
|
-- input registers --
|
||||||
|
-- multiblock properties
|
||||||
|
unit.connect_input_reg(dynamic_tank.getLength)
|
||||||
|
unit.connect_input_reg(dynamic_tank.getWidth)
|
||||||
|
unit.connect_input_reg(dynamic_tank.getHeight)
|
||||||
|
unit.connect_input_reg(dynamic_tank.getMinPos)
|
||||||
|
unit.connect_input_reg(dynamic_tank.getMaxPos)
|
||||||
|
-- build properties
|
||||||
|
unit.connect_input_reg(dynamic_tank.getTankCapacity)
|
||||||
|
unit.connect_input_reg(dynamic_tank.getChemicalTankCapacity)
|
||||||
|
-- tanks/containers
|
||||||
|
unit.connect_input_reg(dynamic_tank.getStored)
|
||||||
|
unit.connect_input_reg(dynamic_tank.getFilledPercentage)
|
||||||
|
|
||||||
|
-- holding registers --
|
||||||
|
unit.connect_holding_reg(dynamic_tank.getContainerEditMode, dynamic_tank.setContainerEditMode)
|
||||||
|
|
||||||
|
-- check if any calls faulted
|
||||||
|
local faulted = dynamic_tank.__p_is_faulted()
|
||||||
|
dynamic_tank.__p_clear_fault()
|
||||||
|
dynamic_tank.__p_enable_afc()
|
||||||
|
|
||||||
|
return unit.interface(), faulted
|
||||||
|
end
|
||||||
|
|
||||||
|
return dynamicv_rtu
|
@ -2,7 +2,7 @@ local rtu = require("rtu.rtu")
|
|||||||
|
|
||||||
local turbinev_rtu = {}
|
local turbinev_rtu = {}
|
||||||
|
|
||||||
-- create new turbine (mek 10.1+) device
|
-- create new turbine device
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
---@param turbine table
|
---@param turbine table
|
||||||
---@return rtu_device interface, boolean faulted
|
---@return rtu_device interface, boolean faulted
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
--
|
--
|
||||||
-- Main SCADA Coordinator GUI
|
-- RTU Front Panel GUI
|
||||||
--
|
--
|
||||||
|
|
||||||
local types = require("scada-common.types")
|
local types = require("scada-common.types")
|
||||||
@ -26,6 +26,7 @@ local UNIT_TYPE_LABELS = {
|
|||||||
"REDSTONE",
|
"REDSTONE",
|
||||||
"BOILER",
|
"BOILER",
|
||||||
"TURBINE",
|
"TURBINE",
|
||||||
|
"DYNAMIC TANK",
|
||||||
"IND MATRIX",
|
"IND MATRIX",
|
||||||
"SPS",
|
"SPS",
|
||||||
"SNA",
|
"SNA",
|
||||||
@ -33,7 +34,7 @@ local UNIT_TYPE_LABELS = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
-- create new main view
|
-- create new front panel view
|
||||||
---@param panel graphics_element main displaybox
|
---@param panel graphics_element main displaybox
|
||||||
---@param units table unit list
|
---@param units table unit list
|
||||||
local function init(panel, units)
|
local function init(panel, units)
|
||||||
|
@ -12,6 +12,7 @@ local cpair = core.cpair
|
|||||||
|
|
||||||
-- remap global colors
|
-- remap global colors
|
||||||
colors.ivory = colors.pink
|
colors.ivory = colors.pink
|
||||||
|
colors.yellow_hc = colors.purple
|
||||||
colors.red_off = colors.brown
|
colors.red_off = colors.brown
|
||||||
colors.yellow_off = colors.magenta
|
colors.yellow_off = colors.magenta
|
||||||
colors.green_off = colors.lime
|
colors.green_off = colors.lime
|
||||||
@ -28,7 +29,7 @@ style.colors = {
|
|||||||
{ c = colors.cyan, hex = 0x34bac8 },
|
{ c = colors.cyan, hex = 0x34bac8 },
|
||||||
{ c = colors.lightBlue, hex = 0x6cc0f2 },
|
{ c = colors.lightBlue, hex = 0x6cc0f2 },
|
||||||
{ c = colors.blue, hex = 0x0096ff },
|
{ c = colors.blue, hex = 0x0096ff },
|
||||||
{ c = colors.purple, hex = 0xb156ee },
|
{ c = colors.purple, hex = 0xb156ee }, -- YELLOW HIGH CONTRAST
|
||||||
{ c = colors.pink, hex = 0xdcd9ca }, -- IVORY
|
{ c = colors.pink, hex = 0xdcd9ca }, -- IVORY
|
||||||
{ c = colors.magenta, hex = 0x85862c }, -- YELLOW OFF
|
{ c = colors.magenta, hex = 0x85862c }, -- YELLOW OFF
|
||||||
-- { c = colors.white, hex = 0xdcd9ca },
|
-- { c = colors.white, hex = 0xdcd9ca },
|
||||||
|
@ -22,6 +22,7 @@ local rtu = require("rtu.rtu")
|
|||||||
local threads = require("rtu.threads")
|
local threads = require("rtu.threads")
|
||||||
|
|
||||||
local boilerv_rtu = require("rtu.dev.boilerv_rtu")
|
local boilerv_rtu = require("rtu.dev.boilerv_rtu")
|
||||||
|
local dynamicv_rtu = require("rtu.dev.dynamicv_rtu")
|
||||||
local envd_rtu = require("rtu.dev.envd_rtu")
|
local envd_rtu = require("rtu.dev.envd_rtu")
|
||||||
local imatrix_rtu = require("rtu.dev.imatrix_rtu")
|
local imatrix_rtu = require("rtu.dev.imatrix_rtu")
|
||||||
local redstone_rtu = require("rtu.dev.redstone_rtu")
|
local redstone_rtu = require("rtu.dev.redstone_rtu")
|
||||||
@ -29,7 +30,7 @@ local sna_rtu = require("rtu.dev.sna_rtu")
|
|||||||
local sps_rtu = require("rtu.dev.sps_rtu")
|
local sps_rtu = require("rtu.dev.sps_rtu")
|
||||||
local turbinev_rtu = require("rtu.dev.turbinev_rtu")
|
local turbinev_rtu = require("rtu.dev.turbinev_rtu")
|
||||||
|
|
||||||
local RTU_VERSION = "v1.4.0"
|
local RTU_VERSION = "v1.5.4"
|
||||||
|
|
||||||
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
|
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
|
||||||
local RTU_UNIT_HW_STATE = databus.RTU_UNIT_HW_STATE
|
local RTU_UNIT_HW_STATE = databus.RTU_UNIT_HW_STATE
|
||||||
@ -342,6 +343,18 @@ local function main()
|
|||||||
log.fatal(util.c("configure> failed to check if '", name, "' is a formed turbine multiblock"))
|
log.fatal(util.c("configure> failed to check if '", name, "' is a formed turbine multiblock"))
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
elseif type == "dynamicValve" then
|
||||||
|
-- dynamic tank multiblock
|
||||||
|
rtu_type = RTU_UNIT_TYPE.DYNAMIC_VALVE
|
||||||
|
rtu_iface, faulted = dynamicv_rtu.new(device)
|
||||||
|
is_multiblock = true
|
||||||
|
formed = device.isFormed()
|
||||||
|
|
||||||
|
if formed == ppm.UNDEFINED_FIELD or formed == ppm.ACCESS_FAULT then
|
||||||
|
println_ts(util.c("configure> failed to check if '", name, "' is formed"))
|
||||||
|
log.fatal(util.c("configure> failed to check if '", name, "' is a formed dynamic tank multiblock"))
|
||||||
|
return false
|
||||||
|
end
|
||||||
elseif type == "inductionPort" then
|
elseif type == "inductionPort" then
|
||||||
-- induction matrix multiblock
|
-- induction matrix multiblock
|
||||||
rtu_type = RTU_UNIT_TYPE.IMATRIX
|
rtu_type = RTU_UNIT_TYPE.IMATRIX
|
||||||
@ -464,7 +477,7 @@ local function main()
|
|||||||
renderer.close_ui()
|
renderer.close_ui()
|
||||||
println_ts(util.c("UI error: ", message))
|
println_ts(util.c("UI error: ", message))
|
||||||
println("startup> running without front panel")
|
println("startup> running without front panel")
|
||||||
log.error(util.c("GUI crashed with error ", message))
|
log.error(util.c("front panel GUI render failed with error ", message))
|
||||||
log.info("startup> running in headless mode without front panel")
|
log.info("startup> running in headless mode without front panel")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ local modbus = require("rtu.modbus")
|
|||||||
local renderer = require("rtu.renderer")
|
local renderer = require("rtu.renderer")
|
||||||
|
|
||||||
local boilerv_rtu = require("rtu.dev.boilerv_rtu")
|
local boilerv_rtu = require("rtu.dev.boilerv_rtu")
|
||||||
|
local dynamicv_rtu = require("rtu.dev.dynamicv_rtu")
|
||||||
local envd_rtu = require("rtu.dev.envd_rtu")
|
local envd_rtu = require("rtu.dev.envd_rtu")
|
||||||
local imatrix_rtu = require("rtu.dev.imatrix_rtu")
|
local imatrix_rtu = require("rtu.dev.imatrix_rtu")
|
||||||
local sna_rtu = require("rtu.dev.sna_rtu")
|
local sna_rtu = require("rtu.dev.sna_rtu")
|
||||||
@ -97,9 +98,15 @@ function threads.thread__main(smem)
|
|||||||
nic.disconnect()
|
nic.disconnect()
|
||||||
|
|
||||||
println_ts("wireless modem disconnected!")
|
println_ts("wireless modem disconnected!")
|
||||||
log.warning("comms modem disconnected!")
|
log.warning("comms modem disconnected")
|
||||||
|
|
||||||
databus.tx_hw_modem(false)
|
local other_modem = ppm.get_wireless_modem()
|
||||||
|
if other_modem then
|
||||||
|
log.info("found another wireless modem, using it for comms")
|
||||||
|
nic.connect(other_modem)
|
||||||
|
else
|
||||||
|
databus.tx_hw_modem(false)
|
||||||
|
end
|
||||||
else
|
else
|
||||||
log.warning("non-comms modem disconnected")
|
log.warning("non-comms modem disconnected")
|
||||||
end
|
end
|
||||||
@ -127,7 +134,7 @@ function threads.thread__main(smem)
|
|||||||
|
|
||||||
if type ~= nil and device ~= nil then
|
if type ~= nil and device ~= nil then
|
||||||
if type == "modem" then
|
if type == "modem" then
|
||||||
if device.isWireless() then
|
if device.isWireless() and not nic.is_connected() then
|
||||||
-- reconnected modem
|
-- reconnected modem
|
||||||
nic.connect(device)
|
nic.connect(device)
|
||||||
|
|
||||||
@ -135,6 +142,8 @@ function threads.thread__main(smem)
|
|||||||
log.info("comms modem reconnected")
|
log.info("comms modem reconnected")
|
||||||
|
|
||||||
databus.tx_hw_modem(true)
|
databus.tx_hw_modem(true)
|
||||||
|
elseif device.isWireless() then
|
||||||
|
log.info("unused wireless modem reconnected")
|
||||||
else
|
else
|
||||||
log.info("wired modem reconnected")
|
log.info("wired modem reconnected")
|
||||||
end
|
end
|
||||||
@ -181,21 +190,22 @@ function threads.thread__main(smem)
|
|||||||
databus.tx_unit_hw_type(unit.uid, unit.type)
|
databus.tx_unit_hw_type(unit.uid, unit.type)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- note for multiblock structures: if not formed, indexing the multiblock functions results in a PPM fault
|
||||||
|
|
||||||
if unit.type == RTU_UNIT_TYPE.BOILER_VALVE then
|
if unit.type == RTU_UNIT_TYPE.BOILER_VALVE then
|
||||||
unit.rtu, faulted = boilerv_rtu.new(device)
|
unit.rtu, faulted = boilerv_rtu.new(device)
|
||||||
-- if not formed, indexing the multiblock functions would have resulted in a PPM fault
|
|
||||||
unit.formed = util.trinary(faulted, false, nil)
|
unit.formed = util.trinary(faulted, false, nil)
|
||||||
elseif unit.type == RTU_UNIT_TYPE.TURBINE_VALVE then
|
elseif unit.type == RTU_UNIT_TYPE.TURBINE_VALVE then
|
||||||
unit.rtu, faulted = turbinev_rtu.new(device)
|
unit.rtu, faulted = turbinev_rtu.new(device)
|
||||||
-- if not formed, indexing the multiblock functions would have resulted in a PPM fault
|
unit.formed = util.trinary(faulted, false, nil)
|
||||||
|
elseif unit.type == RTU_UNIT_TYPE.DYNAMIC_VALVE then
|
||||||
|
unit.rtu, faulted = dynamicv_rtu.new(device)
|
||||||
unit.formed = util.trinary(faulted, false, nil)
|
unit.formed = util.trinary(faulted, false, nil)
|
||||||
elseif unit.type == RTU_UNIT_TYPE.IMATRIX then
|
elseif unit.type == RTU_UNIT_TYPE.IMATRIX then
|
||||||
unit.rtu, faulted = imatrix_rtu.new(device)
|
unit.rtu, faulted = imatrix_rtu.new(device)
|
||||||
-- if not formed, indexing the multiblock functions would have resulted in a PPM fault
|
|
||||||
unit.formed = util.trinary(faulted, false, nil)
|
unit.formed = util.trinary(faulted, false, nil)
|
||||||
elseif unit.type == RTU_UNIT_TYPE.SPS then
|
elseif unit.type == RTU_UNIT_TYPE.SPS then
|
||||||
unit.rtu, faulted = sps_rtu.new(device)
|
unit.rtu, faulted = sps_rtu.new(device)
|
||||||
-- if not formed, indexing the multiblock functions would have resulted in a PPM fault
|
|
||||||
unit.formed = util.trinary(faulted, false, nil)
|
unit.formed = util.trinary(faulted, false, nil)
|
||||||
elseif unit.type == RTU_UNIT_TYPE.SNA then
|
elseif unit.type == RTU_UNIT_TYPE.SNA then
|
||||||
unit.rtu, faulted = sna_rtu.new(device)
|
unit.rtu, faulted = sna_rtu.new(device)
|
||||||
@ -441,6 +451,12 @@ function threads.thread__unit_comms(smem, unit)
|
|||||||
unit.rtu, faulted = turbinev_rtu.new(device)
|
unit.rtu, faulted = turbinev_rtu.new(device)
|
||||||
unit.formed = device.isFormed()
|
unit.formed = device.isFormed()
|
||||||
unit.modbus_io = modbus.new(unit.rtu, true)
|
unit.modbus_io = modbus.new(unit.rtu, true)
|
||||||
|
elseif type == "dynamicValve" and unit.type == RTU_UNIT_TYPE.DYNAMIC_VALVE then
|
||||||
|
-- dynamic tank multiblock
|
||||||
|
unit.device = device
|
||||||
|
unit.rtu, faulted = dynamicv_rtu.new(device)
|
||||||
|
unit.formed = device.isFormed()
|
||||||
|
unit.modbus_io = modbus.new(unit.rtu, true)
|
||||||
elseif type == "inductionPort" and unit.type == RTU_UNIT_TYPE.IMATRIX then
|
elseif type == "inductionPort" and unit.type == RTU_UNIT_TYPE.IMATRIX then
|
||||||
-- induction matrix multiblock
|
-- induction matrix multiblock
|
||||||
unit.device = device
|
unit.device = device
|
||||||
|
@ -14,7 +14,7 @@ local max_distance = nil ---@type number|nil maximum acceptable t
|
|||||||
---@class comms
|
---@class comms
|
||||||
local comms = {}
|
local comms = {}
|
||||||
|
|
||||||
comms.version = "2.1.0"
|
comms.version = "2.1.2"
|
||||||
|
|
||||||
---@enum PROTOCOL
|
---@enum PROTOCOL
|
||||||
local PROTOCOL = {
|
local PROTOCOL = {
|
||||||
@ -92,9 +92,11 @@ local PLC_AUTO_ACK = {
|
|||||||
---@enum FAC_COMMAND
|
---@enum FAC_COMMAND
|
||||||
local FAC_COMMAND = {
|
local FAC_COMMAND = {
|
||||||
SCRAM_ALL = 0, -- SCRAM all reactors
|
SCRAM_ALL = 0, -- SCRAM all reactors
|
||||||
STOP = 1, -- stop automatic control
|
STOP = 1, -- stop automatic process control
|
||||||
START = 2, -- start automatic control
|
START = 2, -- start automatic process control
|
||||||
ACK_ALL_ALARMS = 3 -- acknowledge all alarms on all units
|
ACK_ALL_ALARMS = 3, -- acknowledge all alarms on all units
|
||||||
|
SET_WASTE_MODE = 4, -- set automatic waste processing mode
|
||||||
|
SET_PU_FB = 5 -- set plutonium fallback mode
|
||||||
}
|
}
|
||||||
|
|
||||||
---@enum UNIT_COMMAND
|
---@enum UNIT_COMMAND
|
||||||
|
@ -20,7 +20,9 @@ local logger = {
|
|||||||
mode = MODE.APPEND,
|
mode = MODE.APPEND,
|
||||||
debug = false,
|
debug = false,
|
||||||
file = nil,
|
file = nil,
|
||||||
dmesg_out = nil
|
dmesg_out = nil,
|
||||||
|
dmesg_restore_coord = { 1, 1 },
|
||||||
|
dmesg_scroll_count = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
---@type function
|
---@type function
|
||||||
@ -158,6 +160,7 @@ function log.dmesg(msg, tag, tag_color)
|
|||||||
if cur_y == out_h then
|
if cur_y == out_h then
|
||||||
out.scroll(1)
|
out.scroll(1)
|
||||||
out.setCursorPos(1, cur_y)
|
out.setCursorPos(1, cur_y)
|
||||||
|
logger.dmesg_scroll_count = logger.dmesg_scroll_count + 1
|
||||||
else
|
else
|
||||||
out.setCursorPos(1, cur_y + 1)
|
out.setCursorPos(1, cur_y + 1)
|
||||||
end
|
end
|
||||||
@ -193,6 +196,7 @@ function log.dmesg(msg, tag, tag_color)
|
|||||||
if cur_y == out_h then
|
if cur_y == out_h then
|
||||||
out.scroll(1)
|
out.scroll(1)
|
||||||
out.setCursorPos(1, cur_y)
|
out.setCursorPos(1, cur_y)
|
||||||
|
logger.dmesg_scroll_count = logger.dmesg_scroll_count + 1
|
||||||
else
|
else
|
||||||
out.setCursorPos(1, cur_y + 1)
|
out.setCursorPos(1, cur_y + 1)
|
||||||
end
|
end
|
||||||
@ -201,6 +205,8 @@ function log.dmesg(msg, tag, tag_color)
|
|||||||
out.write(lines[i])
|
out.write(lines[i])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
logger.dmesg_restore_coord = { out.getCursorPos() }
|
||||||
|
|
||||||
_log(util.c("[", t_stamp, "] [", tag, "] ", msg))
|
_log(util.c("[", t_stamp, "] [", tag, "] ", msg))
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -215,6 +221,7 @@ end
|
|||||||
---@return function update, function done
|
---@return function update, function done
|
||||||
function log.dmesg_working(msg, tag, tag_color)
|
function log.dmesg_working(msg, tag, tag_color)
|
||||||
local ts_coord = log.dmesg(msg, tag, tag_color)
|
local ts_coord = log.dmesg(msg, tag, tag_color)
|
||||||
|
local initial_scroll = logger.dmesg_scroll_count
|
||||||
|
|
||||||
local out = logger.dmesg_out
|
local out = logger.dmesg_out
|
||||||
local width = (ts_coord.x2 - ts_coord.x1) + 1
|
local width = (ts_coord.x2 - ts_coord.x1) + 1
|
||||||
@ -225,11 +232,14 @@ function log.dmesg_working(msg, tag, tag_color)
|
|||||||
local counter = 0
|
local counter = 0
|
||||||
|
|
||||||
local function update(sec_remaining)
|
local function update(sec_remaining)
|
||||||
|
local new_y = ts_coord.y - (logger.dmesg_scroll_count - initial_scroll)
|
||||||
|
if new_y < 1 then return end
|
||||||
|
|
||||||
local time = util.sprintf("%ds", sec_remaining)
|
local time = util.sprintf("%ds", sec_remaining)
|
||||||
local available = width - (string.len(time) + 2)
|
local available = width - (string.len(time) + 2)
|
||||||
local progress = ""
|
local progress = ""
|
||||||
|
|
||||||
out.setCursorPos(ts_coord.x1, ts_coord.y)
|
out.setCursorPos(ts_coord.x1, new_y)
|
||||||
out.write(" ")
|
out.write(" ")
|
||||||
|
|
||||||
if counter % 4 == 0 then
|
if counter % 4 == 0 then
|
||||||
@ -249,10 +259,15 @@ function log.dmesg_working(msg, tag, tag_color)
|
|||||||
out.setTextColor(initial_color)
|
out.setTextColor(initial_color)
|
||||||
|
|
||||||
counter = counter + 1
|
counter = counter + 1
|
||||||
|
|
||||||
|
out.setCursorPos(table.unpack(logger.dmesg_restore_coord))
|
||||||
end
|
end
|
||||||
|
|
||||||
local function done(ok)
|
local function done(ok)
|
||||||
out.setCursorPos(ts_coord.x1, ts_coord.y)
|
local new_y = ts_coord.y - (logger.dmesg_scroll_count - initial_scroll)
|
||||||
|
if new_y < 1 then return end
|
||||||
|
|
||||||
|
out.setCursorPos(ts_coord.x1, new_y)
|
||||||
|
|
||||||
if ok or ok == nil then
|
if ok or ok == nil then
|
||||||
out.setTextColor(colors.green)
|
out.setTextColor(colors.green)
|
||||||
@ -263,6 +278,8 @@ function log.dmesg_working(msg, tag, tag_color)
|
|||||||
end
|
end
|
||||||
|
|
||||||
out.setTextColor(initial_color)
|
out.setTextColor(initial_color)
|
||||||
|
|
||||||
|
out.setCursorPos(table.unpack(logger.dmesg_restore_coord))
|
||||||
end
|
end
|
||||||
|
|
||||||
return update, done
|
return update, done
|
||||||
|
@ -94,7 +94,7 @@ function network.nic(modem)
|
|||||||
|
|
||||||
-- check if this NIC has a connected modem
|
-- check if this NIC has a connected modem
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
function public.connected() return self.connected end
|
function public.is_connected() return self.connected end
|
||||||
|
|
||||||
-- connect to a modem peripheral
|
-- connect to a modem peripheral
|
||||||
---@param reconnected_modem table
|
---@param reconnected_modem table
|
||||||
|
@ -89,16 +89,18 @@ types.RTU_UNIT_TYPE = {
|
|||||||
REDSTONE = 1, -- redstone I/O
|
REDSTONE = 1, -- redstone I/O
|
||||||
BOILER_VALVE = 2, -- boiler mekanism 10.1+
|
BOILER_VALVE = 2, -- boiler mekanism 10.1+
|
||||||
TURBINE_VALVE = 3, -- turbine, mekanism 10.1+
|
TURBINE_VALVE = 3, -- turbine, mekanism 10.1+
|
||||||
IMATRIX = 4, -- induction matrix
|
DYNAMIC_VALVE = 4, -- dynamic tank, mekanism 10.1+
|
||||||
SPS = 5, -- SPS
|
IMATRIX = 5, -- induction matrix
|
||||||
SNA = 6, -- SNA
|
SPS = 6, -- SPS
|
||||||
ENV_DETECTOR = 7 -- environment detector
|
SNA = 7, -- SNA
|
||||||
|
ENV_DETECTOR = 8 -- environment detector
|
||||||
}
|
}
|
||||||
|
|
||||||
types.RTU_UNIT_NAMES = {
|
types.RTU_UNIT_NAMES = {
|
||||||
"redstone",
|
"redstone",
|
||||||
"boiler_valve",
|
"boiler_valve",
|
||||||
"turbine_valve",
|
"turbine_valve",
|
||||||
|
"dynamic_valve",
|
||||||
"induction_matrix",
|
"induction_matrix",
|
||||||
"sps",
|
"sps",
|
||||||
"sna",
|
"sna",
|
||||||
@ -115,6 +117,7 @@ function types.rtu_type_to_string(utype)
|
|||||||
elseif utype == types.RTU_UNIT_TYPE.REDSTONE or
|
elseif utype == types.RTU_UNIT_TYPE.REDSTONE or
|
||||||
utype == types.RTU_UNIT_TYPE.BOILER_VALVE or
|
utype == types.RTU_UNIT_TYPE.BOILER_VALVE or
|
||||||
utype == types.RTU_UNIT_TYPE.TURBINE_VALVE or
|
utype == types.RTU_UNIT_TYPE.TURBINE_VALVE or
|
||||||
|
utype == types.RTU_UNIT_TYPE.DYNAMIC_VALVE or
|
||||||
utype == types.RTU_UNIT_TYPE.IMATRIX or
|
utype == types.RTU_UNIT_TYPE.IMATRIX or
|
||||||
utype == types.RTU_UNIT_TYPE.SPS or
|
utype == types.RTU_UNIT_TYPE.SPS or
|
||||||
utype == types.RTU_UNIT_TYPE.SNA or
|
utype == types.RTU_UNIT_TYPE.SNA or
|
||||||
@ -158,13 +161,26 @@ types.PROCESS_NAMES = {
|
|||||||
---@enum WASTE_MODE
|
---@enum WASTE_MODE
|
||||||
types.WASTE_MODE = {
|
types.WASTE_MODE = {
|
||||||
AUTO = 1,
|
AUTO = 1,
|
||||||
PLUTONIUM = 2,
|
MANUAL_PLUTONIUM = 2,
|
||||||
POLONIUM = 3,
|
MANUAL_POLONIUM = 3,
|
||||||
ANTI_MATTER = 4
|
MANUAL_ANTI_MATTER = 4
|
||||||
}
|
}
|
||||||
|
|
||||||
types.WASTE_MODE_NAMES = {
|
types.WASTE_MODE_NAMES = {
|
||||||
"AUTO",
|
"AUTO",
|
||||||
|
"MANUAL_PLUTONIUM",
|
||||||
|
"MANUAL_POLONIUM",
|
||||||
|
"MANUAL_ANTI_MATTER"
|
||||||
|
}
|
||||||
|
|
||||||
|
---@enum WASTE_PRODUCT
|
||||||
|
types.WASTE_PRODUCT = {
|
||||||
|
PLUTONIUM = 1,
|
||||||
|
POLONIUM = 2,
|
||||||
|
ANTI_MATTER = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
types.WASTE_PRODUCT_NAMES = {
|
||||||
"PLUTONIUM",
|
"PLUTONIUM",
|
||||||
"POLONIUM",
|
"POLONIUM",
|
||||||
"ANTI_MATTER"
|
"ANTI_MATTER"
|
||||||
@ -315,6 +331,17 @@ types.RPS_TRIP_CAUSE = {
|
|||||||
FORCE_DISABLED = "force_disabled"
|
FORCE_DISABLED = "force_disabled"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
---@alias container_mode
|
||||||
|
---| "BOTH"
|
||||||
|
---| "FILL"
|
||||||
|
---| "EMPTY"
|
||||||
|
|
||||||
|
types.CONTAINER_MODE = {
|
||||||
|
BOTH = "BOTH",
|
||||||
|
FILL = "FILL",
|
||||||
|
EMPTY = "EMPTY"
|
||||||
|
}
|
||||||
|
|
||||||
---@alias dumping_mode
|
---@alias dumping_mode
|
||||||
---| "IDLE"
|
---| "IDLE"
|
||||||
---| "DUMPING"
|
---| "DUMPING"
|
||||||
|
@ -11,6 +11,9 @@ local rsctl = require("supervisor.session.rsctl")
|
|||||||
local PROCESS = types.PROCESS
|
local PROCESS = types.PROCESS
|
||||||
local PROCESS_NAMES = types.PROCESS_NAMES
|
local PROCESS_NAMES = types.PROCESS_NAMES
|
||||||
local PRIO = types.ALARM_PRIORITY
|
local PRIO = types.ALARM_PRIORITY
|
||||||
|
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
|
||||||
|
local WASTE = types.WASTE_PRODUCT
|
||||||
|
local WASTE_MODE = types.WASTE_MODE
|
||||||
|
|
||||||
local IO = rsio.IO
|
local IO = rsio.IO
|
||||||
|
|
||||||
@ -59,8 +62,11 @@ function facility.new(num_reactors, cooling_conf)
|
|||||||
all_sys_ok = false,
|
all_sys_ok = false,
|
||||||
-- rtus
|
-- rtus
|
||||||
rtu_conn_count = 0,
|
rtu_conn_count = 0,
|
||||||
|
rtu_list = {},
|
||||||
redstone = {},
|
redstone = {},
|
||||||
induction = {},
|
induction = {},
|
||||||
|
sps = {},
|
||||||
|
tanks = {},
|
||||||
envd = {},
|
envd = {},
|
||||||
-- redstone I/O control
|
-- redstone I/O control
|
||||||
io_ctl = nil, ---@type rs_controller
|
io_ctl = nil, ---@type rs_controller
|
||||||
@ -99,6 +105,10 @@ function facility.new(num_reactors, cooling_conf)
|
|||||||
last_update = 0,
|
last_update = 0,
|
||||||
last_error = 0.0,
|
last_error = 0.0,
|
||||||
last_time = 0.0,
|
last_time = 0.0,
|
||||||
|
-- waste processing
|
||||||
|
waste_product = WASTE.PLUTONIUM,
|
||||||
|
current_waste_product = WASTE.PLUTONIUM,
|
||||||
|
pu_fallback = false,
|
||||||
-- statistics
|
-- statistics
|
||||||
im_stat_init = false,
|
im_stat_init = false,
|
||||||
avg_charge = util.mov_avg(3, 0.0),
|
avg_charge = util.mov_avg(3, 0.0),
|
||||||
@ -112,15 +122,12 @@ function facility.new(num_reactors, cooling_conf)
|
|||||||
table.insert(self.group_map, 0)
|
table.insert(self.group_map, 0)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- list for RTU session management
|
||||||
|
self.rtu_list = { self.redstone, self.induction, self.sps, self.tanks, self.envd }
|
||||||
|
|
||||||
-- init redstone RTU I/O controller
|
-- init redstone RTU I/O controller
|
||||||
self.io_ctl = rsctl.new(self.redstone)
|
self.io_ctl = rsctl.new(self.redstone)
|
||||||
|
|
||||||
-- unlink disconnected units
|
|
||||||
---@param sessions table
|
|
||||||
local function _unlink_disconnected_units(sessions)
|
|
||||||
util.filter_table(sessions, function (u) return u.is_connected() end)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- check if all auto-controlled units completed ramping
|
-- check if all auto-controlled units completed ramping
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
local function _all_units_ramped()
|
local function _all_units_ramped()
|
||||||
@ -205,24 +212,50 @@ function facility.new(num_reactors, cooling_conf)
|
|||||||
table.insert(self.redstone, rs_unit)
|
table.insert(self.redstone, rs_unit)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- link an imatrix RTU session
|
-- link an induction matrix RTU session
|
||||||
---@param imatrix unit_session
|
---@param imatrix unit_session
|
||||||
|
---@return boolean linked induction matrix accepted (max 1)
|
||||||
function public.add_imatrix(imatrix)
|
function public.add_imatrix(imatrix)
|
||||||
table.insert(self.induction, imatrix)
|
if #self.induction == 0 then
|
||||||
|
table.insert(self.induction, imatrix)
|
||||||
|
return true
|
||||||
|
else return false end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- link an SPS RTU session
|
||||||
|
---@param sps unit_session
|
||||||
|
---@return boolean linked SPS accepted (max 1)
|
||||||
|
function public.add_sps(sps)
|
||||||
|
if #self.sps == 0 then
|
||||||
|
table.insert(self.sps, sps)
|
||||||
|
return true
|
||||||
|
else return false end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- link a dynamic tank RTU session
|
||||||
|
---@param dynamic_tank unit_session
|
||||||
|
---@return boolean linked dynamic tank accepted (max 1)
|
||||||
|
function public.add_tank(dynamic_tank)
|
||||||
|
if #self.tanks == 0 then
|
||||||
|
table.insert(self.tanks, dynamic_tank)
|
||||||
|
return true
|
||||||
|
else return false end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- link an environment detector RTU session
|
-- link an environment detector RTU session
|
||||||
---@param envd unit_session
|
---@param envd unit_session
|
||||||
|
---@return boolean linked environment detector accepted (max 1)
|
||||||
function public.add_envd(envd)
|
function public.add_envd(envd)
|
||||||
table.insert(self.envd, envd)
|
if #self.envd == 0 then
|
||||||
|
table.insert(self.envd, envd)
|
||||||
|
return true
|
||||||
|
else return false end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- purge devices associated with the given RTU session ID
|
-- purge devices associated with the given RTU session ID
|
||||||
---@param session integer RTU session ID
|
---@param session integer RTU session ID
|
||||||
function public.purge_rtu_devices(session)
|
function public.purge_rtu_devices(session)
|
||||||
util.filter_table(self.redstone, function (s) return s.get_session_id() ~= session end)
|
for _, v in pairs(self.rtu_list) do util.filter_table(v, function (s) return s.get_session_id() ~= session end) end
|
||||||
util.filter_table(self.induction, function (s) return s.get_session_id() ~= session end)
|
|
||||||
util.filter_table(self.envd, function (s) return s.get_session_id() ~= session end)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- UPDATE --
|
-- UPDATE --
|
||||||
@ -236,9 +269,7 @@ function facility.new(num_reactors, cooling_conf)
|
|||||||
-- update (iterate) the facility management
|
-- update (iterate) the facility management
|
||||||
function public.update()
|
function public.update()
|
||||||
-- unlink RTU unit sessions if they are closed
|
-- unlink RTU unit sessions if they are closed
|
||||||
_unlink_disconnected_units(self.redstone)
|
for _, v in pairs(self.rtu_list) do util.filter_table(v, function (u) return u.is_connected() end) end
|
||||||
_unlink_disconnected_units(self.induction)
|
|
||||||
_unlink_disconnected_units(self.envd)
|
|
||||||
|
|
||||||
-- current state for process control
|
-- current state for process control
|
||||||
local charge_update = 0
|
local charge_update = 0
|
||||||
@ -277,6 +308,8 @@ function facility.new(num_reactors, cooling_conf)
|
|||||||
-- Run Process Control --
|
-- Run Process Control --
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
|
--#region Process Control
|
||||||
|
|
||||||
local avg_charge = self.avg_charge.compute()
|
local avg_charge = self.avg_charge.compute()
|
||||||
local avg_inflow = self.avg_inflow.compute()
|
local avg_inflow = self.avg_inflow.compute()
|
||||||
|
|
||||||
@ -542,10 +575,14 @@ function facility.new(num_reactors, cooling_conf)
|
|||||||
next_mode = PROCESS.INACTIVE
|
next_mode = PROCESS.INACTIVE
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
------------------------------
|
------------------------------
|
||||||
-- Evaluate Automatic SCRAM --
|
-- Evaluate Automatic SCRAM --
|
||||||
------------------------------
|
------------------------------
|
||||||
|
|
||||||
|
--#region Automatic SCRAM
|
||||||
|
|
||||||
local astatus = self.ascram_status
|
local astatus = self.ascram_status
|
||||||
|
|
||||||
if self.induction[1] ~= nil then
|
if self.induction[1] ~= nil then
|
||||||
@ -659,6 +696,8 @@ function facility.new(num_reactors, cooling_conf)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
-- update last mode and set next mode
|
-- update last mode and set next mode
|
||||||
self.last_mode = self.mode
|
self.last_mode = self.mode
|
||||||
self.mode = next_mode
|
self.mode = next_mode
|
||||||
@ -692,12 +731,33 @@ function facility.new(num_reactors, cooling_conf)
|
|||||||
|
|
||||||
self.io_ctl.digital_write(IO.F_ALARM, has_alarm)
|
self.io_ctl.digital_write(IO.F_ALARM, has_alarm)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-----------------------------
|
||||||
|
-- Update Waste Processing --
|
||||||
|
-----------------------------
|
||||||
|
|
||||||
|
local insufficent_po_rate = false
|
||||||
|
for i = 1, #self.units do
|
||||||
|
local u = self.units[i] ---@type reactor_unit
|
||||||
|
if u.get_control_inf().waste_mode == WASTE_MODE.AUTO then
|
||||||
|
if (u.get_sna_rate() * 10.0) < u.get_burn_rate() then
|
||||||
|
insufficent_po_rate = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if self.waste_product == WASTE.PLUTONIUM or (self.pu_fallback and insufficent_po_rate) then
|
||||||
|
self.current_waste_product = WASTE.PLUTONIUM
|
||||||
|
else self.current_waste_product = self.waste_product end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- call the update function of all units in the facility
|
-- call the update function of all units in the facility<br>
|
||||||
|
-- additionally sets the requested auto waste mode if applicable
|
||||||
function public.update_units()
|
function public.update_units()
|
||||||
for i = 1, #self.units do
|
for i = 1, #self.units do
|
||||||
local u = self.units[i] ---@type reactor_unit
|
local u = self.units[i] ---@type reactor_unit
|
||||||
|
u.auto_set_waste(self.current_waste_product)
|
||||||
u.update()
|
u.update()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -721,15 +781,15 @@ function facility.new(num_reactors, cooling_conf)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- stop auto control
|
-- stop auto control
|
||||||
function public.auto_stop()
|
function public.auto_stop() self.mode = PROCESS.INACTIVE end
|
||||||
self.mode = PROCESS.INACTIVE
|
|
||||||
end
|
|
||||||
|
|
||||||
-- set automatic control configuration and start the process
|
-- set automatic control configuration and start the process
|
||||||
---@param config coord_auto_config configuration
|
---@param config coord_auto_config configuration
|
||||||
---@return table response ready state (successfully started) and current configuration (after updating)
|
---@return table response ready state (successfully started) and current configuration (after updating)
|
||||||
function public.auto_start(config)
|
function public.auto_start(config)
|
||||||
local ready = false
|
local charge_scaler = 1000000 -- convert MFE to FE
|
||||||
|
local gen_scaler = 1000 -- convert kFE to FE
|
||||||
|
local ready = false
|
||||||
|
|
||||||
-- load up current limits
|
-- load up current limits
|
||||||
local limits = {}
|
local limits = {}
|
||||||
@ -749,11 +809,11 @@ function facility.new(num_reactors, cooling_conf)
|
|||||||
end
|
end
|
||||||
|
|
||||||
if (type(config.charge_target) == "number") and config.charge_target >= 0 then
|
if (type(config.charge_target) == "number") and config.charge_target >= 0 then
|
||||||
self.charge_setpoint = config.charge_target * 1000000 -- convert MFE to FE
|
self.charge_setpoint = config.charge_target * charge_scaler
|
||||||
end
|
end
|
||||||
|
|
||||||
if (type(config.gen_target) == "number") and config.gen_target >= 0 then
|
if (type(config.gen_target) == "number") and config.gen_target >= 0 then
|
||||||
self.gen_rate_setpoint = config.gen_target * 1000 -- convert kFE to FE
|
self.gen_rate_setpoint = config.gen_target * gen_scaler
|
||||||
end
|
end
|
||||||
|
|
||||||
if (type(config.limits) == "table") and (#config.limits == num_reactors) then
|
if (type(config.limits) == "table") and (#config.limits == num_reactors) then
|
||||||
@ -769,11 +829,9 @@ function facility.new(num_reactors, cooling_conf)
|
|||||||
|
|
||||||
ready = self.mode_set > 0
|
ready = self.mode_set > 0
|
||||||
|
|
||||||
if (self.mode_set == PROCESS.CHARGE) and (self.charge_setpoint <= 0) then
|
if (self.mode_set == PROCESS.CHARGE) and (self.charge_setpoint <= 0) or
|
||||||
ready = false
|
(self.mode_set == PROCESS.GEN_RATE) and (self.gen_rate_setpoint <= 0) or
|
||||||
elseif (self.mode_set == PROCESS.GEN_RATE) and (self.gen_rate_setpoint <= 0) then
|
(self.mode_set == PROCESS.BURN_RATE) and (self.burn_target < 0.1) then
|
||||||
ready = false
|
|
||||||
elseif (self.mode_set == PROCESS.BURN_RATE) and (self.burn_target < 0.1) then
|
|
||||||
ready = false
|
ready = false
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -782,7 +840,14 @@ function facility.new(num_reactors, cooling_conf)
|
|||||||
if ready then self.mode = self.mode_set end
|
if ready then self.mode = self.mode_set end
|
||||||
end
|
end
|
||||||
|
|
||||||
return { ready, self.mode_set, self.burn_target, self.charge_setpoint, self.gen_rate_setpoint, limits }
|
return {
|
||||||
|
ready,
|
||||||
|
self.mode_set,
|
||||||
|
self.burn_target,
|
||||||
|
self.charge_setpoint / charge_scaler,
|
||||||
|
self.gen_rate_setpoint / gen_scaler,
|
||||||
|
limits
|
||||||
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
-- SETTINGS --
|
-- SETTINGS --
|
||||||
@ -807,15 +872,35 @@ function facility.new(num_reactors, cooling_conf)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- set waste production
|
||||||
|
---@param product WASTE_PRODUCT target product
|
||||||
|
---@return WASTE_PRODUCT product newly set value, if valid
|
||||||
|
function public.set_waste_product(product)
|
||||||
|
if product == WASTE.PLUTONIUM or product == WASTE.POLONIUM or product == WASTE.ANTI_MATTER then
|
||||||
|
self.waste_product = product
|
||||||
|
end
|
||||||
|
|
||||||
|
return self.waste_product
|
||||||
|
end
|
||||||
|
|
||||||
|
-- enable/disable plutonium fallback
|
||||||
|
---@param enabled boolean requested state
|
||||||
|
---@return boolean enabled newly set value
|
||||||
|
function public.set_pu_fallback(enabled)
|
||||||
|
self.pu_fallback = enabled == true
|
||||||
|
return self.pu_fallback
|
||||||
|
end
|
||||||
|
|
||||||
-- READ STATES/PROPERTIES --
|
-- READ STATES/PROPERTIES --
|
||||||
|
|
||||||
-- get build properties of all machines
|
-- get build properties of all facility devices
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
---@param inc_imatrix boolean? true/nil to include induction matrix build, false to exclude
|
---@param type RTU_UNIT_TYPE? type or nil to include only a particular unit type, or to include all if nil
|
||||||
function public.get_build(inc_imatrix)
|
function public.get_build(type)
|
||||||
|
local all = type == nil
|
||||||
local build = {}
|
local build = {}
|
||||||
|
|
||||||
if inc_imatrix ~= false then
|
if all or type == RTU_UNIT_TYPE.IMATRIX then
|
||||||
build.induction = {}
|
build.induction = {}
|
||||||
for i = 1, #self.induction do
|
for i = 1, #self.induction do
|
||||||
local matrix = self.induction[i] ---@type unit_session
|
local matrix = self.induction[i] ---@type unit_session
|
||||||
@ -823,6 +908,22 @@ function facility.new(num_reactors, cooling_conf)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if all or type == RTU_UNIT_TYPE.SPS then
|
||||||
|
build.sps = {}
|
||||||
|
for i = 1, #self.sps do
|
||||||
|
local sps = self.sps[i] ---@type unit_session
|
||||||
|
build.sps[sps.get_device_idx()] = { sps.get_db().formed, sps.get_db().build }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if all or type == RTU_UNIT_TYPE.DYNAMIC_VALVE then
|
||||||
|
build.tanks = {}
|
||||||
|
for i = 1, #self.tanks do
|
||||||
|
local tank = self.tanks[i] ---@type unit_session
|
||||||
|
build.tanks[tank.get_device_idx()] = { tank.get_db().formed, tank.get_db().build }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
return build
|
return build
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -844,7 +945,9 @@ function facility.new(num_reactors, cooling_conf)
|
|||||||
astat.gen_fault or self.mode == PROCESS.GEN_RATE_FAULT_IDLE,
|
astat.gen_fault or self.mode == PROCESS.GEN_RATE_FAULT_IDLE,
|
||||||
self.status_text[1],
|
self.status_text[1],
|
||||||
self.status_text[2],
|
self.status_text[2],
|
||||||
self.group_map
|
self.group_map,
|
||||||
|
self.current_waste_product,
|
||||||
|
(self.current_waste_product == WASTE.PLUTONIUM) and (self.waste_product ~= WASTE.PLUTONIUM)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -866,23 +969,32 @@ function facility.new(num_reactors, cooling_conf)
|
|||||||
-- status of induction matricies (including tanks)
|
-- status of induction matricies (including tanks)
|
||||||
status.induction = {}
|
status.induction = {}
|
||||||
for i = 1, #self.induction do
|
for i = 1, #self.induction do
|
||||||
local matrix = self.induction[i] ---@type unit_session
|
local matrix = self.induction[i] ---@type unit_session
|
||||||
status.induction[matrix.get_device_idx()] = {
|
local db = matrix.get_db() ---@type imatrix_session_db
|
||||||
matrix.is_faulted(),
|
status.induction[matrix.get_device_idx()] = { matrix.is_faulted(), db.formed, db.state, db.tanks }
|
||||||
matrix.get_db().formed,
|
end
|
||||||
matrix.get_db().state,
|
|
||||||
matrix.get_db().tanks
|
-- status of sps
|
||||||
}
|
status.sps = {}
|
||||||
|
for i = 1, #self.sps do
|
||||||
|
local sps = self.sps[i] ---@type unit_session
|
||||||
|
local db = sps.get_db() ---@type sps_session_db
|
||||||
|
status.sps[sps.get_device_idx()] = { sps.is_faulted(), db.formed, db.state, db.tanks }
|
||||||
|
end
|
||||||
|
|
||||||
|
-- status of dynamic tanks
|
||||||
|
status.tanks = {}
|
||||||
|
for i = 1, #self.tanks do
|
||||||
|
local tank = self.tanks[i] ---@type unit_session
|
||||||
|
local db = tank.get_db() ---@type dynamicv_session_db
|
||||||
|
status.tanks[tank.get_device_idx()] = { tank.is_faulted(), db.formed, db.state, db.tanks }
|
||||||
end
|
end
|
||||||
|
|
||||||
-- radiation monitors (environment detectors)
|
-- radiation monitors (environment detectors)
|
||||||
status.rad_mon = {}
|
status.rad_mon = {}
|
||||||
for i = 1, #self.envd do
|
for i = 1, #self.envd do
|
||||||
local envd = self.envd[i] ---@type unit_session
|
local envd = self.envd[i] ---@type unit_session
|
||||||
status.rad_mon[envd.get_device_idx()] = {
|
status.rad_mon[envd.get_device_idx()] = { envd.is_faulted(), envd.get_db().radiation }
|
||||||
envd.is_faulted(),
|
|
||||||
envd.get_db().radiation
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return status
|
return status
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
--
|
--
|
||||||
-- Main SCADA Coordinator GUI
|
-- Supervisor Front Panel GUI
|
||||||
--
|
--
|
||||||
|
|
||||||
local util = require("scada-common.util")
|
local util = require("scada-common.util")
|
||||||
@ -29,7 +29,7 @@ local TEXT_ALIGN = core.TEXT_ALIGN
|
|||||||
|
|
||||||
local cpair = core.cpair
|
local cpair = core.cpair
|
||||||
|
|
||||||
-- create new main view
|
-- create new front panel view
|
||||||
---@param panel graphics_element main displaybox
|
---@param panel graphics_element main displaybox
|
||||||
local function init(panel)
|
local function init(panel)
|
||||||
TextBox{parent=panel,y=1,text="SCADA SUPERVISOR",alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=style.header}
|
TextBox{parent=panel,y=1,text="SCADA SUPERVISOR",alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=style.header}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
local comms = require("scada-common.comms")
|
local comms = require("scada-common.comms")
|
||||||
local log = require("scada-common.log")
|
local log = require("scada-common.log")
|
||||||
local mqueue = require("scada-common.mqueue")
|
local mqueue = require("scada-common.mqueue")
|
||||||
local types = require("scada-common.types")
|
|
||||||
local util = require("scada-common.util")
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
local databus = require("supervisor.databus")
|
local databus = require("supervisor.databus")
|
||||||
@ -16,8 +15,6 @@ local SCADA_CRDN_TYPE = comms.SCADA_CRDN_TYPE
|
|||||||
local UNIT_COMMAND = comms.UNIT_COMMAND
|
local UNIT_COMMAND = comms.UNIT_COMMAND
|
||||||
local FAC_COMMAND = comms.FAC_COMMAND
|
local FAC_COMMAND = comms.FAC_COMMAND
|
||||||
|
|
||||||
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
|
|
||||||
|
|
||||||
local SV_Q_DATA = svqtypes.SV_Q_DATA
|
local SV_Q_DATA = svqtypes.SV_Q_DATA
|
||||||
|
|
||||||
-- retry time constants in ms
|
-- retry time constants in ms
|
||||||
@ -258,6 +255,18 @@ function coordinator.new_session(id, s_addr, in_queue, out_queue, timeout, facil
|
|||||||
elseif cmd == FAC_COMMAND.ACK_ALL_ALARMS then
|
elseif cmd == FAC_COMMAND.ACK_ALL_ALARMS then
|
||||||
facility.ack_all()
|
facility.ack_all()
|
||||||
_send(SCADA_CRDN_TYPE.FAC_CMD, { cmd, true })
|
_send(SCADA_CRDN_TYPE.FAC_CMD, { cmd, true })
|
||||||
|
elseif cmd == FAC_COMMAND.SET_WASTE_MODE then
|
||||||
|
if pkt.length == 2 then
|
||||||
|
_send(SCADA_CRDN_TYPE.FAC_CMD, { cmd, facility.set_waste_product(pkt.data[2]) })
|
||||||
|
else
|
||||||
|
log.debug(log_header .. "CRDN set waste mode packet length mismatch")
|
||||||
|
end
|
||||||
|
elseif cmd == FAC_COMMAND.SET_PU_FB then
|
||||||
|
if pkt.length == 2 then
|
||||||
|
_send(SCADA_CRDN_TYPE.FAC_CMD, { cmd, facility.set_pu_fallback(pkt.data[2]) })
|
||||||
|
else
|
||||||
|
log.debug(log_header .. "CRDN set pu fallback packet length mismatch")
|
||||||
|
end
|
||||||
else
|
else
|
||||||
log.debug(log_header .. "CRDN facility command unknown")
|
log.debug(log_header .. "CRDN facility command unknown")
|
||||||
end
|
end
|
||||||
@ -294,9 +303,9 @@ function coordinator.new_session(id, s_addr, in_queue, out_queue, timeout, facil
|
|||||||
end
|
end
|
||||||
elseif cmd == UNIT_COMMAND.SET_WASTE then
|
elseif cmd == UNIT_COMMAND.SET_WASTE then
|
||||||
if (pkt.length == 3) and (type(pkt.data[3]) == "number") and (pkt.data[3] > 0) and (pkt.data[3] <= 4) then
|
if (pkt.length == 3) and (type(pkt.data[3]) == "number") and (pkt.data[3] > 0) and (pkt.data[3] <= 4) then
|
||||||
unit.set_waste(pkt.data[3])
|
unit.set_waste_mode(pkt.data[3])
|
||||||
else
|
else
|
||||||
log.debug(log_header .. "CRDN unit command set waste missing option")
|
log.debug(log_header .. "CRDN unit command set waste missing/invalid option")
|
||||||
end
|
end
|
||||||
elseif cmd == UNIT_COMMAND.ACK_ALL_ALARMS then
|
elseif cmd == UNIT_COMMAND.ACK_ALL_ALARMS then
|
||||||
unit.ack_all()
|
unit.ack_all()
|
||||||
@ -394,7 +403,7 @@ function coordinator.new_session(id, s_addr, in_queue, out_queue, timeout, facil
|
|||||||
local builds = {}
|
local builds = {}
|
||||||
|
|
||||||
local unit = self.units[unit_id] ---@type reactor_unit
|
local unit = self.units[unit_id] ---@type reactor_unit
|
||||||
builds[unit_id] = unit.get_build(true, false, false)
|
builds[unit_id] = unit.get_build(-1)
|
||||||
|
|
||||||
_send(SCADA_CRDN_TYPE.UNIT_BUILDS, { builds })
|
_send(SCADA_CRDN_TYPE.UNIT_BUILDS, { builds })
|
||||||
elseif cmd.key == CRD_S_DATA.RESEND_RTU_BUILD then
|
elseif cmd.key == CRD_S_DATA.RESEND_RTU_BUILD then
|
||||||
@ -408,7 +417,7 @@ function coordinator.new_session(id, s_addr, in_queue, out_queue, timeout, facil
|
|||||||
local builds = {}
|
local builds = {}
|
||||||
|
|
||||||
local unit = self.units[unit_id] ---@type reactor_unit
|
local unit = self.units[unit_id] ---@type reactor_unit
|
||||||
builds[unit_id] = unit.get_build(false, cmd.val.type == RTU_UNIT_TYPE.BOILER_VALVE, cmd.val.type == RTU_UNIT_TYPE.TURBINE_VALVE)
|
builds[unit_id] = unit.get_build(cmd.val.type)
|
||||||
|
|
||||||
_send(SCADA_CRDN_TYPE.UNIT_BUILDS, { builds })
|
_send(SCADA_CRDN_TYPE.UNIT_BUILDS, { builds })
|
||||||
else
|
else
|
||||||
@ -417,7 +426,7 @@ function coordinator.new_session(id, s_addr, in_queue, out_queue, timeout, facil
|
|||||||
self.retry_times.f_builds_packet = util.time() + PARTIAL_RETRY_PERIOD
|
self.retry_times.f_builds_packet = util.time() + PARTIAL_RETRY_PERIOD
|
||||||
self.acks.fac_builds = false
|
self.acks.fac_builds = false
|
||||||
|
|
||||||
_send(SCADA_CRDN_TYPE.FAC_BUILDS, { facility.get_build(cmd.val.type == RTU_UNIT_TYPE.IMATRIX) })
|
_send(SCADA_CRDN_TYPE.FAC_BUILDS, { facility.get_build(cmd.val.type) })
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
log.error(log_header .. "unsupported data command received in in_queue (this is a bug)", true)
|
log.error(log_header .. "unsupported data command received in in_queue (this is a bug)", true)
|
||||||
|
@ -313,26 +313,31 @@ function plc.new_session(id, s_addr, reactor_id, in_queue, out_queue, timeout, f
|
|||||||
if pkt.type == RPLC_TYPE.STATUS then
|
if pkt.type == RPLC_TYPE.STATUS then
|
||||||
-- status packet received, update data
|
-- status packet received, update data
|
||||||
if pkt.length >= 5 then
|
if pkt.length >= 5 then
|
||||||
self.sDB.last_status_update = pkt.data[1]
|
if (type(pkt.data[1]) == "number") and (type(pkt.data[2]) == "boolean") and (type(pkt.data[3]) == "boolean") and
|
||||||
self.sDB.control_state = pkt.data[2]
|
(type(pkt.data[4]) == "boolean") and (type(pkt.data[5]) == "number") then
|
||||||
self.sDB.no_reactor = pkt.data[3]
|
self.sDB.last_status_update = pkt.data[1]
|
||||||
self.sDB.formed = pkt.data[4]
|
self.sDB.control_state = pkt.data[2]
|
||||||
self.sDB.auto_ack_token = pkt.data[5]
|
self.sDB.no_reactor = pkt.data[3]
|
||||||
|
self.sDB.formed = pkt.data[4]
|
||||||
|
self.sDB.auto_ack_token = pkt.data[5]
|
||||||
|
|
||||||
if not self.sDB.no_reactor and self.sDB.formed then
|
if (not self.sDB.no_reactor) and self.sDB.formed and (type(pkt.data[6]) == "number") then
|
||||||
self.sDB.mek_status.heating_rate = pkt.data[6] or 0.0
|
self.sDB.mek_status.heating_rate = pkt.data[6] or 0.0
|
||||||
|
|
||||||
-- attempt to read mek_data table
|
-- attempt to read mek_data table
|
||||||
if pkt.data[7] ~= nil then
|
if type(pkt.data[7]) == "table" then
|
||||||
local status = pcall(_copy_status, pkt.data[7])
|
local status = pcall(_copy_status, pkt.data[7])
|
||||||
if status then
|
if status then
|
||||||
-- copied in status data OK
|
-- copied in status data OK
|
||||||
self.received_status_cache = true
|
self.received_status_cache = true
|
||||||
else
|
else
|
||||||
-- error copying status data
|
-- error copying status data
|
||||||
log.error(log_header .. "failed to parse status packet data")
|
log.error(log_header .. "failed to parse status packet data")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
else
|
||||||
|
log.debug(log_header .. "RPLC status packet invalid")
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
log.debug(log_header .. "RPLC status packet length mismatch")
|
log.debug(log_header .. "RPLC status packet length mismatch")
|
||||||
|
@ -11,6 +11,7 @@ local svqtypes = require("supervisor.session.svqtypes")
|
|||||||
-- supervisor rtu sessions (svrs)
|
-- supervisor rtu sessions (svrs)
|
||||||
local unit_session = require("supervisor.session.rtu.unit_session")
|
local unit_session = require("supervisor.session.rtu.unit_session")
|
||||||
local svrs_boilerv = require("supervisor.session.rtu.boilerv")
|
local svrs_boilerv = require("supervisor.session.rtu.boilerv")
|
||||||
|
local svrs_dynamicv = require("supervisor.session.rtu.dynamicv")
|
||||||
local svrs_envd = require("supervisor.session.rtu.envd")
|
local svrs_envd = require("supervisor.session.rtu.envd")
|
||||||
local svrs_imatrix = require("supervisor.session.rtu.imatrix")
|
local svrs_imatrix = require("supervisor.session.rtu.imatrix")
|
||||||
local svrs_redstone = require("supervisor.session.rtu.redstone")
|
local svrs_redstone = require("supervisor.session.rtu.redstone")
|
||||||
@ -138,6 +139,14 @@ function rtu.new_session(id, s_addr, in_queue, out_queue, timeout, advertisement
|
|||||||
-- turbine
|
-- turbine
|
||||||
unit = svrs_turbinev.new(id, i, unit_advert, self.modbus_q)
|
unit = svrs_turbinev.new(id, i, unit_advert, self.modbus_q)
|
||||||
if type(unit) ~= "nil" then target_unit.add_turbine(unit) end
|
if type(unit) ~= "nil" then target_unit.add_turbine(unit) end
|
||||||
|
elseif u_type == RTU_UNIT_TYPE.DYNAMIC_VALVE then
|
||||||
|
-- dynamic tank
|
||||||
|
unit = svrs_dynamicv.new(id, i, unit_advert, self.modbus_q)
|
||||||
|
if type(unit) ~= "nil" then target_unit.add_tank(unit) end
|
||||||
|
elseif u_type == RTU_UNIT_TYPE.SNA then
|
||||||
|
-- solar neutron activator
|
||||||
|
unit = svrs_sna.new(id, i, unit_advert, self.modbus_q)
|
||||||
|
if type(unit) ~= "nil" then target_unit.add_sna(unit) end
|
||||||
elseif u_type == RTU_UNIT_TYPE.ENV_DETECTOR then
|
elseif u_type == RTU_UNIT_TYPE.ENV_DETECTOR then
|
||||||
-- environment detector
|
-- environment detector
|
||||||
unit = svrs_envd.new(id, i, unit_advert, self.modbus_q)
|
unit = svrs_envd.new(id, i, unit_advert, self.modbus_q)
|
||||||
@ -161,9 +170,11 @@ function rtu.new_session(id, s_addr, in_queue, out_queue, timeout, advertisement
|
|||||||
elseif u_type == RTU_UNIT_TYPE.SPS then
|
elseif u_type == RTU_UNIT_TYPE.SPS then
|
||||||
-- super-critical phase shifter
|
-- super-critical phase shifter
|
||||||
unit = svrs_sps.new(id, i, unit_advert, self.modbus_q)
|
unit = svrs_sps.new(id, i, unit_advert, self.modbus_q)
|
||||||
elseif u_type == RTU_UNIT_TYPE.SNA then
|
if type(unit) ~= "nil" then facility.add_sps(unit) end
|
||||||
-- solar neutron activator
|
elseif u_type == RTU_UNIT_TYPE.DYNAMIC_VALVE then
|
||||||
unit = svrs_sna.new(id, i, unit_advert, self.modbus_q)
|
-- dynamic tank
|
||||||
|
unit = svrs_dynamicv.new(id, i, unit_advert, self.modbus_q)
|
||||||
|
if type(unit) ~= "nil" then facility.add_tank(unit) end
|
||||||
elseif u_type == RTU_UNIT_TYPE.ENV_DETECTOR then
|
elseif u_type == RTU_UNIT_TYPE.ENV_DETECTOR then
|
||||||
-- environment detector
|
-- environment detector
|
||||||
unit = svrs_envd.new(id, i, unit_advert, self.modbus_q)
|
unit = svrs_envd.new(id, i, unit_advert, self.modbus_q)
|
||||||
|
289
supervisor/session/rtu/dynamicv.lua
Normal file
289
supervisor/session/rtu/dynamicv.lua
Normal file
@ -0,0 +1,289 @@
|
|||||||
|
local log = require("scada-common.log")
|
||||||
|
local mqueue = require("scada-common.mqueue")
|
||||||
|
local types = require("scada-common.types")
|
||||||
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
|
local qtypes = require("supervisor.session.rtu.qtypes")
|
||||||
|
local unit_session = require("supervisor.session.rtu.unit_session")
|
||||||
|
|
||||||
|
local dynamicv = {}
|
||||||
|
|
||||||
|
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
|
||||||
|
local CONTAINER_MODE = types.CONTAINER_MODE
|
||||||
|
local MODBUS_FCODE = types.MODBUS_FCODE
|
||||||
|
|
||||||
|
local DTV_RTU_S_CMDS = qtypes.DTV_RTU_S_CMDS
|
||||||
|
local DTV_RTU_S_DATA = qtypes.DTV_RTU_S_DATA
|
||||||
|
|
||||||
|
local TXN_TYPES = {
|
||||||
|
FORMED = 1,
|
||||||
|
BUILD = 2,
|
||||||
|
STATE = 3,
|
||||||
|
TANKS = 4,
|
||||||
|
INC_CONT = 5,
|
||||||
|
DEC_CONT = 6,
|
||||||
|
SET_CONT = 7
|
||||||
|
}
|
||||||
|
|
||||||
|
local TXN_TAGS = {
|
||||||
|
"dynamicv.formed",
|
||||||
|
"dynamicv.build",
|
||||||
|
"dynamicv.state",
|
||||||
|
"dynamicv.tanks",
|
||||||
|
"dynamicv.inc_cont_mode",
|
||||||
|
"dynamicv.dec_cont_mode",
|
||||||
|
"dynamicv.set_cont_mode"
|
||||||
|
}
|
||||||
|
|
||||||
|
local PERIODICS = {
|
||||||
|
FORMED = 2000,
|
||||||
|
BUILD = 1000,
|
||||||
|
STATE = 1000,
|
||||||
|
TANKS = 500
|
||||||
|
}
|
||||||
|
|
||||||
|
-- create a new dynamicv rtu session runner
|
||||||
|
---@nodiscard
|
||||||
|
---@param session_id integer RTU session ID
|
||||||
|
---@param unit_id integer RTU unit ID
|
||||||
|
---@param advert rtu_advertisement RTU advertisement table
|
||||||
|
---@param out_queue mqueue RTU unit message out queue
|
||||||
|
function dynamicv.new(session_id, unit_id, advert, out_queue)
|
||||||
|
-- type check
|
||||||
|
if advert.type ~= RTU_UNIT_TYPE.DYNAMIC_VALVE then
|
||||||
|
log.error("attempt to instantiate dynamicv RTU for type '" .. types.rtu_type_to_string(advert.type) .. "'. this is a bug.")
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local log_tag = "session.rtu(" .. session_id .. ").dynamicv(" .. advert.index .. "): "
|
||||||
|
|
||||||
|
local self = {
|
||||||
|
session = unit_session.new(session_id, unit_id, advert, out_queue, log_tag, TXN_TAGS),
|
||||||
|
has_build = false,
|
||||||
|
periodics = {
|
||||||
|
next_formed_req = 0,
|
||||||
|
next_build_req = 0,
|
||||||
|
next_state_req = 0,
|
||||||
|
next_tanks_req = 0
|
||||||
|
},
|
||||||
|
---@class dynamicv_session_db
|
||||||
|
db = {
|
||||||
|
formed = false,
|
||||||
|
build = {
|
||||||
|
last_update = 0,
|
||||||
|
length = 0,
|
||||||
|
width = 0,
|
||||||
|
height = 0,
|
||||||
|
min_pos = types.new_zero_coordinate(),
|
||||||
|
max_pos = types.new_zero_coordinate(),
|
||||||
|
tank_capacity = 0,
|
||||||
|
chem_tank_capacity = 0
|
||||||
|
},
|
||||||
|
state = {
|
||||||
|
last_update = 0,
|
||||||
|
container_mode = CONTAINER_MODE.BOTH ---@type container_mode
|
||||||
|
},
|
||||||
|
tanks = {
|
||||||
|
last_update = 0,
|
||||||
|
stored = types.new_empty_gas(),
|
||||||
|
fill = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
local public = self.session.get()
|
||||||
|
|
||||||
|
-- PRIVATE FUNCTIONS --
|
||||||
|
|
||||||
|
-- increment the container mode
|
||||||
|
local function _inc_cont_mode()
|
||||||
|
-- write coil 1 with unused value 0
|
||||||
|
self.session.send_request(TXN_TYPES.INC_CONT, MODBUS_FCODE.WRITE_SINGLE_COIL, { 1, 0 })
|
||||||
|
end
|
||||||
|
|
||||||
|
-- decrement the container mode
|
||||||
|
local function _dec_cont_mode()
|
||||||
|
-- write coil 2 with unused value 0
|
||||||
|
self.session.send_request(TXN_TYPES.DEC_CONT, MODBUS_FCODE.WRITE_SINGLE_COIL, { 2, 0 })
|
||||||
|
end
|
||||||
|
|
||||||
|
-- set the container mode
|
||||||
|
---@param mode container_mode
|
||||||
|
local function _set_cont_mode(mode)
|
||||||
|
-- write holding register 1
|
||||||
|
self.session.send_request(TXN_TYPES.SET_CONT, MODBUS_FCODE.WRITE_SINGLE_HOLD_REG, { 1, mode })
|
||||||
|
end
|
||||||
|
|
||||||
|
-- query if the multiblock is formed
|
||||||
|
local function _request_formed()
|
||||||
|
-- read discrete input 1 (start = 1, count = 1)
|
||||||
|
self.session.send_request(TXN_TYPES.FORMED, MODBUS_FCODE.READ_DISCRETE_INPUTS, { 1, 1 })
|
||||||
|
end
|
||||||
|
|
||||||
|
-- query the build of the device
|
||||||
|
local function _request_build()
|
||||||
|
-- read input registers 1 through 7 (start = 1, count = 7)
|
||||||
|
self.session.send_request(TXN_TYPES.BUILD, MODBUS_FCODE.READ_INPUT_REGS, { 1, 7 })
|
||||||
|
end
|
||||||
|
|
||||||
|
-- query the state of the device
|
||||||
|
local function _request_state()
|
||||||
|
-- read holding register 1 (start = 1, count = 1)
|
||||||
|
self.session.send_request(TXN_TYPES.STATE, MODBUS_FCODE.READ_MUL_HOLD_REGS, { 1, 1 })
|
||||||
|
end
|
||||||
|
|
||||||
|
-- query the tanks of the device
|
||||||
|
local function _request_tanks()
|
||||||
|
-- read input registers 8 through 9 (start = 8, count = 2)
|
||||||
|
self.session.send_request(TXN_TYPES.TANKS, MODBUS_FCODE.READ_INPUT_REGS, { 8, 2 })
|
||||||
|
end
|
||||||
|
|
||||||
|
-- PUBLIC FUNCTIONS --
|
||||||
|
|
||||||
|
-- handle a packet
|
||||||
|
---@param m_pkt modbus_frame
|
||||||
|
function public.handle_packet(m_pkt)
|
||||||
|
local txn_type = self.session.try_resolve(m_pkt)
|
||||||
|
if txn_type == false then
|
||||||
|
-- nothing to do
|
||||||
|
elseif txn_type == TXN_TYPES.FORMED then
|
||||||
|
-- formed response
|
||||||
|
-- load in data if correct length
|
||||||
|
if m_pkt.length == 1 then
|
||||||
|
self.db.formed = m_pkt.data[1]
|
||||||
|
|
||||||
|
if not self.db.formed then self.has_build = false end
|
||||||
|
else
|
||||||
|
log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")")
|
||||||
|
end
|
||||||
|
elseif txn_type == TXN_TYPES.BUILD then
|
||||||
|
-- build response
|
||||||
|
if m_pkt.length == 7 then
|
||||||
|
self.db.build.last_update = util.time_ms()
|
||||||
|
self.db.build.length = m_pkt.data[1]
|
||||||
|
self.db.build.width = m_pkt.data[2]
|
||||||
|
self.db.build.height = m_pkt.data[3]
|
||||||
|
self.db.build.min_pos = m_pkt.data[4]
|
||||||
|
self.db.build.max_pos = m_pkt.data[5]
|
||||||
|
self.db.build.tank_capacity = m_pkt.data[6]
|
||||||
|
self.db.build.chem_tank_capacity = m_pkt.data[7]
|
||||||
|
self.has_build = true
|
||||||
|
|
||||||
|
out_queue.push_data(unit_session.RTU_US_DATA.BUILD_CHANGED, { unit = advert.reactor, type = advert.type })
|
||||||
|
else
|
||||||
|
log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")")
|
||||||
|
end
|
||||||
|
elseif txn_type == TXN_TYPES.STATE then
|
||||||
|
-- state response
|
||||||
|
if m_pkt.length == 1 then
|
||||||
|
self.db.state.last_update = util.time_ms()
|
||||||
|
self.db.state.container_mode = m_pkt.data[1]
|
||||||
|
else
|
||||||
|
log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")")
|
||||||
|
end
|
||||||
|
elseif txn_type == TXN_TYPES.TANKS then
|
||||||
|
-- tanks response
|
||||||
|
if m_pkt.length == 2 then
|
||||||
|
self.db.tanks.last_update = util.time_ms()
|
||||||
|
self.db.tanks.stored = m_pkt.data[1]
|
||||||
|
self.db.tanks.fill = m_pkt.data[2]
|
||||||
|
else
|
||||||
|
log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")")
|
||||||
|
end
|
||||||
|
elseif txn_type == TXN_TYPES.INC_CONT or txn_type == TXN_TYPES.DEC_CONT or txn_type == TXN_TYPES.SET_CONT then
|
||||||
|
-- successful acknowledgement
|
||||||
|
elseif txn_type == nil then
|
||||||
|
log.error(log_tag .. "unknown transaction reply")
|
||||||
|
else
|
||||||
|
log.error(log_tag .. "unknown transaction type " .. txn_type)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- update this runner
|
||||||
|
---@param time_now integer milliseconds
|
||||||
|
function public.update(time_now)
|
||||||
|
-- check command queue
|
||||||
|
while self.session.in_q.ready() do
|
||||||
|
-- get a new message to process
|
||||||
|
local msg = self.session.in_q.pop()
|
||||||
|
|
||||||
|
if msg ~= nil then
|
||||||
|
if msg.qtype == mqueue.TYPE.COMMAND then
|
||||||
|
-- instruction
|
||||||
|
local cmd = msg.message
|
||||||
|
|
||||||
|
if cmd == DTV_RTU_S_CMDS.INC_CONT_MODE then
|
||||||
|
_inc_cont_mode()
|
||||||
|
elseif cmd == DTV_RTU_S_CMDS.DEC_CONT_MODE then
|
||||||
|
_dec_cont_mode()
|
||||||
|
else
|
||||||
|
log.debug(util.c(log_tag, "unrecognized in-queue command ", cmd))
|
||||||
|
end
|
||||||
|
elseif msg.qtype == mqueue.TYPE.DATA then
|
||||||
|
-- instruction with body
|
||||||
|
local cmd = msg.message ---@type queue_data
|
||||||
|
if cmd.key == DTV_RTU_S_DATA.SET_CONT_MODE then
|
||||||
|
if cmd.val == types.CONTAINER_MODE.BOTH or
|
||||||
|
cmd.val == types.CONTAINER_MODE.FILL or
|
||||||
|
cmd.val == types.CONTAINER_MODE.EMPTY then
|
||||||
|
_set_cont_mode(cmd.val)
|
||||||
|
else
|
||||||
|
log.debug(util.c(log_tag, "unrecognized container mode \"", cmd.val, "\""))
|
||||||
|
end
|
||||||
|
else
|
||||||
|
log.debug(util.c(log_tag, "unrecognized in-queue data ", cmd.key))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- max 100ms spent processing queue
|
||||||
|
if util.time() - time_now > 100 then
|
||||||
|
log.warning(log_tag .. "exceeded 100ms queue process limit")
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
time_now = util.time()
|
||||||
|
|
||||||
|
-- handle periodics
|
||||||
|
|
||||||
|
if self.periodics.next_formed_req <= time_now then
|
||||||
|
_request_formed()
|
||||||
|
self.periodics.next_formed_req = time_now + PERIODICS.FORMED
|
||||||
|
end
|
||||||
|
|
||||||
|
if self.db.formed then
|
||||||
|
if not self.has_build and self.periodics.next_build_req <= time_now then
|
||||||
|
_request_build()
|
||||||
|
self.periodics.next_build_req = time_now + PERIODICS.BUILD
|
||||||
|
end
|
||||||
|
|
||||||
|
if self.periodics.next_state_req <= time_now then
|
||||||
|
_request_state()
|
||||||
|
self.periodics.next_state_req = time_now + PERIODICS.STATE
|
||||||
|
end
|
||||||
|
|
||||||
|
if self.periodics.next_tanks_req <= time_now then
|
||||||
|
_request_tanks()
|
||||||
|
self.periodics.next_tanks_req = time_now + PERIODICS.TANKS
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
self.session.post_update()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- invalidate build cache
|
||||||
|
function public.invalidate_cache()
|
||||||
|
self.periodics.next_formed_req = 0
|
||||||
|
self.periodics.next_build_req = 0
|
||||||
|
self.has_build = false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- get the unit session database
|
||||||
|
---@nodiscard
|
||||||
|
function public.get_db() return self.db end
|
||||||
|
|
||||||
|
return public
|
||||||
|
end
|
||||||
|
|
||||||
|
return dynamicv
|
@ -1,16 +1,31 @@
|
|||||||
---@class rtu_unit_qtypes
|
---@class rtu_unit_qtypes
|
||||||
local qtypes = {}
|
local qtypes = {}
|
||||||
|
|
||||||
|
-- turbine valve rtu session commands
|
||||||
local TBV_RTU_S_CMDS = {
|
local TBV_RTU_S_CMDS = {
|
||||||
INC_DUMP_MODE = 1,
|
INC_DUMP_MODE = 1,
|
||||||
DEC_DUMP_MODE = 2
|
DEC_DUMP_MODE = 2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
-- turbine valve rtu session commands w/ parameters
|
||||||
local TBV_RTU_S_DATA = {
|
local TBV_RTU_S_DATA = {
|
||||||
SET_DUMP_MODE = 1
|
SET_DUMP_MODE = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
-- dynamic tank valve rtu session commands
|
||||||
|
local DTV_RTU_S_CMDS = {
|
||||||
|
INC_CONT_MODE = 1,
|
||||||
|
DEC_CONT_MODE = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
-- dynamic tank valve rtu session commands w/ parameters
|
||||||
|
local DTV_RTU_S_DATA = {
|
||||||
|
SET_CONT_MODE = 1
|
||||||
|
}
|
||||||
|
|
||||||
qtypes.TBV_RTU_S_CMDS = TBV_RTU_S_CMDS
|
qtypes.TBV_RTU_S_CMDS = TBV_RTU_S_CMDS
|
||||||
qtypes.TBV_RTU_S_DATA = TBV_RTU_S_DATA
|
qtypes.TBV_RTU_S_DATA = TBV_RTU_S_DATA
|
||||||
|
qtypes.DTV_RTU_S_CMDS = DTV_RTU_S_CMDS
|
||||||
|
qtypes.DTV_RTU_S_DATA = DTV_RTU_S_DATA
|
||||||
|
|
||||||
return qtypes
|
return qtypes
|
||||||
|
@ -21,7 +21,7 @@ local supervisor = require("supervisor.supervisor")
|
|||||||
|
|
||||||
local svsessions = require("supervisor.session.svsessions")
|
local svsessions = require("supervisor.session.svsessions")
|
||||||
|
|
||||||
local SUPERVISOR_VERSION = "v0.18.0"
|
local SUPERVISOR_VERSION = "v0.20.2"
|
||||||
|
|
||||||
local println = util.println
|
local println = util.println
|
||||||
local println_ts = util.println_ts
|
local println_ts = util.println_ts
|
||||||
@ -116,7 +116,7 @@ local function main()
|
|||||||
if not fp_ok then
|
if not fp_ok then
|
||||||
renderer.close_ui()
|
renderer.close_ui()
|
||||||
println_ts(util.c("UI error: ", message))
|
println_ts(util.c("UI error: ", message))
|
||||||
log.error(util.c("GUI crashed with error ", message))
|
log.error(util.c("front panel GUI render failed with error ", message))
|
||||||
else
|
else
|
||||||
-- redefine println_ts local to not print as we have the front panel running
|
-- redefine println_ts local to not print as we have the front panel running
|
||||||
println_ts = function (_) end
|
println_ts = function (_) end
|
||||||
@ -153,7 +153,13 @@ local function main()
|
|||||||
println_ts("wireless modem disconnected!")
|
println_ts("wireless modem disconnected!")
|
||||||
log.warning("comms modem disconnected")
|
log.warning("comms modem disconnected")
|
||||||
|
|
||||||
databus.tx_hw_modem(false)
|
local other_modem = ppm.get_wireless_modem()
|
||||||
|
if other_modem then
|
||||||
|
log.info("found another wireless modem, using it for comms")
|
||||||
|
nic.connect(other_modem)
|
||||||
|
else
|
||||||
|
databus.tx_hw_modem(false)
|
||||||
|
end
|
||||||
else
|
else
|
||||||
log.warning("non-comms modem disconnected")
|
log.warning("non-comms modem disconnected")
|
||||||
end
|
end
|
||||||
@ -164,7 +170,7 @@ local function main()
|
|||||||
|
|
||||||
if type ~= nil and device ~= nil then
|
if type ~= nil and device ~= nil then
|
||||||
if type == "modem" then
|
if type == "modem" then
|
||||||
if device.isWireless() and not nic.connected() then
|
if device.isWireless() and not nic.is_connected() then
|
||||||
-- reconnected modem
|
-- reconnected modem
|
||||||
nic.connect(device)
|
nic.connect(device)
|
||||||
|
|
||||||
@ -172,6 +178,8 @@ local function main()
|
|||||||
log.info("comms modem reconnected")
|
log.info("comms modem reconnected")
|
||||||
|
|
||||||
databus.tx_hw_modem(true)
|
databus.tx_hw_modem(true)
|
||||||
|
elseif device.isWireless() then
|
||||||
|
log.info("unused wireless modem reconnected")
|
||||||
else
|
else
|
||||||
log.info("wired modem reconnected")
|
log.info("wired modem reconnected")
|
||||||
end
|
end
|
||||||
|
@ -11,11 +11,13 @@ local rsctl = require("supervisor.session.rsctl")
|
|||||||
---@class reactor_control_unit
|
---@class reactor_control_unit
|
||||||
local unit = {}
|
local unit = {}
|
||||||
|
|
||||||
local WASTE_MODE = types.WASTE_MODE
|
local WASTE_MODE = types.WASTE_MODE
|
||||||
local ALARM = types.ALARM
|
local WASTE = types.WASTE_PRODUCT
|
||||||
local PRIO = types.ALARM_PRIORITY
|
local ALARM = types.ALARM
|
||||||
local ALARM_STATE = types.ALARM_STATE
|
local PRIO = types.ALARM_PRIORITY
|
||||||
local TRI_FAIL = types.TRI_FAIL
|
local ALARM_STATE = types.ALARM_STATE
|
||||||
|
local TRI_FAIL = types.TRI_FAIL
|
||||||
|
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
|
||||||
|
|
||||||
local PLC_S_CMDS = plc.PLC_S_CMDS
|
local PLC_S_CMDS = plc.PLC_S_CMDS
|
||||||
|
|
||||||
@ -68,10 +70,14 @@ function unit.new(reactor_id, num_boilers, num_turbines)
|
|||||||
num_turbines = num_turbines,
|
num_turbines = num_turbines,
|
||||||
types = { DT_KEYS = DT_KEYS, AISTATE = AISTATE },
|
types = { DT_KEYS = DT_KEYS, AISTATE = AISTATE },
|
||||||
-- rtus
|
-- rtus
|
||||||
|
rtu_list = {},
|
||||||
redstone = {},
|
redstone = {},
|
||||||
boilers = {},
|
boilers = {},
|
||||||
turbines = {},
|
turbines = {},
|
||||||
|
tanks = {},
|
||||||
|
snas = {},
|
||||||
envd = {},
|
envd = {},
|
||||||
|
sna_prod_rate = 0,
|
||||||
-- redstone control
|
-- redstone control
|
||||||
io_ctl = nil, ---@type rs_controller
|
io_ctl = nil, ---@type rs_controller
|
||||||
valves = {}, ---@type unit_valves
|
valves = {}, ---@type unit_valves
|
||||||
@ -89,7 +95,7 @@ function unit.new(reactor_id, num_boilers, num_turbines)
|
|||||||
damage_start = 0,
|
damage_start = 0,
|
||||||
damage_last = 0,
|
damage_last = 0,
|
||||||
damage_est_last = 0,
|
damage_est_last = 0,
|
||||||
waste_mode = WASTE_MODE.AUTO,
|
waste_product = WASTE.PLUTONIUM, ---@type WASTE_PRODUCT
|
||||||
status_text = { "UNKNOWN", "awaiting connection..." },
|
status_text = { "UNKNOWN", "awaiting connection..." },
|
||||||
-- logic for alarms
|
-- logic for alarms
|
||||||
had_reactor = false,
|
had_reactor = false,
|
||||||
@ -221,11 +227,15 @@ function unit.new(reactor_id, num_boilers, num_turbines)
|
|||||||
degraded = false,
|
degraded = false,
|
||||||
blade_count = 0,
|
blade_count = 0,
|
||||||
br100 = 0,
|
br100 = 0,
|
||||||
lim_br100 = 0
|
lim_br100 = 0,
|
||||||
|
waste_mode = WASTE_MODE.AUTO ---@type WASTE_MODE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
-- list for RTU session management
|
||||||
|
self.rtu_list = { self.redstone, self.boilers, self.turbines, self.tanks, self.snas, self.envd }
|
||||||
|
|
||||||
-- init redstone RTU I/O controller
|
-- init redstone RTU I/O controller
|
||||||
self.io_ctl = rsctl.new(self.redstone)
|
self.io_ctl = rsctl.new(self.redstone)
|
||||||
|
|
||||||
@ -341,14 +351,34 @@ function unit.new(reactor_id, num_boilers, num_turbines)
|
|||||||
emer_cool = emer_cool
|
emer_cool = emer_cool
|
||||||
}
|
}
|
||||||
|
|
||||||
--#endregion
|
-- route reactor waste for a given waste product
|
||||||
|
---@param product WASTE_PRODUCT waste product to route valves for
|
||||||
|
local function _set_waste_valves(product)
|
||||||
|
self.waste_product = product
|
||||||
|
|
||||||
-- unlink disconnected units
|
if product == WASTE.PLUTONIUM then
|
||||||
---@param sessions table
|
-- route through plutonium generation
|
||||||
local function _unlink_disconnected_units(sessions)
|
waste_pu.open()
|
||||||
util.filter_table(sessions, function (u) return u.is_connected() end)
|
waste_sna.close()
|
||||||
|
waste_po.close()
|
||||||
|
waste_sps.close()
|
||||||
|
elseif product == WASTE.POLONIUM then
|
||||||
|
-- route through polonium generation into pellets
|
||||||
|
waste_pu.close()
|
||||||
|
waste_sna.open()
|
||||||
|
waste_po.open()
|
||||||
|
waste_sps.close()
|
||||||
|
elseif product == WASTE.ANTI_MATTER then
|
||||||
|
-- route through polonium generation into SPS
|
||||||
|
waste_pu.close()
|
||||||
|
waste_sna.open()
|
||||||
|
waste_po.close()
|
||||||
|
waste_sps.open()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
-- PUBLIC FUNCTIONS --
|
-- PUBLIC FUNCTIONS --
|
||||||
|
|
||||||
---@class reactor_unit
|
---@class reactor_unit
|
||||||
@ -378,11 +408,12 @@ function unit.new(reactor_id, num_boilers, num_turbines)
|
|||||||
table.insert(self.redstone, rs_unit)
|
table.insert(self.redstone, rs_unit)
|
||||||
|
|
||||||
-- send or re-send waste settings
|
-- send or re-send waste settings
|
||||||
public.set_waste(self.waste_mode)
|
_set_waste_valves(self.waste_product)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- link a turbine RTU session
|
-- link a turbine RTU session
|
||||||
---@param turbine unit_session
|
---@param turbine unit_session
|
||||||
|
---@return boolean linked turbine accepted to associated device slot
|
||||||
function public.add_turbine(turbine)
|
function public.add_turbine(turbine)
|
||||||
if #self.turbines < num_turbines and turbine.get_device_idx() <= num_turbines then
|
if #self.turbines < num_turbines and turbine.get_device_idx() <= num_turbines then
|
||||||
table.insert(self.turbines, turbine)
|
table.insert(self.turbines, turbine)
|
||||||
@ -392,13 +423,12 @@ function unit.new(reactor_id, num_boilers, num_turbines)
|
|||||||
_reset_dt(DT_KEYS.TurbinePower .. turbine.get_device_idx())
|
_reset_dt(DT_KEYS.TurbinePower .. turbine.get_device_idx())
|
||||||
|
|
||||||
return true
|
return true
|
||||||
else
|
else return false end
|
||||||
return false
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- link a boiler RTU session
|
-- link a boiler RTU session
|
||||||
---@param boiler unit_session
|
---@param boiler unit_session
|
||||||
|
---@return boolean linked boiler accepted to associated device slot
|
||||||
function public.add_boiler(boiler)
|
function public.add_boiler(boiler)
|
||||||
if #self.boilers < num_boilers and boiler.get_device_idx() <= num_boilers then
|
if #self.boilers < num_boilers and boiler.get_device_idx() <= num_boilers then
|
||||||
table.insert(self.boilers, boiler)
|
table.insert(self.boilers, boiler)
|
||||||
@ -410,24 +440,37 @@ function unit.new(reactor_id, num_boilers, num_turbines)
|
|||||||
_reset_dt(DT_KEYS.BoilerHCool .. boiler.get_device_idx())
|
_reset_dt(DT_KEYS.BoilerHCool .. boiler.get_device_idx())
|
||||||
|
|
||||||
return true
|
return true
|
||||||
else
|
else return false end
|
||||||
return false
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- link a dynamic tank RTU session
|
||||||
|
---@param dynamic_tank unit_session
|
||||||
|
---@return boolean linked dynamic tank accepted (max 1)
|
||||||
|
function public.add_tank(dynamic_tank)
|
||||||
|
if #self.tanks == 0 then
|
||||||
|
table.insert(self.tanks, dynamic_tank)
|
||||||
|
return true
|
||||||
|
else return false end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- link a solar neutron activator RTU session
|
||||||
|
---@param sna unit_session
|
||||||
|
function public.add_sna(sna) table.insert(self.snas, sna) end
|
||||||
|
|
||||||
-- link an environment detector RTU session
|
-- link an environment detector RTU session
|
||||||
---@param envd unit_session
|
---@param envd unit_session
|
||||||
|
---@return boolean linked environment detector accepted (max 1)
|
||||||
function public.add_envd(envd)
|
function public.add_envd(envd)
|
||||||
table.insert(self.envd, envd)
|
if #self.envd == 0 then
|
||||||
|
table.insert(self.envd, envd)
|
||||||
|
return true
|
||||||
|
else return false end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- purge devices associated with the given RTU session ID
|
-- purge devices associated with the given RTU session ID
|
||||||
---@param session integer RTU session ID
|
---@param session integer RTU session ID
|
||||||
function public.purge_rtu_devices(session)
|
function public.purge_rtu_devices(session)
|
||||||
util.filter_table(self.redstone, function (s) return s.get_session_id() ~= session end)
|
for _, v in pairs(self.rtu_list) do util.filter_table(v, function (s) return s.get_session_id() ~= session end) end
|
||||||
util.filter_table(self.boilers, function (s) return s.get_session_id() ~= session end)
|
|
||||||
util.filter_table(self.turbines, function (s) return s.get_session_id() ~= session end)
|
|
||||||
util.filter_table(self.envd, function (s) return s.get_session_id() ~= session end)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--#endregion
|
--#endregion
|
||||||
@ -445,10 +488,7 @@ function unit.new(reactor_id, num_boilers, num_turbines)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- unlink RTU unit sessions if they are closed
|
-- unlink RTU unit sessions if they are closed
|
||||||
_unlink_disconnected_units(self.redstone)
|
for _, v in pairs(self.rtu_list) do util.filter_table(v, function (u) return u.is_connected() end) end
|
||||||
_unlink_disconnected_units(self.boilers)
|
|
||||||
_unlink_disconnected_units(self.turbines)
|
|
||||||
_unlink_disconnected_units(self.envd)
|
|
||||||
|
|
||||||
-- update degraded state for auto control
|
-- update degraded state for auto control
|
||||||
self.db.control.degraded = (#self.boilers ~= num_boilers) or (#self.turbines ~= num_turbines) or (self.plc_i == nil)
|
self.db.control.degraded = (#self.boilers ~= num_boilers) or (#self.turbines ~= num_turbines) or (self.plc_i == nil)
|
||||||
@ -577,6 +617,15 @@ function unit.new(reactor_id, num_boilers, num_turbines)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- set automatic waste product if mode is set to auto
|
||||||
|
---@param product WASTE_PRODUCT waste product to generate
|
||||||
|
function public.auto_set_waste(product)
|
||||||
|
if self.db.control.waste_mode == WASTE_MODE.AUTO then
|
||||||
|
self.waste_product = product
|
||||||
|
_set_waste_valves(product)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
--#endregion
|
--#endregion
|
||||||
|
|
||||||
-- OPERATIONS --
|
-- OPERATIONS --
|
||||||
@ -621,34 +670,18 @@ function unit.new(reactor_id, num_boilers, num_turbines)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- route reactor waste
|
-- set waste processing mode
|
||||||
---@param mode WASTE_MODE waste handling mode
|
---@param mode WASTE_MODE processing mode
|
||||||
function public.set_waste(mode)
|
function public.set_waste_mode(mode)
|
||||||
if mode == WASTE_MODE.AUTO then
|
self.db.control.waste_mode = mode
|
||||||
---@todo automatic waste routing
|
|
||||||
self.waste_mode = mode
|
if mode == WASTE_MODE.MANUAL_PLUTONIUM then
|
||||||
elseif mode == WASTE_MODE.PLUTONIUM then
|
_set_waste_valves(WASTE.PLUTONIUM)
|
||||||
-- route through plutonium generation
|
elseif mode == WASTE_MODE.MANUAL_POLONIUM then
|
||||||
self.waste_mode = mode
|
_set_waste_valves(WASTE.POLONIUM)
|
||||||
waste_pu.open()
|
elseif mode == WASTE_MODE.MANUAL_ANTI_MATTER then
|
||||||
waste_sna.close()
|
_set_waste_valves(WASTE.ANTI_MATTER)
|
||||||
waste_po.close()
|
elseif mode > WASTE_MODE.MANUAL_ANTI_MATTER then
|
||||||
waste_sps.close()
|
|
||||||
elseif mode == WASTE_MODE.POLONIUM then
|
|
||||||
-- route through polonium generation into pellets
|
|
||||||
self.waste_mode = mode
|
|
||||||
waste_pu.close()
|
|
||||||
waste_sna.open()
|
|
||||||
waste_po.open()
|
|
||||||
waste_sps.close()
|
|
||||||
elseif mode == WASTE_MODE.ANTI_MATTER then
|
|
||||||
-- route through polonium generation into SPS
|
|
||||||
self.waste_mode = mode
|
|
||||||
waste_pu.close()
|
|
||||||
waste_sna.open()
|
|
||||||
waste_po.close()
|
|
||||||
waste_sps.open()
|
|
||||||
else
|
|
||||||
log.debug(util.c("invalid waste mode setting ", mode))
|
log.debug(util.c("invalid waste mode setting ", mode))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -686,21 +719,25 @@ function unit.new(reactor_id, num_boilers, num_turbines)
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
-- get build properties of all machines
|
-- get build properties of machines
|
||||||
|
--
|
||||||
|
-- filter options
|
||||||
|
-- - nil to include all builds
|
||||||
|
-- - -1 to include only PLC build
|
||||||
|
-- - RTU_UNIT_TYPE to include all builds of machines of that type
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
---@param inc_plc boolean? true/nil to include PLC build, false to exclude
|
---@param filter -1|RTU_UNIT_TYPE? filter as described above
|
||||||
---@param inc_boilers boolean? true/nil to include boiler builds, false to exclude
|
function public.get_build(filter)
|
||||||
---@param inc_turbines boolean? true/nil to include turbine builds, false to exclude
|
local all = filter == nil
|
||||||
function public.get_build(inc_plc, inc_boilers, inc_turbines)
|
|
||||||
local build = {}
|
local build = {}
|
||||||
|
|
||||||
if inc_plc ~= false then
|
if all or (filter == -1) then
|
||||||
if self.plc_i ~= nil then
|
if self.plc_i ~= nil then
|
||||||
build.reactor = self.plc_i.get_struct()
|
build.reactor = self.plc_i.get_struct()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if inc_boilers ~= false then
|
if all or (filter == RTU_UNIT_TYPE.BOILER_VALVE) then
|
||||||
build.boilers = {}
|
build.boilers = {}
|
||||||
for i = 1, #self.boilers do
|
for i = 1, #self.boilers do
|
||||||
local boiler = self.boilers[i] ---@type unit_session
|
local boiler = self.boilers[i] ---@type unit_session
|
||||||
@ -708,7 +745,7 @@ function unit.new(reactor_id, num_boilers, num_turbines)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if inc_turbines ~= false then
|
if all or (filter == RTU_UNIT_TYPE.TURBINE_VALVE) then
|
||||||
build.turbines = {}
|
build.turbines = {}
|
||||||
for i = 1, #self.turbines do
|
for i = 1, #self.turbines do
|
||||||
local turbine = self.turbines[i] ---@type unit_session
|
local turbine = self.turbines[i] ---@type unit_session
|
||||||
@ -716,6 +753,14 @@ function unit.new(reactor_id, num_boilers, num_turbines)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if all or (filter == RTU_UNIT_TYPE.DYNAMIC_VALVE) then
|
||||||
|
build.tanks = {}
|
||||||
|
for i = 1, #self.tanks do
|
||||||
|
local tank = self.tanks[i] ---@type unit_session
|
||||||
|
build.tanks[tank.get_device_idx()] = { tank.get_db().formed, tank.get_db().build }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
return build
|
return build
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -730,6 +775,14 @@ function unit.new(reactor_id, num_boilers, num_turbines)
|
|||||||
return status
|
return status
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- get the current burn rate (actual rate)
|
||||||
|
---@nodiscard
|
||||||
|
function public.get_burn_rate()
|
||||||
|
local rate = 0
|
||||||
|
if self.plc_i ~= nil then rate = self.plc_i.get_status().act_burn_rate end
|
||||||
|
return rate or 0
|
||||||
|
end
|
||||||
|
|
||||||
-- get RTU statuses
|
-- get RTU statuses
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
function public.get_rtu_statuses()
|
function public.get_rtu_statuses()
|
||||||
@ -738,40 +791,54 @@ function unit.new(reactor_id, num_boilers, num_turbines)
|
|||||||
-- status of boilers (including tanks)
|
-- status of boilers (including tanks)
|
||||||
status.boilers = {}
|
status.boilers = {}
|
||||||
for i = 1, #self.boilers do
|
for i = 1, #self.boilers do
|
||||||
local boiler = self.boilers[i] ---@type unit_session
|
local boiler = self.boilers[i] ---@type unit_session
|
||||||
status.boilers[boiler.get_device_idx()] = {
|
local db = boiler.get_db() ---@type boilerv_session_db
|
||||||
boiler.is_faulted(),
|
status.boilers[boiler.get_device_idx()] = { boiler.is_faulted(), db.formed, db.state, db.tanks }
|
||||||
boiler.get_db().formed,
|
|
||||||
boiler.get_db().state,
|
|
||||||
boiler.get_db().tanks
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- status of turbines (including tanks)
|
-- status of turbines (including tanks)
|
||||||
status.turbines = {}
|
status.turbines = {}
|
||||||
for i = 1, #self.turbines do
|
for i = 1, #self.turbines do
|
||||||
local turbine = self.turbines[i] ---@type unit_session
|
local turbine = self.turbines[i] ---@type unit_session
|
||||||
status.turbines[turbine.get_device_idx()] = {
|
local db = turbine.get_db() ---@type turbinev_session_db
|
||||||
turbine.is_faulted(),
|
status.turbines[turbine.get_device_idx()] = { turbine.is_faulted(), db.formed, db.state, db.tanks }
|
||||||
turbine.get_db().formed,
|
|
||||||
turbine.get_db().state,
|
|
||||||
turbine.get_db().tanks
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- status of dynamic tanks
|
||||||
|
status.tanks = {}
|
||||||
|
for i = 1, #self.tanks do
|
||||||
|
local tank = self.tanks[i] ---@type unit_session
|
||||||
|
local db = tank.get_db() ---@type dynamicv_session_db
|
||||||
|
status.tanks[tank.get_device_idx()] = { tank.is_faulted(), db.formed, db.state, db.tanks }
|
||||||
|
end
|
||||||
|
|
||||||
|
-- basic SNA statistical information
|
||||||
|
status.sna = { #self.snas, public.get_sna_rate() }
|
||||||
|
|
||||||
-- radiation monitors (environment detectors)
|
-- radiation monitors (environment detectors)
|
||||||
status.rad_mon = {}
|
status.rad_mon = {}
|
||||||
for i = 1, #self.envd do
|
for i = 1, #self.envd do
|
||||||
local envd = self.envd[i] ---@type unit_session
|
local envd = self.envd[i] ---@type unit_session
|
||||||
status.rad_mon[envd.get_device_idx()] = {
|
status.rad_mon[envd.get_device_idx()] = { envd.is_faulted(), envd.get_db().radiation }
|
||||||
envd.is_faulted(),
|
|
||||||
envd.get_db().radiation
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return status
|
return status
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- get the current total [max] production rate is
|
||||||
|
---@nodiscard
|
||||||
|
---@return number total_avail_rate
|
||||||
|
function public.get_sna_rate()
|
||||||
|
local total_avail_rate = 0
|
||||||
|
|
||||||
|
for i = 1, #self.snas do
|
||||||
|
local db = self.snas[i].get_db() ---@type sna_session_db
|
||||||
|
total_avail_rate = total_avail_rate + db.state.production_rate
|
||||||
|
end
|
||||||
|
|
||||||
|
return total_avail_rate
|
||||||
|
end
|
||||||
|
|
||||||
-- get the annunciator status
|
-- get the annunciator status
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
function public.get_annunciator() return self.db.annunciator end
|
function public.get_annunciator() return self.db.annunciator end
|
||||||
@ -787,7 +854,14 @@ function unit.new(reactor_id, num_boilers, num_turbines)
|
|||||||
-- get unit state
|
-- get unit state
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
function public.get_state()
|
function public.get_state()
|
||||||
return { self.status_text[1], self.status_text[2], self.waste_mode, self.db.control.ready, self.db.control.degraded }
|
return {
|
||||||
|
self.status_text[1],
|
||||||
|
self.status_text[2],
|
||||||
|
self.db.control.ready,
|
||||||
|
self.db.control.degraded,
|
||||||
|
self.db.control.waste_mode,
|
||||||
|
self.waste_product
|
||||||
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
-- get the reactor ID
|
-- get the reactor ID
|
||||||
|
Loading…
x
Reference in New Issue
Block a user