Compare commits
41 Commits
v1.5.0-bet
...
v1.6.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
acb7b5b4cb | ||
|
|
9bd79dacad | ||
|
|
c544d140bf | ||
|
|
353cb3622b | ||
|
|
b54f15bad6 | ||
|
|
4d9783beca | ||
|
|
5529774b0e | ||
|
|
2a541ef3fe | ||
|
|
e1b4d72ef8 | ||
|
|
6a0992c7a4 | ||
|
|
cff7c724be | ||
|
|
47bda73afe | ||
|
|
8daedc109c | ||
|
|
a164c18a50 | ||
|
|
4d663ada8d | ||
|
|
084a153a79 | ||
|
|
4ed6ec1c63 | ||
|
|
d3c2ba7bee | ||
|
|
55ff9dad4b | ||
|
|
0d6022f5e3 | ||
|
|
8b136d78a8 | ||
|
|
a5214730ef | ||
|
|
9f3ad3caf0 | ||
|
|
9bb2a99be5 | ||
|
|
65ace26258 | ||
|
|
61d975d13f | ||
|
|
1d7d6e9817 | ||
|
|
a2e0999cea | ||
|
|
1edee7f64b | ||
|
|
df61ec2c62 | ||
|
|
bf7a316b04 | ||
|
|
96c4444184 | ||
|
|
59eac62c33 | ||
|
|
ab193db153 | ||
|
|
7d65bba589 | ||
|
|
dcef5a96f0 | ||
|
|
ba0900ac65 | ||
|
|
8f54e95519 | ||
|
|
7b9824b6f9 | ||
|
|
b6835fc7d1 | ||
|
|
bc5a94cd3b |
56
README.md
56
README.md
@@ -7,6 +7,27 @@ Configurable ComputerCraft SCADA system for multi-reactor control of Mekanism fi
|
|||||||

|

|
||||||

|

|
||||||
|
|
||||||
|
### [Join](https://discord.gg/R9NSCkhcwt) the Discord!
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Released Component Versions
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
Mod Requirements:
|
Mod Requirements:
|
||||||
- CC: Tweaked
|
- CC: Tweaked
|
||||||
- Mekanism v10.1+
|
- Mekanism v10.1+
|
||||||
@@ -16,32 +37,11 @@ Mod Recommendations:
|
|||||||
|
|
||||||
v10.1+ is required due the complete support of CC:Tweaked added in Mekanism v10.1
|
v10.1+ is required due the complete support of CC:Tweaked added in Mekanism v10.1
|
||||||
|
|
||||||
There was also an apparent bug with boilers disconnecting and reconnecting when active in my test world on 10.0.24, so it may not even have been an option to fully implement this with support for 10.0.
|
|
||||||
|
|
||||||
## Released Component Versions
|
|
||||||
|
|
||||||
### Core
|
|
||||||
|
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||
### Utilities
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
### Applications
|
|
||||||
|
|
||||||

|
|
||||||

|
|
||||||

|
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
You can install this on a ComputerCraft computer using either:
|
You can install this on a ComputerCraft computer using either:
|
||||||
* `wget https://raw.githubusercontent.com/MikaylaFischler/cc-mek-scada/main/ccmsi.lua`
|
* `wget https://raw.githubusercontent.com/MikaylaFischler/cc-mek-scada/main/ccmsi.lua`
|
||||||
* `pastebin get eRz6cUNM ccmsi.lua`
|
* `pastebin get RGasyTM4 ccmsi.lua`
|
||||||
|
|
||||||
## [SCADA](https://en.wikipedia.org/wiki/SCADA)
|
## [SCADA](https://en.wikipedia.org/wiki/SCADA)
|
||||||
> Supervisory control and data acquisition (SCADA) is a control system architecture comprising computers, networked data communications and graphical user interfaces for high-level supervision of machines and processes. It also covers sensors and other devices, such as programmable logic controllers, which interface with process plant or machinery.
|
> Supervisory control and data acquisition (SCADA) is a control system architecture comprising computers, networked data communications and graphical user interfaces for high-level supervision of machines and processes. It also covers sensors and other devices, such as programmable logic controllers, which interface with process plant or machinery.
|
||||||
@@ -86,14 +86,8 @@ A vaguely-modbus [modbus](https://en.wikipedia.org/wiki/Modbus) communication pr
|
|||||||
- Input Registers: Multi-Byte Read-Only (analog inputs)
|
- Input Registers: Multi-Byte Read-Only (analog inputs)
|
||||||
- Holding Registers: Multi-Byte Read/Write (analog I/O)
|
- Holding Registers: Multi-Byte Read/Write (analog I/O)
|
||||||
|
|
||||||
### Security and Encryption
|
### Security
|
||||||
|
|
||||||
TBD, I am planning on AES symmetric encryption for security + HMAC to prevent replay attacks. This will be done utilizing this codebase: https://github.com/somesocks/lua-lockbox.
|
HMAC message authentication is available as a configuration option to prevent replay attacks and generally prevent control or false data reporting within a system's network. This is done utilizing the [lua-lockbox](https://github.com/somesocks/lua-lockbox) project.
|
||||||
|
|
||||||
This is somewhat important here as otherwise anyone can just control your setup, which is undeseriable. Unlike normal Minecraft PVP chaos, it would be very difficult to identify who is messing with your system, as with an Ender Modem they can do it from effectively anywhere and the server operators would have to check every computer's filesystem to find suspicious code.
|
The other, simpler security feature is to enforce a maximum authorized transmission range, which is also a configurable feature on each device.
|
||||||
|
|
||||||
The other security mitigation for commanding (no effect on monitoring) is to enforce a maximum authorized transmission range, which has been added as a configurable feature.
|
|
||||||
|
|
||||||
## Known Issues
|
|
||||||
|
|
||||||
None yet since the switch to requiring 10.1+!
|
|
||||||
|
|||||||
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
|
||||||
|
|||||||
Reference in New Issue
Block a user