Compare commits
203 Commits
v1.8.22-be
...
v1.9.3-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6eb9ac5845 | ||
|
|
acaa9369f4 | ||
|
|
2998371b89 | ||
|
|
12664c6190 | ||
|
|
abe0c45534 | ||
|
|
a104d8ba83 | ||
|
|
a629c04d11 | ||
|
|
6d3b35a41d | ||
|
|
e1ac42f5f8 | ||
|
|
9e59883a84 | ||
|
|
79d63fce78 | ||
|
|
ce92fd15ef | ||
|
|
919ca6f0af | ||
|
|
264edc0030 | ||
|
|
fcb17ae5e7 | ||
|
|
35f82af2e2 | ||
|
|
1a2ecd0599 | ||
|
|
5f8c947105 | ||
|
|
41e6d89a4b | ||
|
|
f01fb62863 | ||
|
|
8f6425b814 | ||
|
|
069a7ce0ad | ||
|
|
8eff1c0d76 | ||
|
|
7404e6da31 | ||
|
|
12ead136a3 | ||
|
|
e3dbda3c54 | ||
|
|
41b6a558d5 | ||
|
|
07bb0f13e3 | ||
|
|
f1014ce941 | ||
|
|
0debbdc167 | ||
|
|
0a26629e20 | ||
|
|
9393b1830d | ||
|
|
0df1e48780 | ||
|
|
b8c30ba8a4 | ||
|
|
eafd39fa35 | ||
|
|
86dc92f09a | ||
|
|
e6f5ab8ef4 | ||
|
|
be462db50b | ||
|
|
1dc3d82e59 | ||
|
|
fa2a6d7786 | ||
|
|
04c53c7074 | ||
|
|
1af2cdba8d | ||
|
|
0d7302dc8e | ||
|
|
48ec973695 | ||
|
|
ee868eb607 | ||
|
|
e4da9a62d9 | ||
|
|
c8910bfc40 | ||
|
|
d6e3a67562 | ||
|
|
f7c0a1d97d | ||
|
|
13509136b8 | ||
|
|
bfab2d6af2 | ||
|
|
ae055a7d99 | ||
|
|
592f1110ed | ||
|
|
97875f4e52 | ||
|
|
657261642c | ||
|
|
0da944c3ea | ||
|
|
1b692b5b9a | ||
|
|
b4a9366f73 | ||
|
|
2b3099ac59 | ||
|
|
cd654fb9b8 | ||
|
|
ad834218c2 | ||
|
|
c6a7de2669 | ||
|
|
d374967cb7 | ||
|
|
b1ad2084f2 | ||
|
|
1971153dae | ||
|
|
5fc8912590 | ||
|
|
122fa1a7a7 | ||
|
|
2b73196130 | ||
|
|
d45f19c8a6 | ||
|
|
a9f68ce3ea | ||
|
|
7ab5ea710f | ||
|
|
de41ee56aa | ||
|
|
99ea59a86b | ||
|
|
234652b886 | ||
|
|
e37e3ba696 | ||
|
|
20b71bead1 | ||
|
|
18d093e72d | ||
|
|
21eae4932f | ||
|
|
9163fb14c4 | ||
|
|
02db01524c | ||
|
|
e0d1eb3445 | ||
|
|
7c22c172d5 | ||
|
|
7b29702000 | ||
|
|
425a6c8775 | ||
|
|
eafcd89aba | ||
|
|
016cd988e1 | ||
|
|
06a8e3d9ca | ||
|
|
5f22069ce1 | ||
|
|
ecdaf78ed0 | ||
|
|
3b2fb00285 | ||
|
|
54167e2113 | ||
|
|
22cdbc8638 | ||
|
|
556331f75b | ||
|
|
40cb9f599a | ||
|
|
cab3427c70 | ||
|
|
4e31b33b09 | ||
|
|
f32855084e | ||
|
|
b3cf40a01a | ||
|
|
cf9e26ac8f | ||
|
|
cbc84c5998 | ||
|
|
869e67710f | ||
|
|
1b9d3d3f23 | ||
|
|
0a060b656c | ||
|
|
c859c22964 | ||
|
|
3767c0f8d9 | ||
|
|
fbebc2a021 | ||
|
|
afd6800be6 | ||
|
|
baba2e1411 | ||
|
|
127c878794 | ||
|
|
767b54c3e6 | ||
|
|
1c57fc1fe3 | ||
|
|
2d83de8b88 | ||
|
|
4a4234c8c8 | ||
|
|
eb197e7fdd | ||
|
|
78b0e1bf24 | ||
|
|
cbc004a6c7 | ||
|
|
d05abf6e00 | ||
|
|
813e30bcde | ||
|
|
fb139949f8 | ||
|
|
2fdc9feea7 | ||
|
|
eabb065d17 | ||
|
|
fb221a566c | ||
|
|
cd4caf0163 | ||
|
|
1190fe2dd5 | ||
|
|
4cb6f9ca0f | ||
|
|
872082b970 | ||
|
|
071df9e431 | ||
|
|
ae85cfc579 | ||
|
|
1dece587b2 | ||
|
|
01c5d62f38 | ||
|
|
c6a5d487e0 | ||
|
|
ba4a5aa85e | ||
|
|
451232ce91 | ||
|
|
11fa9f625d | ||
|
|
b61fd2c620 | ||
|
|
cb2ebd409d | ||
|
|
57c75be997 | ||
|
|
22a7fdae88 | ||
|
|
5a9768f005 | ||
|
|
fd414a814c | ||
|
|
909bd78912 | ||
|
|
c487b22fe1 | ||
|
|
9892fbc602 | ||
|
|
1695b58329 | ||
|
|
178681941f | ||
|
|
de3fa163c5 | ||
|
|
68977bcdea | ||
|
|
c4c45ae329 | ||
|
|
e8b8dfde5b | ||
|
|
3f42adea5b | ||
|
|
feabed6a1e | ||
|
|
ffd4bae2d5 | ||
|
|
bc4228d4eb | ||
|
|
4501cb783f | ||
|
|
78225a8cf4 | ||
|
|
9b443709f4 | ||
|
|
33803a1ace | ||
|
|
fe8ac349d6 | ||
|
|
1538fb3d26 | ||
|
|
a546b946ee | ||
|
|
019284de7b | ||
|
|
849caa2521 | ||
|
|
6838d21bd7 | ||
|
|
6bd43af5c0 | ||
|
|
7eebf0524f | ||
|
|
20bffec79f | ||
|
|
49b545ba2c | ||
|
|
e54ecf43ed | ||
|
|
0544587d84 | ||
|
|
72fcc01acd | ||
|
|
c6343e5956 | ||
|
|
7372908637 | ||
|
|
50b2f62c66 | ||
|
|
68851a6b30 | ||
|
|
56e4f93db8 | ||
|
|
bc7a38b9d4 | ||
|
|
8bdb6b9ed6 | ||
|
|
8469bb78a3 | ||
|
|
fc603677ef | ||
|
|
532c15e258 | ||
|
|
7b6b1de539 | ||
|
|
edde416889 | ||
|
|
8fad94c4c6 | ||
|
|
3e1f567c0f | ||
|
|
bafd20ec22 | ||
|
|
21591f4d7d | ||
|
|
b15835ab87 | ||
|
|
d36f7adab1 | ||
|
|
8439e02586 | ||
|
|
764638c212 | ||
|
|
459ddbaef8 | ||
|
|
627dd99dd7 | ||
|
|
129bf8809a | ||
|
|
55f6e4756e | ||
|
|
e27d5eeb85 | ||
|
|
661bef063c | ||
|
|
801fd99448 | ||
|
|
c1c3723b67 | ||
|
|
7fb88becb8 | ||
|
|
051d119b99 | ||
|
|
21a3a18764 | ||
|
|
91cb51bad9 | ||
|
|
8ddc233da0 |
@@ -28,6 +28,9 @@ def minify(path: str):
|
|||||||
contents = f.read()
|
contents = f.read()
|
||||||
f.close()
|
f.close()
|
||||||
|
|
||||||
|
# remove --[[@as type]] hints before anything, since it would detect as multiline comments
|
||||||
|
contents = re.sub(r' --+\[.+]]', '', contents)
|
||||||
|
|
||||||
if re.search(r'--+\[+', contents) != None:
|
if re.search(r'--+\[+', contents) != None:
|
||||||
# absolutely not dealing with lua multiline comments
|
# absolutely not dealing with lua multiline comments
|
||||||
# - there are more important things to do
|
# - there are more important things to do
|
||||||
|
|||||||
175
ccmsi.lua
175
ccmsi.lua
@@ -15,7 +15,7 @@ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|||||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
]]--
|
]]--
|
||||||
|
|
||||||
local CCMSI_VERSION = "v1.19"
|
local CCMSI_VERSION = "v1.21"
|
||||||
|
|
||||||
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/"
|
||||||
@@ -149,16 +149,16 @@ local function get_remote_manifest()
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- record the local installation manifest
|
-- record the local installation manifest
|
||||||
local function write_install_manifest(manifest, dependencies)
|
local function write_install_manifest(manifest, deps)
|
||||||
local versions = {}
|
local versions = {}
|
||||||
for key, value in pairs(manifest.versions) do
|
for key, value in pairs(manifest.versions) do
|
||||||
local is_dependency = false
|
local is_dep = false
|
||||||
for _, dependency in pairs(dependencies) do
|
for _, dep in pairs(deps) do
|
||||||
if (key == "bootloader" and dependency == "system") or key == dependency then
|
if (key == "bootloader" and dep == "system") or key == dep then
|
||||||
is_dependency = true;break
|
is_dep = 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_dep then versions[key] = value end
|
||||||
end
|
end
|
||||||
|
|
||||||
manifest.versions = versions
|
manifest.versions = versions
|
||||||
@@ -321,23 +321,38 @@ if #opts == 0 or opts[1] == "help" then
|
|||||||
end
|
end
|
||||||
return
|
return
|
||||||
else
|
else
|
||||||
|
|
||||||
mode = get_opt(opts[1], { "check", "install", "update", "uninstall" })
|
mode = get_opt(opts[1], { "check", "install", "update", "uninstall" })
|
||||||
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", "installer" })
|
local next_opt = 3
|
||||||
|
local apps = { "reactor-plc", "rtu", "supervisor", "coordinator", "pocket", "installer" }
|
||||||
|
app = get_opt(opts[2], apps)
|
||||||
|
if app == nil then
|
||||||
|
for _, a in pairs(apps) do
|
||||||
|
if fs.exists(a) and fs.isDir(a) then
|
||||||
|
app = a
|
||||||
|
next_opt = 2
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
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
|
||||||
|
elseif mode == "check" then
|
||||||
|
next_opt = 2
|
||||||
elseif app == "installer" and mode ~= "update" then
|
elseif app == "installer" and mode ~= "update" then
|
||||||
red();println("Installer app only supports 'update' option.");white()
|
red();println("Installer app only supports 'update' option.");white()
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
-- determine target
|
-- determine target
|
||||||
if mode == "check" then target = opts[2] else target = opts[3] end
|
target = opts[next_opt]
|
||||||
if (target ~= "main") and (target ~= "devel") then
|
if (target ~= "main") 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"
|
||||||
@@ -383,8 +398,10 @@ if mode == "check" then
|
|||||||
yellow();println("\nA different version of the installer is available, it is recommended to update (use 'ccmsi update installer').");white()
|
yellow();println("\nA different version of the installer is available, it is recommended to update (use 'ccmsi update installer').");white()
|
||||||
end
|
end
|
||||||
elseif mode == "install" or mode == "update" then
|
elseif mode == "install" or mode == "update" then
|
||||||
|
local ok, r_manifest, l_manifest
|
||||||
|
|
||||||
local update_installer = app == "installer"
|
local update_installer = app == "installer"
|
||||||
local ok, manifest = get_remote_manifest()
|
ok, r_manifest = get_remote_manifest()
|
||||||
if not ok then return end
|
if not ok then return end
|
||||||
|
|
||||||
local ver = {
|
local ver = {
|
||||||
@@ -397,27 +414,27 @@ elseif mode == "install" or mode == "update" then
|
|||||||
}
|
}
|
||||||
|
|
||||||
-- try to find local versions
|
-- try to find local versions
|
||||||
local local_ok, lmnf = read_local_manifest()
|
ok, l_manifest = read_local_manifest()
|
||||||
if not local_ok then
|
if mode == "update" and not update_installer then
|
||||||
if mode == "update" then
|
if not ok 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
|
else
|
||||||
elseif not update_installer then
|
ver.boot.v_local = l_manifest.versions.bootloader
|
||||||
ver.boot.v_local = lmnf.versions.bootloader
|
ver.app.v_local = l_manifest.versions[app]
|
||||||
ver.app.v_local = lmnf.versions[app]
|
ver.comms.v_local = l_manifest.versions.comms
|
||||||
ver.comms.v_local = lmnf.versions.comms
|
ver.common.v_local = l_manifest.versions.common
|
||||||
ver.common.v_local = lmnf.versions.common
|
ver.graphics.v_local = l_manifest.versions.graphics
|
||||||
ver.graphics.v_local = lmnf.versions.graphics
|
ver.lockbox.v_local = l_manifest.versions.lockbox
|
||||||
ver.lockbox.v_local = lmnf.versions.lockbox
|
|
||||||
|
|
||||||
if lmnf.versions[app] == nil then
|
if l_manifest.versions[app] == nil then
|
||||||
red();println("Another application is already installed, please uninstall it before installing a new application.");white()
|
red();println("Another application is already installed, please uninstall it before installing a new application.");white()
|
||||||
return
|
return
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if manifest.versions.installer ~= CCMSI_VERSION then
|
if r_manifest.versions.installer ~= CCMSI_VERSION then
|
||||||
if not update_installer then yellow();println("A different version of the installer is available, it is recommended to update to it.");white() end
|
if not update_installer then yellow();println("A different version of the installer is available, it is recommended to update to it.");white() end
|
||||||
if update_installer or ask_y_n("Would you like to update now", true) then
|
if update_installer or ask_y_n("Would you like to update now", true) then
|
||||||
lgray();println("GET ccmsi.lua")
|
lgray();println("GET ccmsi.lua")
|
||||||
@@ -440,12 +457,12 @@ elseif mode == "install" or mode == "update" then
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
ver.boot.v_remote = manifest.versions.bootloader
|
ver.boot.v_remote = r_manifest.versions.bootloader
|
||||||
ver.app.v_remote = manifest.versions[app]
|
ver.app.v_remote = r_manifest.versions[app]
|
||||||
ver.comms.v_remote = manifest.versions.comms
|
ver.comms.v_remote = r_manifest.versions.comms
|
||||||
ver.common.v_remote = manifest.versions.common
|
ver.common.v_remote = r_manifest.versions.common
|
||||||
ver.graphics.v_remote = manifest.versions.graphics
|
ver.graphics.v_remote = r_manifest.versions.graphics
|
||||||
ver.lockbox.v_remote = manifest.versions.lockbox
|
ver.lockbox.v_remote = r_manifest.versions.lockbox
|
||||||
|
|
||||||
green()
|
green()
|
||||||
if mode == "install" then print("Installing ") else print("Updating ") end
|
if mode == "install" then print("Installing ") else print("Updating ") end
|
||||||
@@ -461,36 +478,33 @@ elseif mode == "install" or mode == "update" then
|
|||||||
ver.graphics.changed = show_pkg_change("graphics", ver.graphics)
|
ver.graphics.changed = show_pkg_change("graphics", ver.graphics)
|
||||||
ver.lockbox.changed = show_pkg_change("lockbox", ver.lockbox)
|
ver.lockbox.changed = show_pkg_change("lockbox", ver.lockbox)
|
||||||
|
|
||||||
--------------------------
|
-- start install/update
|
||||||
-- START INSTALL/UPDATE --
|
|
||||||
--------------------------
|
|
||||||
|
|
||||||
local space_required = manifest.sizes.manifest
|
local space_req = r_manifest.sizes.manifest
|
||||||
local space_available = fs.getFreeSpace("/")
|
local space_avail = fs.getFreeSpace("/")
|
||||||
|
|
||||||
local single_file_mode = false
|
local file_list = r_manifest.files
|
||||||
local file_list = manifest.files
|
local size_list = r_manifest.sizes
|
||||||
local size_list = manifest.sizes
|
local deps = r_manifest.depends[app]
|
||||||
local dependencies = manifest.depends[app]
|
|
||||||
|
|
||||||
table.insert(dependencies, app)
|
table.insert(deps, app)
|
||||||
|
|
||||||
-- helper function to check if a dependency is unchanged
|
-- helper function to check if a dependency is unchanged
|
||||||
local function unchanged(dependency)
|
local function unchanged(dep)
|
||||||
if dependency == "system" then return not ver.boot.changed
|
if dep == "system" then return not ver.boot.changed
|
||||||
elseif dependency == "graphics" then return not ver.graphics.changed
|
elseif dep == "graphics" then return not ver.graphics.changed
|
||||||
elseif dependency == "lockbox" then return not ver.lockbox.changed
|
elseif dep == "lockbox" then return not ver.lockbox.changed
|
||||||
elseif dependency == "common" then return not (ver.common.changed or ver.comms.changed)
|
elseif dep == "common" then return not (ver.common.changed or ver.comms.changed)
|
||||||
elseif dependency == app then return not ver.app.changed
|
elseif dep == app then return not ver.app.changed
|
||||||
else return true end
|
else return true end
|
||||||
end
|
end
|
||||||
|
|
||||||
local any_change = false
|
local any_change = false
|
||||||
|
|
||||||
for _, dependency in pairs(dependencies) do
|
for _, dep in pairs(deps) do
|
||||||
local size = size_list[dependency]
|
local size = size_list[dep]
|
||||||
space_required = space_required + size
|
space_req = space_req + size
|
||||||
any_change = any_change or not unchanged(dependency)
|
any_change = any_change or not unchanged(dep)
|
||||||
end
|
end
|
||||||
|
|
||||||
if mode == "update" and not any_change then
|
if mode == "update" and not any_change then
|
||||||
@@ -501,10 +515,7 @@ elseif mode == "install" or mode == "update" then
|
|||||||
-- 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
|
||||||
|
|
||||||
-- check space constraints
|
local single_file_mode = space_avail < space_req
|
||||||
if space_available < space_required then
|
|
||||||
single_file_mode = true
|
|
||||||
end
|
|
||||||
|
|
||||||
local success = true
|
local success = true
|
||||||
|
|
||||||
@@ -548,7 +559,7 @@ elseif mode == "install" or mode == "update" then
|
|||||||
success = false
|
success = false
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
clean(manifest)
|
clean(r_manifest)
|
||||||
sf_install(3)
|
sf_install(3)
|
||||||
elseif attempt == 3 then
|
elseif attempt == 3 then
|
||||||
yellow()
|
yellow()
|
||||||
@@ -574,30 +585,30 @@ elseif mode == "install" or mode == "update" then
|
|||||||
local abort_attempt = false
|
local abort_attempt = false
|
||||||
success = true
|
success = true
|
||||||
|
|
||||||
for _, dependency in pairs(dependencies) do
|
for _, dep in pairs(deps) do
|
||||||
if mode == "update" and unchanged(dependency) then
|
if mode == "update" and unchanged(dep) then
|
||||||
pkg_message("skipping install of unchanged package", dependency)
|
pkg_message("skipping install of unchanged package", dep)
|
||||||
else
|
else
|
||||||
pkg_message("installing package", dependency)
|
pkg_message("installing package", dep)
|
||||||
lgray()
|
lgray()
|
||||||
|
|
||||||
-- beginning on the second try, delete the directory before starting
|
-- beginning on the second try, delete the directory before starting
|
||||||
if attempt >= 2 then
|
if attempt >= 2 then
|
||||||
if dependency == "system" then
|
if dep == "system" then
|
||||||
elseif dependency == "common" then
|
elseif dep == "common" then
|
||||||
if fs.exists("/scada-common") then
|
if fs.exists("/scada-common") then
|
||||||
fs.delete("/scada-common")
|
fs.delete("/scada-common")
|
||||||
println("deleted /scada-common")
|
println("deleted /scada-common")
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
if fs.exists("/"..dependency) then
|
if fs.exists("/"..dep) then
|
||||||
fs.delete("/"..dependency)
|
fs.delete("/"..dep)
|
||||||
println("deleted /"..dependency)
|
println("deleted /"..dep)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local files = file_list[dependency]
|
local files = file_list[dep]
|
||||||
for _, file in pairs(files) do
|
for _, file in pairs(files) do
|
||||||
println("GET "..file)
|
println("GET "..file)
|
||||||
mitigate_case(file)
|
mitigate_case(file)
|
||||||
@@ -620,14 +631,14 @@ elseif mode == "install" or mode == "update" then
|
|||||||
if fs.exists(install_dir) then fs.delete(install_dir);fs.makeDir(install_dir) end
|
if fs.exists(install_dir) then fs.delete(install_dir);fs.makeDir(install_dir) end
|
||||||
|
|
||||||
-- download all dependencies
|
-- download all dependencies
|
||||||
for _, dependency in pairs(dependencies) do
|
for _, dep in pairs(deps) do
|
||||||
if mode == "update" and unchanged(dependency) then
|
if mode == "update" and unchanged(dep) then
|
||||||
pkg_message("skipping download of unchanged package", dependency)
|
pkg_message("skipping download of unchanged package", dep)
|
||||||
else
|
else
|
||||||
pkg_message("downloading package", dependency)
|
pkg_message("downloading package", dep)
|
||||||
lgray()
|
lgray()
|
||||||
|
|
||||||
local files = file_list[dependency]
|
local files = file_list[dep]
|
||||||
for _, file in pairs(files) do
|
for _, file in pairs(files) do
|
||||||
println("GET "..file)
|
println("GET "..file)
|
||||||
local dl_stat = http_get_file(file, install_dir.."/")
|
local dl_stat = http_get_file(file, install_dir.."/")
|
||||||
@@ -650,14 +661,14 @@ elseif mode == "install" or mode == "update" then
|
|||||||
|
|
||||||
-- copy in downloaded files (installation)
|
-- copy in downloaded files (installation)
|
||||||
if success then
|
if success then
|
||||||
for _, dependency in pairs(dependencies) do
|
for _, dep in pairs(deps) do
|
||||||
if mode == "update" and unchanged(dependency) then
|
if mode == "update" and unchanged(dep) then
|
||||||
pkg_message("skipping install of unchanged package", dependency)
|
pkg_message("skipping install of unchanged package", dep)
|
||||||
else
|
else
|
||||||
pkg_message("installing package", dependency)
|
pkg_message("installing package", dep)
|
||||||
lgray()
|
lgray()
|
||||||
|
|
||||||
local files = file_list[dependency]
|
local files = file_list[dep]
|
||||||
for _, file in pairs(files) do
|
for _, file in pairs(files) do
|
||||||
local temp_file = install_dir.."/"..file
|
local temp_file = install_dir.."/"..file
|
||||||
if fs.exists(file) then fs.delete(file) end
|
if fs.exists(file) then fs.delete(file) end
|
||||||
@@ -671,13 +682,13 @@ elseif mode == "install" or mode == "update" then
|
|||||||
end
|
end
|
||||||
|
|
||||||
if success then
|
if success then
|
||||||
write_install_manifest(manifest, dependencies)
|
write_install_manifest(r_manifest, deps)
|
||||||
green()
|
green()
|
||||||
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...")
|
white();println("Ready to clean up unused files, press any key to continue...")
|
||||||
any_key();clean(manifest)
|
any_key();clean(r_manifest)
|
||||||
white();println("Done.")
|
white();println("Done.")
|
||||||
else
|
else
|
||||||
red()
|
red()
|
||||||
@@ -712,14 +723,14 @@ elseif mode == "uninstall" then
|
|||||||
clean(manifest)
|
clean(manifest)
|
||||||
|
|
||||||
local file_list = manifest.files
|
local file_list = manifest.files
|
||||||
local dependencies = manifest.depends[app]
|
local deps = manifest.depends[app]
|
||||||
|
|
||||||
table.insert(dependencies, app)
|
table.insert(deps, app)
|
||||||
|
|
||||||
-- delete all installed files
|
-- delete all installed files
|
||||||
lgray()
|
lgray()
|
||||||
for _, dependency in pairs(dependencies) do
|
for _, dep in pairs(deps) do
|
||||||
local files = file_list[dependency]
|
local files = file_list[dep]
|
||||||
for _, file in pairs(files) do
|
for _, file in pairs(files) do
|
||||||
if fs.exists(file) then fs.delete(file);println("deleted "..file) end
|
if fs.exists(file) then fs.delete(file);println("deleted "..file) end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
print("CONFIGURE> SCANNING FOR CONFIGURATOR...")
|
print("CONFIGURE> SCANNING FOR CONFIGURATOR...")
|
||||||
|
|
||||||
if fs.exists("reactor-plc/configure.lua") then require("reactor-plc.configure").configure()
|
for _, app in ipairs({ "reactor-plc", "rtu", "supervisor", "coordinator", "pocket" }) do
|
||||||
elseif fs.exists("rtu/configure.lua") then require("rtu.configure").configure()
|
if fs.exists(app .. "/configure.lua") then
|
||||||
elseif fs.exists("supervisor/configure.lua") then require("supervisor.configure").configure()
|
local _, _, launch = require(app .. ".configure").configure()
|
||||||
elseif fs.exists("coordinator/configure.lua") then require("coordinator.configure").configure()
|
if launch then shell.execute("/startup") end
|
||||||
elseif fs.exists("pocket/configure.lua") then require("pocket.configure").configure()
|
return
|
||||||
else
|
end
|
||||||
print("CONFIGURE> NO CONFIGURATOR FOUND")
|
|
||||||
print("CONFIGURE> EXIT")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
print("CONFIGURE> NO CONFIGURATOR FOUND")
|
||||||
|
print("CONFIGURE> EXIT")
|
||||||
|
|||||||
@@ -231,7 +231,7 @@ function facility.create(tool_ctl, main_pane, cfg_sys, fac_cfg, style)
|
|||||||
TextBox{parent=fac_c_2,x=1,y=1,height=3,text="Please enter the number of reactors you have, also referred to as reactor units or 'units' for short. A maximum of 4 is currently supported."}
|
TextBox{parent=fac_c_2,x=1,y=1,height=3,text="Please enter the number of reactors you have, also referred to as reactor units or 'units' for short. A maximum of 4 is currently supported."}
|
||||||
tool_ctl.num_units = NumberField{parent=fac_c_2,x=1,y=5,width=5,max_chars=2,default=ini_cfg.UnitCount,min=1,max=4,fg_bg=bw_fg_bg}
|
tool_ctl.num_units = NumberField{parent=fac_c_2,x=1,y=5,width=5,max_chars=2,default=ini_cfg.UnitCount,min=1,max=4,fg_bg=bw_fg_bg}
|
||||||
TextBox{parent=fac_c_2,x=7,y=5,text="reactors"}
|
TextBox{parent=fac_c_2,x=7,y=5,text="reactors"}
|
||||||
TextBox{parent=fac_c_2,x=1,y=7,height=3,text="This will decide how many monitors you need. If this does not match the supervisor's number of reactor units, the coordinator will not connect.",fg_bg=g_lg_fg_bg}
|
TextBox{parent=fac_c_2,x=1,y=7,height=3,text="This will decide how many monitors you need. If this does not match the supervisor's number of reactor units, the coordinator will not connect.",fg_bg=cpair(colors.yellow,colors._INHERIT)}
|
||||||
TextBox{parent=fac_c_2,x=1,y=10,height=3,text="Since you skipped supervisor sync, the main monitor minimum height can't be determined precisely. It is marked with * on the next page.",fg_bg=g_lg_fg_bg}
|
TextBox{parent=fac_c_2,x=1,y=10,height=3,text="Since you skipped supervisor sync, the main monitor minimum height can't be determined precisely. It is marked with * on the next page.",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
local nu_error = TextBox{parent=fac_c_2,x=8,y=14,width=35,text="Please set the number of reactors.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
local nu_error = TextBox{parent=fac_c_2,x=8,y=14,width=35,text="Please set the number of reactors.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||||
|
|||||||
@@ -234,19 +234,24 @@ function hmi.create(tool_ctl, main_pane, cfg_sys, divs, style)
|
|||||||
|
|
||||||
TextBox{parent=crd_cfg,x=1,y=2,text=" Coordinator UI Configuration",fg_bg=cpair(colors.black,colors.lime)}
|
TextBox{parent=crd_cfg,x=1,y=2,text=" Coordinator UI Configuration",fg_bg=cpair(colors.black,colors.lime)}
|
||||||
|
|
||||||
TextBox{parent=crd_c_1,x=1,y=1,height=3,text="Configure the UI interface options below if you wish to customize formats."}
|
TextBox{parent=crd_c_1,x=1,y=1,height=2,text="You can customize the UI with the interface options below."}
|
||||||
|
|
||||||
TextBox{parent=crd_c_1,x=1,y=4,text="Clock Time Format"}
|
TextBox{parent=crd_c_1,x=1,y=4,text="Clock Time Format"}
|
||||||
tool_ctl.clock_fmt = RadioButton{parent=crd_c_1,x=1,y=5,default=util.trinary(ini_cfg.Time24Hour,1,2),options={"24-Hour","12-Hour"},callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime}
|
tool_ctl.clock_fmt = RadioButton{parent=crd_c_1,x=1,y=5,default=util.trinary(ini_cfg.Time24Hour,1,2),options={"24-Hour","12-Hour"},callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime}
|
||||||
|
|
||||||
|
TextBox{parent=crd_c_1,x=20,y=4,text="Po/Pu Pellet Color"}
|
||||||
|
TextBox{parent=crd_c_1,x=39,y=4,text="new!",fg_bg=cpair(colors.red,colors._INHERIT)} ---@todo remove NEW tag on next revision
|
||||||
|
tool_ctl.pellet_color = RadioButton{parent=crd_c_1,x=20,y=5,default=util.trinary(ini_cfg.GreenPuPellet,1,2),options={"Green Pu/Cyan Po","Cyan Pu/Green Po (Mek 10.4+)"},callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime}
|
||||||
|
|
||||||
TextBox{parent=crd_c_1,x=1,y=8,text="Temperature Scale"}
|
TextBox{parent=crd_c_1,x=1,y=8,text="Temperature Scale"}
|
||||||
tool_ctl.temp_scale = RadioButton{parent=crd_c_1,x=1,y=9,default=ini_cfg.TempScale,options=types.TEMP_SCALE_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime}
|
tool_ctl.temp_scale = RadioButton{parent=crd_c_1,x=1,y=9,default=ini_cfg.TempScale,options=types.TEMP_SCALE_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime}
|
||||||
|
|
||||||
TextBox{parent=crd_c_1,x=24,y=8,text="Energy Scale"}
|
TextBox{parent=crd_c_1,x=20,y=8,text="Energy Scale"}
|
||||||
tool_ctl.energy_scale = RadioButton{parent=crd_c_1,x=24,y=9,default=ini_cfg.EnergyScale,options=types.ENERGY_SCALE_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime}
|
tool_ctl.energy_scale = RadioButton{parent=crd_c_1,x=20,y=9,default=ini_cfg.EnergyScale,options=types.ENERGY_SCALE_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime}
|
||||||
|
|
||||||
local function submit_ui_opts()
|
local function submit_ui_opts()
|
||||||
tmp_cfg.Time24Hour = tool_ctl.clock_fmt.get_value() == 1
|
tmp_cfg.Time24Hour = tool_ctl.clock_fmt.get_value() == 1
|
||||||
|
tmp_cfg.GreenPuPellet = tool_ctl.pellet_color.get_value() == 1
|
||||||
tmp_cfg.TempScale = tool_ctl.temp_scale.get_value()
|
tmp_cfg.TempScale = tool_ctl.temp_scale.get_value()
|
||||||
tmp_cfg.EnergyScale = tool_ctl.energy_scale.get_value()
|
tmp_cfg.EnergyScale = tool_ctl.energy_scale.get_value()
|
||||||
main_pane.set_value(7)
|
main_pane.set_value(7)
|
||||||
|
|||||||
@@ -380,6 +380,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style)
|
|||||||
try_set(tool_ctl.num_units, ini_cfg.UnitCount)
|
try_set(tool_ctl.num_units, ini_cfg.UnitCount)
|
||||||
try_set(tool_ctl.dis_flow_view, ini_cfg.DisableFlowView)
|
try_set(tool_ctl.dis_flow_view, ini_cfg.DisableFlowView)
|
||||||
try_set(tool_ctl.s_vol, ini_cfg.SpeakerVolume)
|
try_set(tool_ctl.s_vol, ini_cfg.SpeakerVolume)
|
||||||
|
try_set(tool_ctl.pellet_color, ini_cfg.GreenPuPellet)
|
||||||
try_set(tool_ctl.clock_fmt, tri(ini_cfg.Time24Hour, 1, 2))
|
try_set(tool_ctl.clock_fmt, tri(ini_cfg.Time24Hour, 1, 2))
|
||||||
try_set(tool_ctl.temp_scale, ini_cfg.TempScale)
|
try_set(tool_ctl.temp_scale, ini_cfg.TempScale)
|
||||||
try_set(tool_ctl.energy_scale, ini_cfg.EnergyScale)
|
try_set(tool_ctl.energy_scale, ini_cfg.EnergyScale)
|
||||||
@@ -528,6 +529,8 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style)
|
|||||||
|
|
||||||
if f[1] == "AuthKey" then val = string.rep("*", string.len(val))
|
if f[1] == "AuthKey" then val = string.rep("*", string.len(val))
|
||||||
elseif f[1] == "LogMode" then val = util.trinary(raw == log.MODE.APPEND, "append", "replace")
|
elseif f[1] == "LogMode" then val = util.trinary(raw == log.MODE.APPEND, "append", "replace")
|
||||||
|
elseif f[1] == "GreenPuPellet" then
|
||||||
|
val = tri(raw, "Green Pu/Cyan Po", "Cyan Pu/Green Po")
|
||||||
elseif f[1] == "TempScale" then
|
elseif f[1] == "TempScale" then
|
||||||
val = util.strval(types.TEMP_SCALE_NAMES[raw])
|
val = util.strval(types.TEMP_SCALE_NAMES[raw])
|
||||||
elseif f[1] == "EnergyScale" then
|
elseif f[1] == "EnergyScale" then
|
||||||
@@ -550,7 +553,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style)
|
|||||||
local c = util.trinary(alternate, g_lg_fg_bg, cpair(colors.gray,colors.white))
|
local c = util.trinary(alternate, g_lg_fg_bg, cpair(colors.gray,colors.white))
|
||||||
alternate = not alternate
|
alternate = not alternate
|
||||||
|
|
||||||
if string.len(val) > val_max_w then
|
if (string.len(val) > val_max_w) or string.find(val, "\n") then
|
||||||
local lines = util.strwrap(val, inner_width)
|
local lines = util.strwrap(val, inner_width)
|
||||||
height = #lines + 1
|
height = #lines + 1
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -35,7 +35,8 @@ local changes = {
|
|||||||
{ "v1.2.4", { "Added temperature scale options" } },
|
{ "v1.2.4", { "Added temperature scale options" } },
|
||||||
{ "v1.2.12", { "Added main UI theme", "Added front panel UI theme", "Added color accessibility modes" } },
|
{ "v1.2.12", { "Added main UI theme", "Added front panel UI theme", "Added color accessibility modes" } },
|
||||||
{ "v1.3.3", { "Added standard with black off state color mode", "Added blue indicator color modes" } },
|
{ "v1.3.3", { "Added standard with black off state color mode", "Added blue indicator color modes" } },
|
||||||
{ "v1.5.1", { "Added energy scale options" } }
|
{ "v1.5.1", { "Added energy scale options" } },
|
||||||
|
{ "v1.6.13", { "Added option for Po/Pu pellet green/cyan pairing" } }
|
||||||
}
|
}
|
||||||
|
|
||||||
---@class crd_configurator
|
---@class crd_configurator
|
||||||
@@ -58,6 +59,7 @@ style.btn_dis_fg_bg = cpair(colors.lightGray,colors.white)
|
|||||||
local tool_ctl = {
|
local tool_ctl = {
|
||||||
sv_cool_conf = nil, ---@type [ integer, integer ][] list of boiler & turbine counts
|
sv_cool_conf = nil, ---@type [ integer, integer ][] list of boiler & turbine counts
|
||||||
|
|
||||||
|
launch_startup = false,
|
||||||
start_fail = 0,
|
start_fail = 0,
|
||||||
fail_message = "",
|
fail_message = "",
|
||||||
has_config = false,
|
has_config = false,
|
||||||
@@ -76,6 +78,7 @@ local tool_ctl = {
|
|||||||
-- settings elements from hmi
|
-- settings elements from hmi
|
||||||
dis_flow_view = nil, ---@type Checkbox
|
dis_flow_view = nil, ---@type Checkbox
|
||||||
s_vol = nil, ---@type NumberField
|
s_vol = nil, ---@type NumberField
|
||||||
|
pellet_color = nil, ---@type RadioButton
|
||||||
clock_fmt = nil, ---@type RadioButton
|
clock_fmt = nil, ---@type RadioButton
|
||||||
temp_scale = nil, ---@type RadioButton
|
temp_scale = nil, ---@type RadioButton
|
||||||
energy_scale = nil, ---@type RadioButton
|
energy_scale = nil, ---@type RadioButton
|
||||||
@@ -94,6 +97,7 @@ local tmp_cfg = {
|
|||||||
UnitCount = 1,
|
UnitCount = 1,
|
||||||
SpeakerVolume = 1.0,
|
SpeakerVolume = 1.0,
|
||||||
Time24Hour = true,
|
Time24Hour = true,
|
||||||
|
GreenPuPellet = false,
|
||||||
TempScale = 1, ---@type TEMP_SCALE
|
TempScale = 1, ---@type TEMP_SCALE
|
||||||
EnergyScale = 1, ---@type ENERGY_SCALE
|
EnergyScale = 1, ---@type ENERGY_SCALE
|
||||||
DisableFlowView = false,
|
DisableFlowView = false,
|
||||||
@@ -128,6 +132,7 @@ local fields = {
|
|||||||
{ "UnitDisplays", "Unit Monitors", {} },
|
{ "UnitDisplays", "Unit Monitors", {} },
|
||||||
{ "SpeakerVolume", "Speaker Volume", 1.0 },
|
{ "SpeakerVolume", "Speaker Volume", 1.0 },
|
||||||
{ "Time24Hour", "Use 24-hour Time Format", true },
|
{ "Time24Hour", "Use 24-hour Time Format", true },
|
||||||
|
{ "GreenPuPellet", "Pellet Colors", false },
|
||||||
{ "TempScale", "Temperature Scale", types.TEMP_SCALE.KELVIN },
|
{ "TempScale", "Temperature Scale", types.TEMP_SCALE.KELVIN },
|
||||||
{ "EnergyScale", "Energy Scale", types.ENERGY_SCALE.FE },
|
{ "EnergyScale", "Energy Scale", types.ENERGY_SCALE.FE },
|
||||||
{ "DisableFlowView", "Disable Flow Monitor (legacy, discouraged)", false },
|
{ "DisableFlowView", "Disable Flow Monitor (legacy, discouraged)", false },
|
||||||
@@ -210,7 +215,7 @@ local function config_view(display)
|
|||||||
TextBox{parent=main_page,x=2,y=y_start,height=4,width=49,text=msg,fg_bg=cpair(colors.red,colors.lightGray)}
|
TextBox{parent=main_page,x=2,y=y_start,height=4,width=49,text=msg,fg_bg=cpair(colors.red,colors.lightGray)}
|
||||||
y_start = y_start + 5
|
y_start = y_start + 5
|
||||||
elseif tool_ctl.start_fail > 0 then
|
elseif tool_ctl.start_fail > 0 then
|
||||||
TextBox{parent=main_page,x=2,y=y_start,height=4,width=49,text="Notice: This device had no valid config so the configurator has been automatically started. If you previously had a valid config, you may want to check the Change Log to see what changed.",fg_bg=cpair(colors.red,colors.lightGray)}
|
TextBox{parent=main_page,x=2,y=y_start,height=4,width=49,text="Notice: This device is not configured for this version of the coordinator. If you previously had a valid config, it's not lost. You may want to check the Change Log to see what changed.",fg_bg=cpair(colors.red,colors.lightGray)}
|
||||||
y_start = y_start + 5
|
y_start = y_start + 5
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -236,9 +241,17 @@ local function config_view(display)
|
|||||||
main_pane.set_value(8)
|
main_pane.set_value(8)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function startup()
|
||||||
|
tool_ctl.launch_startup = true
|
||||||
|
exit()
|
||||||
|
end
|
||||||
|
|
||||||
PushButton{parent=main_page,x=2,y=17,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=main_page,x=2,y=17,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=btn_act_fg_bg}
|
||||||
tool_ctl.color_cfg = PushButton{parent=main_page,x=23,y=17,min_width=15,text="Color Options",callback=jump_color,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)}
|
local start_btn = PushButton{parent=main_page,x=42,y=17,min_width=9,text="Startup",callback=startup,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
||||||
PushButton{parent=main_page,x=39,y=17,min_width=12,text="Change Log",callback=function()main_pane.set_value(10)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
tool_ctl.color_cfg = PushButton{parent=main_page,x=36,y=y_start,min_width=15,text="Color Options",callback=jump_color,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)}
|
||||||
|
PushButton{parent=main_page,x=39,y=y_start+2,min_width=12,text="Change Log",callback=function()main_pane.set_value(10)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
|
if tool_ctl.start_fail ~= 0 then start_btn.disable() end
|
||||||
|
|
||||||
if not tool_ctl.has_config then
|
if not tool_ctl.has_config then
|
||||||
tool_ctl.view_cfg.disable()
|
tool_ctl.view_cfg.disable()
|
||||||
@@ -372,7 +385,7 @@ function configurator.configure(start_code, message)
|
|||||||
println("configurator error: " .. error)
|
println("configurator error: " .. error)
|
||||||
end
|
end
|
||||||
|
|
||||||
return status, error
|
return status, error, tool_ctl.launch_startup
|
||||||
end
|
end
|
||||||
|
|
||||||
return configurator
|
return configurator
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ local LINK_TIMEOUT = 60.0
|
|||||||
local coordinator = {}
|
local coordinator = {}
|
||||||
|
|
||||||
---@type crd_config
|
---@type crd_config
|
||||||
|
---@diagnostic disable-next-line: missing-fields
|
||||||
local config = {}
|
local config = {}
|
||||||
|
|
||||||
coordinator.config = config
|
coordinator.config = config
|
||||||
@@ -37,6 +38,7 @@ function coordinator.load_config()
|
|||||||
config.UnitCount = settings.get("UnitCount")
|
config.UnitCount = settings.get("UnitCount")
|
||||||
config.SpeakerVolume = settings.get("SpeakerVolume")
|
config.SpeakerVolume = settings.get("SpeakerVolume")
|
||||||
config.Time24Hour = settings.get("Time24Hour")
|
config.Time24Hour = settings.get("Time24Hour")
|
||||||
|
config.GreenPuPellet = settings.get("GreenPuPellet")
|
||||||
config.TempScale = settings.get("TempScale")
|
config.TempScale = settings.get("TempScale")
|
||||||
config.EnergyScale = settings.get("EnergyScale")
|
config.EnergyScale = settings.get("EnergyScale")
|
||||||
|
|
||||||
@@ -66,6 +68,7 @@ function coordinator.load_config()
|
|||||||
cfv.assert_type_int(config.UnitCount)
|
cfv.assert_type_int(config.UnitCount)
|
||||||
cfv.assert_range(config.UnitCount, 1, 4)
|
cfv.assert_range(config.UnitCount, 1, 4)
|
||||||
cfv.assert_type_bool(config.Time24Hour)
|
cfv.assert_type_bool(config.Time24Hour)
|
||||||
|
cfv.assert_type_bool(config.GreenPuPellet)
|
||||||
cfv.assert_type_int(config.TempScale)
|
cfv.assert_type_int(config.TempScale)
|
||||||
cfv.assert_range(config.TempScale, 1, 4)
|
cfv.assert_range(config.TempScale, 1, 4)
|
||||||
cfv.assert_type_int(config.EnergyScale)
|
cfv.assert_type_int(config.EnergyScale)
|
||||||
@@ -379,6 +382,18 @@ function coordinator.comms(version, nic, sv_watchdog)
|
|||||||
_send_sv(PROTOCOL.SCADA_MGMT, MGMT_TYPE.CLOSE, {})
|
_send_sv(PROTOCOL.SCADA_MGMT, MGMT_TYPE.CLOSE, {})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- send the resume ready state to the supervisor
|
||||||
|
---@param mode PROCESS process control mode
|
||||||
|
---@param burn_target number burn rate target
|
||||||
|
---@param charge_target number charge level target
|
||||||
|
---@param gen_target number generation rate target
|
||||||
|
---@param limits number[] unit burn rate limits
|
||||||
|
function public.send_ready(mode, burn_target, charge_target, gen_target, limits)
|
||||||
|
_send_sv(PROTOCOL.SCADA_CRDN, CRDN_TYPE.PROCESS_READY, {
|
||||||
|
mode, burn_target, charge_target, gen_target, limits
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
-- send a facility command
|
-- send a facility command
|
||||||
---@param cmd FAC_COMMAND command
|
---@param cmd FAC_COMMAND command
|
||||||
---@param option any? optional option options for the optional options (like waste mode)
|
---@param option any? optional option options for the optional options (like waste mode)
|
||||||
|
|||||||
@@ -20,6 +20,13 @@ local ENERGY_UNITS = types.ENERGY_SCALE_UNITS
|
|||||||
local TEMP_SCALE = types.TEMP_SCALE
|
local TEMP_SCALE = types.TEMP_SCALE
|
||||||
local TEMP_UNITS = types.TEMP_SCALE_UNITS
|
local TEMP_UNITS = types.TEMP_SCALE_UNITS
|
||||||
|
|
||||||
|
local RCT_STATE = types.REACTOR_STATE
|
||||||
|
local BLR_STATE = types.BOILER_STATE
|
||||||
|
local TRB_STATE = types.TURBINE_STATE
|
||||||
|
local TNK_STATE = types.TANK_STATE
|
||||||
|
local MTX_STATE = types.IMATRIX_STATE
|
||||||
|
local SPS_STATE = types.SPS_STATE
|
||||||
|
|
||||||
-- nominal RTT is ping (0ms to 10ms usually) + 500ms for CRD main loop tick
|
-- 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 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 HIGH_RTT = 1500 -- 3.33x as long as expected w/ 0 ping
|
||||||
@@ -81,6 +88,8 @@ function iocontrol.init(conf, comms, temp_scale, energy_scale)
|
|||||||
tank_mode = conf.cooling.fac_tank_mode,
|
tank_mode = conf.cooling.fac_tank_mode,
|
||||||
tank_defs = conf.cooling.fac_tank_defs,
|
tank_defs = conf.cooling.fac_tank_defs,
|
||||||
tank_list = conf.cooling.fac_tank_list,
|
tank_list = conf.cooling.fac_tank_list,
|
||||||
|
tank_conns = conf.cooling.fac_tank_conns,
|
||||||
|
tank_fluid_types = conf.cooling.tank_fluid_types,
|
||||||
all_sys_ok = false,
|
all_sys_ok = false,
|
||||||
rtu_count = 0,
|
rtu_count = 0,
|
||||||
|
|
||||||
@@ -94,7 +103,7 @@ function iocontrol.init(conf, comms, temp_scale, energy_scale)
|
|||||||
auto_scram = false,
|
auto_scram = false,
|
||||||
---@type ascram_status
|
---@type ascram_status
|
||||||
ascram_status = {
|
ascram_status = {
|
||||||
matrix_dc = false,
|
matrix_fault = false,
|
||||||
matrix_fill = false,
|
matrix_fill = false,
|
||||||
crit_alarm = false,
|
crit_alarm = false,
|
||||||
radiation = false,
|
radiation = false,
|
||||||
@@ -105,10 +114,11 @@ function iocontrol.init(conf, comms, temp_scale, energy_scale)
|
|||||||
auto_current_waste_product = types.WASTE_PRODUCT.PLUTONIUM,
|
auto_current_waste_product = types.WASTE_PRODUCT.PLUTONIUM,
|
||||||
auto_pu_fallback_active = false,
|
auto_pu_fallback_active = false,
|
||||||
auto_sps_disabled = false,
|
auto_sps_disabled = false,
|
||||||
|
waste_stats = { 0, 0, 0, 0, 0, 0 }, -- waste in, pu, po, po pellets, am, spent waste
|
||||||
|
|
||||||
radiation = types.new_zero_radiation_reading(),
|
radiation = types.new_zero_radiation_reading(),
|
||||||
|
|
||||||
save_cfg_ack = nil, ---@type fun(success: boolean)
|
save_cfg_ack = nil, ---@type fun(success: boolean)
|
||||||
|
|
||||||
---@type { [TONE]: boolean }
|
---@type { [TONE]: boolean }
|
||||||
alarm_tones = { false, false, false, false, false, false, false, false },
|
alarm_tones = { false, false, false, false, false, false, false, false },
|
||||||
@@ -122,7 +132,9 @@ function iocontrol.init(conf, comms, temp_scale, energy_scale)
|
|||||||
sps_data_tbl = {}, ---@type sps_session_db[]
|
sps_data_tbl = {}, ---@type sps_session_db[]
|
||||||
|
|
||||||
tank_ps_tbl = {}, ---@type psil[]
|
tank_ps_tbl = {}, ---@type psil[]
|
||||||
tank_data_tbl = {} ---@type dynamicv_session_db[]
|
tank_data_tbl = {}, ---@type dynamicv_session_db[]
|
||||||
|
|
||||||
|
rad_monitors = {} ---@type { radiation: radiation_reading, raw: number }[]
|
||||||
}
|
}
|
||||||
|
|
||||||
-- create induction and SPS tables (currently only 1 of each is supported)
|
-- create induction and SPS tables (currently only 1 of each is supported)
|
||||||
@@ -149,15 +161,12 @@ function iocontrol.init(conf, comms, temp_scale, energy_scale)
|
|||||||
local entry = {
|
local entry = {
|
||||||
unit_id = i,
|
unit_id = i,
|
||||||
connected = false,
|
connected = false,
|
||||||
rtu_hw = {
|
|
||||||
boilers = {}, ---@type { connected: boolean, faulted: boolean }[]
|
|
||||||
turbines = {} ---@type { connected: boolean, faulted: boolean }[]
|
|
||||||
},
|
|
||||||
|
|
||||||
num_boilers = 0,
|
num_boilers = 0,
|
||||||
num_turbines = 0,
|
num_turbines = 0,
|
||||||
num_snas = 0,
|
num_snas = 0,
|
||||||
has_tank = conf.cooling.r_cool[i].TankConnection,
|
has_tank = conf.cooling.r_cool[i].TankConnection,
|
||||||
|
aux_coolant = conf.cooling.aux_coolant[i],
|
||||||
|
|
||||||
status_lines = { "", "" },
|
status_lines = { "", "" },
|
||||||
|
|
||||||
@@ -174,6 +183,7 @@ function iocontrol.init(conf, comms, temp_scale, energy_scale)
|
|||||||
|
|
||||||
waste_mode = types.WASTE_MODE.MANUAL_PLUTONIUM,
|
waste_mode = types.WASTE_MODE.MANUAL_PLUTONIUM,
|
||||||
waste_product = types.WASTE_PRODUCT.PLUTONIUM,
|
waste_product = types.WASTE_PRODUCT.PLUTONIUM,
|
||||||
|
waste_stats = { 0, 0, 0 }, -- plutonium, polonium, po pellets
|
||||||
|
|
||||||
last_rate_change_ms = 0,
|
last_rate_change_ms = 0,
|
||||||
turbine_flow_stable = false,
|
turbine_flow_stable = false,
|
||||||
@@ -221,6 +231,7 @@ function iocontrol.init(conf, comms, temp_scale, energy_scale)
|
|||||||
ALARM_STATE.INACTIVE -- turbine trip
|
ALARM_STATE.INACTIVE -- turbine trip
|
||||||
},
|
},
|
||||||
|
|
||||||
|
---@diagnostic disable-next-line: missing-fields
|
||||||
annunciator = {}, ---@type annunciator
|
annunciator = {}, ---@type annunciator
|
||||||
|
|
||||||
unit_ps = psil.create(),
|
unit_ps = psil.create(),
|
||||||
@@ -233,7 +244,9 @@ function iocontrol.init(conf, comms, temp_scale, energy_scale)
|
|||||||
turbine_data_tbl = {}, ---@type turbinev_session_db[]
|
turbine_data_tbl = {}, ---@type turbinev_session_db[]
|
||||||
|
|
||||||
tank_ps_tbl = {}, ---@type psil[]
|
tank_ps_tbl = {}, ---@type psil[]
|
||||||
tank_data_tbl = {} ---@type dynamicv_session_db[]
|
tank_data_tbl = {}, ---@type dynamicv_session_db[]
|
||||||
|
|
||||||
|
rad_monitors = {} ---@type { radiation: radiation_reading, raw: number }[]
|
||||||
}
|
}
|
||||||
|
|
||||||
-- on other facility modes, overwrite unit TANK option with facility tank defs
|
-- on other facility modes, overwrite unit TANK option with facility tank defs
|
||||||
@@ -245,14 +258,12 @@ function iocontrol.init(conf, comms, temp_scale, energy_scale)
|
|||||||
for _ = 1, conf.cooling.r_cool[i].BoilerCount do
|
for _ = 1, conf.cooling.r_cool[i].BoilerCount do
|
||||||
table.insert(entry.boiler_ps_tbl, psil.create())
|
table.insert(entry.boiler_ps_tbl, psil.create())
|
||||||
table.insert(entry.boiler_data_tbl, {})
|
table.insert(entry.boiler_data_tbl, {})
|
||||||
table.insert(entry.rtu_hw.boilers, { connected = false, faulted = false })
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- create turbine tables
|
-- create turbine tables
|
||||||
for _ = 1, conf.cooling.r_cool[i].TurbineCount do
|
for _ = 1, conf.cooling.r_cool[i].TurbineCount do
|
||||||
table.insert(entry.turbine_ps_tbl, psil.create())
|
table.insert(entry.turbine_ps_tbl, psil.create())
|
||||||
table.insert(entry.turbine_data_tbl, {})
|
table.insert(entry.turbine_data_tbl, {})
|
||||||
table.insert(entry.rtu_hw.turbines, { connected = false, faulted = false })
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- create tank tables
|
-- create tank tables
|
||||||
@@ -363,6 +374,7 @@ local function _record_multiblock_build(id, entry, data_tbl, ps_tbl, create)
|
|||||||
if exists or create then
|
if exists or create then
|
||||||
if not exists then
|
if not exists then
|
||||||
ps_tbl[id] = psil.create()
|
ps_tbl[id] = psil.create()
|
||||||
|
---@diagnostic disable-next-line: missing-fields
|
||||||
data_tbl[id] = {}
|
data_tbl[id] = {}
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -488,6 +500,49 @@ end
|
|||||||
|
|
||||||
--#region Statuses
|
--#region Statuses
|
||||||
|
|
||||||
|
-- generate the text string for the induction matrix charge/discharge ETA
|
||||||
|
---@param eta_ms number eta in milliseconds
|
||||||
|
local function gen_eta_text(eta_ms)
|
||||||
|
local str, pre = "", util.trinary(eta_ms >= 0, "Full in ", "Empty in ")
|
||||||
|
|
||||||
|
local seconds = math.abs(eta_ms) / 1000
|
||||||
|
local minutes = seconds / 60
|
||||||
|
local hours = minutes / 60
|
||||||
|
local days = hours / 24
|
||||||
|
|
||||||
|
if math.abs(eta_ms) < 1000 or (eta_ms ~= eta_ms) then
|
||||||
|
-- really small or NaN
|
||||||
|
str = "No ETA"
|
||||||
|
elseif days < 1000 then
|
||||||
|
days = math.floor(days)
|
||||||
|
hours = math.floor(hours % 24)
|
||||||
|
minutes = math.floor(minutes % 60)
|
||||||
|
seconds = math.floor(seconds % 60)
|
||||||
|
|
||||||
|
if days > 0 then
|
||||||
|
str = days .. "d"
|
||||||
|
elseif hours > 0 then
|
||||||
|
str = hours .. "h " .. minutes .. "m"
|
||||||
|
elseif minutes > 0 then
|
||||||
|
str = minutes .. "m " .. seconds .. "s"
|
||||||
|
elseif seconds > 0 then
|
||||||
|
str = seconds .. "s"
|
||||||
|
end
|
||||||
|
|
||||||
|
str = pre .. str
|
||||||
|
else
|
||||||
|
local years = math.floor(days / 365.25)
|
||||||
|
|
||||||
|
if years <= 99999999 then
|
||||||
|
str = pre .. years .. "y"
|
||||||
|
else
|
||||||
|
str = pre .. "eras"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return str
|
||||||
|
end
|
||||||
|
|
||||||
-- record and publish multiblock status data
|
-- record and publish multiblock status data
|
||||||
---@param entry any
|
---@param entry any
|
||||||
---@param data imatrix_session_db|sps_session_db|dynamicv_session_db|turbinev_session_db|boilerv_session_db
|
---@param data imatrix_session_db|sps_session_db|dynamicv_session_db|turbinev_session_db|boilerv_session_db
|
||||||
@@ -540,7 +595,7 @@ function iocontrol.update_facility_status(status)
|
|||||||
fac.auto_saturated = ctl_status[5]
|
fac.auto_saturated = ctl_status[5]
|
||||||
|
|
||||||
fac.auto_scram = ctl_status[6]
|
fac.auto_scram = ctl_status[6]
|
||||||
fac.ascram_status.matrix_dc = ctl_status[7]
|
fac.ascram_status.matrix_fault = ctl_status[7]
|
||||||
fac.ascram_status.matrix_fill = ctl_status[8]
|
fac.ascram_status.matrix_fill = ctl_status[8]
|
||||||
fac.ascram_status.crit_alarm = ctl_status[9]
|
fac.ascram_status.crit_alarm = ctl_status[9]
|
||||||
fac.ascram_status.radiation = ctl_status[10]
|
fac.ascram_status.radiation = ctl_status[10]
|
||||||
@@ -555,7 +610,7 @@ function iocontrol.update_facility_status(status)
|
|||||||
fac.ps.publish("auto_ramping", fac.auto_ramping)
|
fac.ps.publish("auto_ramping", fac.auto_ramping)
|
||||||
fac.ps.publish("auto_saturated", fac.auto_saturated)
|
fac.ps.publish("auto_saturated", fac.auto_saturated)
|
||||||
fac.ps.publish("auto_scram", fac.auto_scram)
|
fac.ps.publish("auto_scram", fac.auto_scram)
|
||||||
fac.ps.publish("as_matrix_dc", fac.ascram_status.matrix_dc)
|
fac.ps.publish("as_matrix_fault", fac.ascram_status.matrix_fault)
|
||||||
fac.ps.publish("as_matrix_fill", fac.ascram_status.matrix_fill)
|
fac.ps.publish("as_matrix_fill", fac.ascram_status.matrix_fill)
|
||||||
fac.ps.publish("as_crit_alarm", fac.ascram_status.crit_alarm)
|
fac.ps.publish("as_crit_alarm", fac.ascram_status.crit_alarm)
|
||||||
fac.ps.publish("as_radiation", fac.ascram_status.radiation)
|
fac.ps.publish("as_radiation", fac.ascram_status.radiation)
|
||||||
@@ -609,6 +664,7 @@ function iocontrol.update_facility_status(status)
|
|||||||
ps.publish("avg_inflow", in_f)
|
ps.publish("avg_inflow", in_f)
|
||||||
ps.publish("avg_outflow", out_f)
|
ps.publish("avg_outflow", out_f)
|
||||||
ps.publish("eta_ms", eta)
|
ps.publish("eta_ms", eta)
|
||||||
|
ps.publish("eta_string", gen_eta_text(eta or 0))
|
||||||
|
|
||||||
ps.publish("is_charging", in_f > out_f)
|
ps.publish("is_charging", in_f > out_f)
|
||||||
ps.publish("is_discharging", out_f > in_f)
|
ps.publish("is_discharging", out_f > in_f)
|
||||||
@@ -624,10 +680,12 @@ function iocontrol.update_facility_status(status)
|
|||||||
|
|
||||||
-- induction matricies statuses
|
-- induction matricies statuses
|
||||||
if type(rtu_statuses.induction) == "table" then
|
if type(rtu_statuses.induction) == "table" then
|
||||||
|
local matrix_status = MTX_STATE.OFFLINE
|
||||||
|
|
||||||
for id = 1, #fac.induction_ps_tbl do
|
for id = 1, #fac.induction_ps_tbl do
|
||||||
if rtu_statuses.induction[id] == nil then
|
if rtu_statuses.induction[id] == nil then
|
||||||
-- disconnected
|
-- disconnected
|
||||||
fac.induction_ps_tbl[id].publish("computed_status", 1)
|
fac.induction_ps_tbl[id].publish("computed_status", matrix_status)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -639,18 +697,20 @@ function iocontrol.update_facility_status(status)
|
|||||||
local rtu_faulted = _record_multiblock_status(matrix, data, ps)
|
local rtu_faulted = _record_multiblock_status(matrix, data, ps)
|
||||||
|
|
||||||
if rtu_faulted then
|
if rtu_faulted then
|
||||||
ps.publish("computed_status", 3) -- faulted
|
matrix_status = MTX_STATE.FAULT
|
||||||
elseif data.formed then
|
elseif data.formed then
|
||||||
if data.tanks.energy_fill >= 0.99 then
|
if data.tanks.energy_fill >= 0.99 then
|
||||||
ps.publish("computed_status", 6) -- full
|
matrix_status = MTX_STATE.HIGH_CHARGE
|
||||||
elseif data.tanks.energy_fill <= 0.01 then
|
elseif data.tanks.energy_fill <= 0.01 then
|
||||||
ps.publish("computed_status", 5) -- empty
|
matrix_status = MTX_STATE.LOW_CHARGE
|
||||||
else
|
else
|
||||||
ps.publish("computed_status", 4) -- on-line
|
matrix_status = MTX_STATE.ONLINE
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
ps.publish("computed_status", 2) -- not formed
|
matrix_status = MTX_STATE.UNFORMED
|
||||||
end
|
end
|
||||||
|
|
||||||
|
ps.publish("computed_status", matrix_status)
|
||||||
else
|
else
|
||||||
log.debug(util.c(log_header, "invalid induction matrix id ", id))
|
log.debug(util.c(log_header, "invalid induction matrix id ", id))
|
||||||
end
|
end
|
||||||
@@ -662,10 +722,12 @@ function iocontrol.update_facility_status(status)
|
|||||||
|
|
||||||
-- SPS statuses
|
-- SPS statuses
|
||||||
if type(rtu_statuses.sps) == "table" then
|
if type(rtu_statuses.sps) == "table" then
|
||||||
|
local sps_status = SPS_STATE.OFFLINE
|
||||||
|
|
||||||
for id = 1, #fac.sps_ps_tbl do
|
for id = 1, #fac.sps_ps_tbl do
|
||||||
if rtu_statuses.sps[id] == nil then
|
if rtu_statuses.sps[id] == nil then
|
||||||
-- disconnected
|
-- disconnected
|
||||||
fac.sps_ps_tbl[id].publish("computed_status", 1)
|
fac.sps_ps_tbl[id].publish("computed_status", sps_status)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -677,16 +739,13 @@ function iocontrol.update_facility_status(status)
|
|||||||
local rtu_faulted = _record_multiblock_status(sps, data, ps)
|
local rtu_faulted = _record_multiblock_status(sps, data, ps)
|
||||||
|
|
||||||
if rtu_faulted then
|
if rtu_faulted then
|
||||||
ps.publish("computed_status", 3) -- faulted
|
sps_status = SPS_STATE.FAULT
|
||||||
elseif data.formed then
|
elseif data.formed then
|
||||||
if data.state.process_rate > 0 then
|
-- active / idle
|
||||||
ps.publish("computed_status", 5) -- active
|
sps_status = util.trinary(data.state.process_rate > 0, SPS_STATE.ACTIVE, SPS_STATE.IDLE)
|
||||||
else
|
else sps_status = SPS_STATE.UNFORMED end
|
||||||
ps.publish("computed_status", 4) -- idle
|
|
||||||
end
|
ps.publish("computed_status", sps_status)
|
||||||
else
|
|
||||||
ps.publish("computed_status", 2) -- not formed
|
|
||||||
end
|
|
||||||
|
|
||||||
io.facility.ps.publish("am_rate", data.state.process_rate * 1000)
|
io.facility.ps.publish("am_rate", data.state.process_rate * 1000)
|
||||||
else
|
else
|
||||||
@@ -700,10 +759,12 @@ function iocontrol.update_facility_status(status)
|
|||||||
|
|
||||||
-- dynamic tank statuses
|
-- dynamic tank statuses
|
||||||
if type(rtu_statuses.tanks) == "table" then
|
if type(rtu_statuses.tanks) == "table" then
|
||||||
|
local tank_status = TNK_STATE.OFFLINE
|
||||||
|
|
||||||
for id = 1, #fac.tank_ps_tbl do
|
for id = 1, #fac.tank_ps_tbl do
|
||||||
if rtu_statuses.tanks[id] == nil then
|
if rtu_statuses.tanks[id] == nil then
|
||||||
-- disconnected
|
-- disconnected
|
||||||
fac.tank_ps_tbl[id].publish("computed_status", 1)
|
fac.tank_ps_tbl[id].publish("computed_status", tank_status)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -715,18 +776,18 @@ function iocontrol.update_facility_status(status)
|
|||||||
local rtu_faulted = _record_multiblock_status(tank, data, ps)
|
local rtu_faulted = _record_multiblock_status(tank, data, ps)
|
||||||
|
|
||||||
if rtu_faulted then
|
if rtu_faulted then
|
||||||
ps.publish("computed_status", 3) -- faulted
|
tank_status = TNK_STATE.FAULT
|
||||||
elseif data.formed then
|
elseif data.formed then
|
||||||
if data.tanks.fill >= 0.99 then
|
if data.tanks.fill >= 0.99 then
|
||||||
ps.publish("computed_status", 6) -- full
|
tank_status = TNK_STATE.HIGH_FILL
|
||||||
elseif data.tanks.fill < 0.20 then
|
elseif data.tanks.fill < 0.20 then
|
||||||
ps.publish("computed_status", 5) -- low
|
tank_status = TNK_STATE.LOW_FILL
|
||||||
else
|
else
|
||||||
ps.publish("computed_status", 4) -- on-line
|
tank_status = TNK_STATE.ONLINE
|
||||||
end
|
end
|
||||||
else
|
else tank_status = TNK_STATE.UNFORMED end
|
||||||
ps.publish("computed_status", 2) -- not formed
|
|
||||||
end
|
ps.publish("computed_status", tank_status)
|
||||||
else
|
else
|
||||||
log.debug(util.c(log_header, "invalid dynamic tank id ", id))
|
log.debug(util.c(log_header, "invalid dynamic tank id ", id))
|
||||||
end
|
end
|
||||||
@@ -740,7 +801,9 @@ function iocontrol.update_facility_status(status)
|
|||||||
if type(rtu_statuses.envds) == "table" then
|
if type(rtu_statuses.envds) == "table" then
|
||||||
local max_rad, max_reading, any_conn, any_faulted = 0, types.new_zero_radiation_reading(), false, false
|
local max_rad, max_reading, any_conn, any_faulted = 0, types.new_zero_radiation_reading(), false, false
|
||||||
|
|
||||||
for _, envd in pairs(rtu_statuses.envds) do
|
fac.rad_monitors = {}
|
||||||
|
|
||||||
|
for id, envd in pairs(rtu_statuses.envds) do
|
||||||
local rtu_faulted = envd[1] ---@type boolean
|
local rtu_faulted = envd[1] ---@type boolean
|
||||||
local radiation = envd[2] ---@type radiation_reading
|
local radiation = envd[2] ---@type radiation_reading
|
||||||
local rad_raw = envd[3] ---@type number
|
local rad_raw = envd[3] ---@type number
|
||||||
@@ -752,6 +815,10 @@ function iocontrol.update_facility_status(status)
|
|||||||
max_rad = rad_raw
|
max_rad = rad_raw
|
||||||
max_reading = radiation
|
max_reading = radiation
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if not rtu_faulted then
|
||||||
|
fac.rad_monitors[id] = { radiation = radiation, raw = rad_raw }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if any_conn then
|
if any_conn then
|
||||||
@@ -826,9 +893,11 @@ function iocontrol.update_unit_statuses(statuses)
|
|||||||
log.debug(log_header .. "reactor status not a table")
|
log.debug(log_header .. "reactor status not a table")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local computed_status = RCT_STATE.OFFLINE
|
||||||
|
|
||||||
if #reactor_status == 0 then
|
if #reactor_status == 0 then
|
||||||
unit.connected = false
|
unit.connected = false
|
||||||
unit.unit_ps.publish("computed_status", 1) -- disconnected
|
unit.unit_ps.publish("computed_status", computed_status)
|
||||||
elseif #reactor_status == 3 then
|
elseif #reactor_status == 3 then
|
||||||
local mek_status = reactor_status[1]
|
local mek_status = reactor_status[1]
|
||||||
local rps_status = reactor_status[2]
|
local rps_status = reactor_status[2]
|
||||||
@@ -867,22 +936,23 @@ function iocontrol.update_unit_statuses(statuses)
|
|||||||
burn_rate_sum = burn_rate_sum + burn_rate
|
burn_rate_sum = burn_rate_sum + burn_rate
|
||||||
|
|
||||||
if unit.reactor_data.mek_status.status then
|
if unit.reactor_data.mek_status.status then
|
||||||
unit.unit_ps.publish("computed_status", 5) -- running
|
computed_status = RCT_STATE.ACTIVE
|
||||||
else
|
else
|
||||||
if unit.reactor_data.no_reactor then
|
if unit.reactor_data.no_reactor then
|
||||||
unit.unit_ps.publish("computed_status", 3) -- faulted
|
computed_status = RCT_STATE.FAULT
|
||||||
elseif not unit.reactor_data.formed then
|
elseif not unit.reactor_data.formed then
|
||||||
unit.unit_ps.publish("computed_status", 2) -- multiblock not formed
|
computed_status = RCT_STATE.UNFORMED
|
||||||
elseif unit.reactor_data.rps_status.force_dis then
|
elseif unit.reactor_data.rps_status.force_dis then
|
||||||
unit.unit_ps.publish("computed_status", 7) -- reactor force disabled
|
computed_status = RCT_STATE.FORCE_DISABLED
|
||||||
elseif unit.reactor_data.rps_tripped and unit.reactor_data.rps_trip_cause ~= "manual" then
|
elseif unit.reactor_data.rps_tripped and unit.reactor_data.rps_trip_cause ~= "manual" then
|
||||||
unit.unit_ps.publish("computed_status", 6) -- SCRAM
|
computed_status = RCT_STATE.SCRAMMED
|
||||||
else
|
else
|
||||||
unit.unit_ps.publish("computed_status", 4) -- disabled
|
computed_status = RCT_STATE.DISABLED
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
unit.connected = true
|
unit.connected = true
|
||||||
|
unit.unit_ps.publish("computed_status", computed_status)
|
||||||
else
|
else
|
||||||
log.debug(log_header .. "reactor status length mismatch")
|
log.debug(log_header .. "reactor status length mismatch")
|
||||||
valid = false
|
valid = false
|
||||||
@@ -896,13 +966,11 @@ function iocontrol.update_unit_statuses(statuses)
|
|||||||
if type(rtu_statuses.boilers) == "table" then
|
if type(rtu_statuses.boilers) == "table" then
|
||||||
local boil_sum = 0
|
local boil_sum = 0
|
||||||
|
|
||||||
for id = 1, #unit.boiler_ps_tbl do
|
computed_status = BLR_STATE.OFFLINE
|
||||||
local connected = rtu_statuses.boilers[id] ~= nil
|
|
||||||
unit.rtu_hw.boilers[id].connected = connected
|
|
||||||
|
|
||||||
if not connected then
|
for id = 1, #unit.boiler_ps_tbl do
|
||||||
-- disconnected
|
if rtu_statuses.boilers[id] == nil then
|
||||||
unit.boiler_ps_tbl[id].publish("computed_status", 1)
|
unit.boiler_ps_tbl[id].publish("computed_status", computed_status)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -912,21 +980,15 @@ function iocontrol.update_unit_statuses(statuses)
|
|||||||
local ps = unit.boiler_ps_tbl[id]
|
local ps = unit.boiler_ps_tbl[id]
|
||||||
|
|
||||||
local rtu_faulted = _record_multiblock_status(boiler, data, ps)
|
local rtu_faulted = _record_multiblock_status(boiler, data, ps)
|
||||||
unit.rtu_hw.boilers[id].faulted = rtu_faulted
|
|
||||||
|
|
||||||
if rtu_faulted then
|
if rtu_faulted then
|
||||||
ps.publish("computed_status", 3) -- faulted
|
computed_status = BLR_STATE.FAULT
|
||||||
elseif data.formed then
|
elseif data.formed then
|
||||||
boil_sum = boil_sum + data.state.boil_rate
|
boil_sum = boil_sum + data.state.boil_rate
|
||||||
|
computed_status = util.trinary(data.state.boil_rate > 0, BLR_STATE.ACTIVE, BLR_STATE.IDLE)
|
||||||
|
else computed_status = BLR_STATE.UNFORMED end
|
||||||
|
|
||||||
if data.state.boil_rate > 0 then
|
unit.boiler_ps_tbl[id].publish("computed_status", computed_status)
|
||||||
ps.publish("computed_status", 5) -- active
|
|
||||||
else
|
|
||||||
ps.publish("computed_status", 4) -- idle
|
|
||||||
end
|
|
||||||
else
|
|
||||||
ps.publish("computed_status", 2) -- not formed
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
log.debug(util.c(log_header, "invalid boiler id ", id))
|
log.debug(util.c(log_header, "invalid boiler id ", id))
|
||||||
valid = false
|
valid = false
|
||||||
@@ -943,13 +1005,11 @@ function iocontrol.update_unit_statuses(statuses)
|
|||||||
if type(rtu_statuses.turbines) == "table" then
|
if type(rtu_statuses.turbines) == "table" then
|
||||||
local flow_sum = 0
|
local flow_sum = 0
|
||||||
|
|
||||||
for id = 1, #unit.turbine_ps_tbl do
|
computed_status = TRB_STATE.OFFLINE
|
||||||
local connected = rtu_statuses.turbines[id] ~= nil
|
|
||||||
unit.rtu_hw.turbines[id].connected = connected
|
|
||||||
|
|
||||||
if not connected then
|
for id = 1, #unit.turbine_ps_tbl do
|
||||||
-- disconnected
|
if rtu_statuses.turbines[id] == nil then
|
||||||
unit.turbine_ps_tbl[id].publish("computed_status", 1)
|
unit.turbine_ps_tbl[id].publish("computed_status", computed_status)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -959,23 +1019,22 @@ function iocontrol.update_unit_statuses(statuses)
|
|||||||
local ps = unit.turbine_ps_tbl[id]
|
local ps = unit.turbine_ps_tbl[id]
|
||||||
|
|
||||||
local rtu_faulted = _record_multiblock_status(turbine, data, ps)
|
local rtu_faulted = _record_multiblock_status(turbine, data, ps)
|
||||||
unit.rtu_hw.turbines[id].faulted = rtu_faulted
|
|
||||||
|
|
||||||
if rtu_faulted then
|
if rtu_faulted then
|
||||||
ps.publish("computed_status", 3) -- faulted
|
computed_status = TRB_STATE.FAULT
|
||||||
elseif data.formed then
|
elseif data.formed then
|
||||||
flow_sum = flow_sum + data.state.flow_rate
|
flow_sum = flow_sum + data.state.flow_rate
|
||||||
|
|
||||||
if data.tanks.energy_fill >= 0.99 then
|
if data.tanks.energy_fill >= 0.99 then
|
||||||
ps.publish("computed_status", 6) -- trip
|
computed_status = TRB_STATE.TRIPPED
|
||||||
elseif data.state.flow_rate < 100 then
|
elseif data.state.flow_rate < 100 then
|
||||||
ps.publish("computed_status", 4) -- idle
|
computed_status = TRB_STATE.IDLE
|
||||||
else
|
else
|
||||||
ps.publish("computed_status", 5) -- active
|
computed_status = TRB_STATE.ACTIVE
|
||||||
end
|
end
|
||||||
else
|
else computed_status = TRB_STATE.UNFORMED end
|
||||||
ps.publish("computed_status", 2) -- not formed
|
|
||||||
end
|
unit.turbine_ps_tbl[id].publish("computed_status", computed_status)
|
||||||
else
|
else
|
||||||
log.debug(util.c(log_header, "invalid turbine id ", id))
|
log.debug(util.c(log_header, "invalid turbine id ", id))
|
||||||
valid = false
|
valid = false
|
||||||
@@ -990,10 +1049,11 @@ function iocontrol.update_unit_statuses(statuses)
|
|||||||
|
|
||||||
-- dynamic tank statuses
|
-- dynamic tank statuses
|
||||||
if type(rtu_statuses.tanks) == "table" then
|
if type(rtu_statuses.tanks) == "table" then
|
||||||
|
computed_status = TNK_STATE.OFFLINE
|
||||||
|
|
||||||
for id = 1, #unit.tank_ps_tbl do
|
for id = 1, #unit.tank_ps_tbl do
|
||||||
if rtu_statuses.tanks[id] == nil then
|
if rtu_statuses.tanks[id] == nil then
|
||||||
-- disconnected
|
unit.tank_ps_tbl[id].publish("computed_status", computed_status)
|
||||||
unit.tank_ps_tbl[id].publish("computed_status", 1)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -1005,18 +1065,18 @@ function iocontrol.update_unit_statuses(statuses)
|
|||||||
local rtu_faulted = _record_multiblock_status(tank, data, ps)
|
local rtu_faulted = _record_multiblock_status(tank, data, ps)
|
||||||
|
|
||||||
if rtu_faulted then
|
if rtu_faulted then
|
||||||
ps.publish("computed_status", 3) -- faulted
|
computed_status = TNK_STATE.FAULT
|
||||||
elseif data.formed then
|
elseif data.formed then
|
||||||
if data.tanks.fill >= 0.99 then
|
if data.tanks.fill >= 0.99 then
|
||||||
ps.publish("computed_status", 6) -- full
|
computed_status = TNK_STATE.HIGH_FILL
|
||||||
elseif data.tanks.fill < 0.20 then
|
elseif data.tanks.fill < 0.20 then
|
||||||
ps.publish("computed_status", 5) -- low
|
computed_status = TNK_STATE.LOW_FILL
|
||||||
else
|
else
|
||||||
ps.publish("computed_status", 4) -- on-line
|
computed_status = TNK_STATE.ONLINE
|
||||||
end
|
end
|
||||||
else
|
else computed_status = TNK_STATE.UNFORMED end
|
||||||
ps.publish("computed_status", 2) -- not formed
|
|
||||||
end
|
unit.tank_ps_tbl[id].publish("computed_status", computed_status)
|
||||||
else
|
else
|
||||||
log.debug(util.c(log_header, "invalid dynamic tank id ", id))
|
log.debug(util.c(log_header, "invalid dynamic tank id ", id))
|
||||||
valid = false
|
valid = false
|
||||||
@@ -1049,9 +1109,12 @@ function iocontrol.update_unit_statuses(statuses)
|
|||||||
if type(rtu_statuses.envds) == "table" then
|
if type(rtu_statuses.envds) == "table" then
|
||||||
local max_rad, max_reading, any_conn = 0, types.new_zero_radiation_reading(), false
|
local max_rad, max_reading, any_conn = 0, types.new_zero_radiation_reading(), false
|
||||||
|
|
||||||
for _, envd in pairs(rtu_statuses.envds) do
|
unit.rad_monitors = {}
|
||||||
local radiation = envd[2] ---@type radiation_reading
|
|
||||||
local rad_raw = envd[3] ---@type number
|
for id, envd in pairs(rtu_statuses.envds) do
|
||||||
|
local rtu_faulted = envd[1] ---@type boolean
|
||||||
|
local radiation = envd[2] ---@type radiation_reading
|
||||||
|
local rad_raw = envd[3] ---@type number
|
||||||
|
|
||||||
any_conn = true
|
any_conn = true
|
||||||
|
|
||||||
@@ -1059,6 +1122,10 @@ function iocontrol.update_unit_statuses(statuses)
|
|||||||
max_rad = rad_raw
|
max_rad = rad_raw
|
||||||
max_reading = radiation
|
max_reading = radiation
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if not rtu_faulted then
|
||||||
|
unit.rad_monitors[id] = { radiation = radiation, raw = rad_raw }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if any_conn then
|
if any_conn then
|
||||||
@@ -1081,6 +1148,7 @@ function iocontrol.update_unit_statuses(statuses)
|
|||||||
unit.annunciator = status[3]
|
unit.annunciator = status[3]
|
||||||
|
|
||||||
if type(unit.annunciator) ~= "table" then
|
if type(unit.annunciator) ~= "table" then
|
||||||
|
---@diagnostic disable-next-line: missing-fields
|
||||||
unit.annunciator = {}
|
unit.annunciator = {}
|
||||||
log.debug(log_header .. "annunciator state not a table")
|
log.debug(log_header .. "annunciator state not a table")
|
||||||
valid = false
|
valid = false
|
||||||
@@ -1164,7 +1232,7 @@ function iocontrol.update_unit_statuses(statuses)
|
|||||||
local valve_states = status[6]
|
local valve_states = status[6]
|
||||||
|
|
||||||
if type(valve_states) == "table" then
|
if type(valve_states) == "table" then
|
||||||
if #valve_states == 5 then
|
if #valve_states == 6 then
|
||||||
unit.unit_ps.publish("V_pu_conn", valve_states[1] > 0)
|
unit.unit_ps.publish("V_pu_conn", valve_states[1] > 0)
|
||||||
unit.unit_ps.publish("V_pu_state", valve_states[1] == 2)
|
unit.unit_ps.publish("V_pu_state", valve_states[1] == 2)
|
||||||
unit.unit_ps.publish("V_po_conn", valve_states[2] > 0)
|
unit.unit_ps.publish("V_po_conn", valve_states[2] > 0)
|
||||||
@@ -1175,6 +1243,8 @@ function iocontrol.update_unit_statuses(statuses)
|
|||||||
unit.unit_ps.publish("V_am_state", valve_states[4] == 2)
|
unit.unit_ps.publish("V_am_state", valve_states[4] == 2)
|
||||||
unit.unit_ps.publish("V_emc_conn", valve_states[5] > 0)
|
unit.unit_ps.publish("V_emc_conn", valve_states[5] > 0)
|
||||||
unit.unit_ps.publish("V_emc_state", valve_states[5] == 2)
|
unit.unit_ps.publish("V_emc_state", valve_states[5] == 2)
|
||||||
|
unit.unit_ps.publish("V_aux_conn", valve_states[6] > 0)
|
||||||
|
unit.unit_ps.publish("V_aux_state", valve_states[6] == 2)
|
||||||
else
|
else
|
||||||
log.debug(log_header .. "valve states length mismatch")
|
log.debug(log_header .. "valve states length mismatch")
|
||||||
valid = false
|
valid = false
|
||||||
@@ -1192,6 +1262,7 @@ function iocontrol.update_unit_statuses(statuses)
|
|||||||
local u_spent_rate = waste_rate
|
local u_spent_rate = waste_rate
|
||||||
local u_pu_rate = util.trinary(is_pu, waste_rate, 0.0)
|
local u_pu_rate = util.trinary(is_pu, waste_rate, 0.0)
|
||||||
local u_po_rate = unit.sna_out_rate
|
local u_po_rate = unit.sna_out_rate
|
||||||
|
local u_po_pl_rate = 0
|
||||||
|
|
||||||
unit.unit_ps.publish("pu_rate", u_pu_rate)
|
unit.unit_ps.publish("pu_rate", u_pu_rate)
|
||||||
unit.unit_ps.publish("po_rate", u_po_rate)
|
unit.unit_ps.publish("po_rate", u_po_rate)
|
||||||
@@ -1202,6 +1273,7 @@ function iocontrol.update_unit_statuses(statuses)
|
|||||||
u_spent_rate = u_po_rate
|
u_spent_rate = u_po_rate
|
||||||
unit.unit_ps.publish("po_pl_rate", u_po_rate)
|
unit.unit_ps.publish("po_pl_rate", u_po_rate)
|
||||||
unit.unit_ps.publish("po_am_rate", 0)
|
unit.unit_ps.publish("po_am_rate", 0)
|
||||||
|
u_po_pl_rate = u_po_rate
|
||||||
po_pl_rate = po_pl_rate + u_po_rate
|
po_pl_rate = po_pl_rate + u_po_rate
|
||||||
elseif unit.waste_product == types.WASTE_PRODUCT.ANTI_MATTER then
|
elseif unit.waste_product == types.WASTE_PRODUCT.ANTI_MATTER then
|
||||||
u_spent_rate = 0
|
u_spent_rate = 0
|
||||||
@@ -1213,6 +1285,8 @@ function iocontrol.update_unit_statuses(statuses)
|
|||||||
unit.unit_ps.publish("po_am_rate", 0)
|
unit.unit_ps.publish("po_am_rate", 0)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
unit.waste_stats = { u_pu_rate, u_po_rate, u_po_pl_rate }
|
||||||
|
|
||||||
unit.unit_ps.publish("ws_rate", u_spent_rate)
|
unit.unit_ps.publish("ws_rate", u_spent_rate)
|
||||||
|
|
||||||
pu_rate = pu_rate + u_pu_rate
|
pu_rate = pu_rate + u_pu_rate
|
||||||
@@ -1221,6 +1295,8 @@ function iocontrol.update_unit_statuses(statuses)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
io.facility.waste_stats = { burn_rate_sum, pu_rate, po_rate, po_pl_rate, po_am_rate, spent_rate }
|
||||||
|
|
||||||
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("sna_count", sna_count_sum)
|
||||||
io.facility.ps.publish("pu_rate", pu_rate)
|
io.facility.ps.publish("pu_rate", pu_rate)
|
||||||
|
|||||||
@@ -139,6 +139,11 @@ function process.init(iocontrol, coord_comms)
|
|||||||
|
|
||||||
log.info("PROCESS: loaded priority groups settings")
|
log.info("PROCESS: loaded priority groups settings")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- report to the supervisor all initial configuration data has been sent
|
||||||
|
-- startup resume can occur if needed
|
||||||
|
local p = ctl_proc
|
||||||
|
pctl.comms.send_ready(p.mode, p.burn_target, p.charge_target, p.gen_target, p.limits)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- create a handle to process control for usage of commands that get acknowledgements
|
-- create a handle to process control for usage of commands that get acknowledgements
|
||||||
@@ -286,6 +291,7 @@ function process.create_handle()
|
|||||||
handle.unit_ack = {}
|
handle.unit_ack = {}
|
||||||
|
|
||||||
for u = 1, pctl.io.facility.num_units do
|
for u = 1, pctl.io.facility.num_units do
|
||||||
|
---@diagnostic disable-next-line: missing-fields
|
||||||
handle.unit_ack[u] = {}
|
handle.unit_ack[u] = {}
|
||||||
|
|
||||||
---@class process_unit_ack
|
---@class process_unit_ack
|
||||||
@@ -445,36 +451,21 @@ end
|
|||||||
---@param product WASTE_PRODUCT waste product for auto control
|
---@param product WASTE_PRODUCT waste product for auto control
|
||||||
function process.set_process_waste(product)
|
function process.set_process_waste(product)
|
||||||
pctl.comms.send_fac_command(F_CMD.SET_WASTE_MODE, product)
|
pctl.comms.send_fac_command(F_CMD.SET_WASTE_MODE, product)
|
||||||
|
|
||||||
log.debug(util.c("PROCESS: SET WASTE ", product))
|
log.debug(util.c("PROCESS: SET WASTE ", product))
|
||||||
|
|
||||||
-- update config table and save
|
|
||||||
pctl.control_states.process.waste_product = product
|
|
||||||
_write_auto_config()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- set automatic process control plutonium fallback
|
-- set automatic process control plutonium fallback
|
||||||
---@param enabled boolean whether to enable plutonium fallback
|
---@param enabled boolean whether to enable plutonium fallback
|
||||||
function process.set_pu_fallback(enabled)
|
function process.set_pu_fallback(enabled)
|
||||||
pctl.comms.send_fac_command(F_CMD.SET_PU_FB, enabled)
|
pctl.comms.send_fac_command(F_CMD.SET_PU_FB, enabled)
|
||||||
|
|
||||||
log.debug(util.c("PROCESS: SET PU FALLBACK ", enabled))
|
log.debug(util.c("PROCESS: SET PU FALLBACK ", enabled))
|
||||||
|
|
||||||
-- update config table and save
|
|
||||||
pctl.control_states.process.pu_fallback = enabled
|
|
||||||
_write_auto_config()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- set automatic process control SPS usage at low power
|
-- set automatic process control SPS usage at low power
|
||||||
---@param enabled boolean whether to enable SPS usage at low power
|
---@param enabled boolean whether to enable SPS usage at low power
|
||||||
function process.set_sps_low_power(enabled)
|
function process.set_sps_low_power(enabled)
|
||||||
pctl.comms.send_fac_command(F_CMD.SET_SPS_LP, enabled)
|
pctl.comms.send_fac_command(F_CMD.SET_SPS_LP, enabled)
|
||||||
|
|
||||||
log.debug(util.c("PROCESS: SET SPS LOW POWER ", enabled))
|
log.debug(util.c("PROCESS: SET SPS LOW POWER ", enabled))
|
||||||
|
|
||||||
-- update config table and save
|
|
||||||
pctl.control_states.process.sps_low_power = enabled
|
|
||||||
_write_auto_config()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- save process control settings
|
-- save process control settings
|
||||||
@@ -527,21 +518,30 @@ end
|
|||||||
-- record waste product settting after attempting to change it
|
-- record waste product settting after attempting to change it
|
||||||
---@param response WASTE_PRODUCT supervisor waste product settting
|
---@param response WASTE_PRODUCT supervisor waste product settting
|
||||||
function process.waste_ack_handle(response)
|
function process.waste_ack_handle(response)
|
||||||
|
-- update config table and save
|
||||||
pctl.control_states.process.waste_product = response
|
pctl.control_states.process.waste_product = response
|
||||||
|
_write_auto_config()
|
||||||
|
|
||||||
pctl.io.facility.ps.publish("process_waste_product", response)
|
pctl.io.facility.ps.publish("process_waste_product", response)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- record plutonium fallback settting after attempting to change it
|
-- record plutonium fallback settting after attempting to change it
|
||||||
---@param response boolean supervisor plutonium fallback settting
|
---@param response boolean supervisor plutonium fallback settting
|
||||||
function process.pu_fb_ack_handle(response)
|
function process.pu_fb_ack_handle(response)
|
||||||
|
-- update config table and save
|
||||||
pctl.control_states.process.pu_fallback = response
|
pctl.control_states.process.pu_fallback = response
|
||||||
|
_write_auto_config()
|
||||||
|
|
||||||
pctl.io.facility.ps.publish("process_pu_fallback", response)
|
pctl.io.facility.ps.publish("process_pu_fallback", response)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- record SPS low power settting after attempting to change it
|
-- record SPS low power settting after attempting to change it
|
||||||
---@param response boolean supervisor SPS low power settting
|
---@param response boolean supervisor SPS low power settting
|
||||||
function process.sps_lp_ack_handle(response)
|
function process.sps_lp_ack_handle(response)
|
||||||
|
-- update config table and save
|
||||||
pctl.control_states.process.sps_low_power = response
|
pctl.control_states.process.sps_low_power = response
|
||||||
|
_write_auto_config()
|
||||||
|
|
||||||
pctl.io.facility.ps.publish("process_sps_low_power", response)
|
pctl.io.facility.ps.publish("process_sps_low_power", response)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ function renderer.try_start_fp()
|
|||||||
if not engine.fp_ready then
|
if not engine.fp_ready then
|
||||||
-- show front panel view on terminal
|
-- show front panel view on terminal
|
||||||
status, msg = pcall(function ()
|
status, msg = pcall(function ()
|
||||||
engine.ui.front_panel = DisplayBox{window=term.native(),fg_bg=style.fp.root}
|
engine.ui.front_panel = DisplayBox{window=term.current(),fg_bg=style.fp.root}
|
||||||
panel_view(engine.ui.front_panel, #engine.monitors.unit_displays)
|
panel_view(engine.ui.front_panel, #engine.monitors.unit_displays)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
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 iocontrol = require("coordinator.iocontrol")
|
local iocontrol = require("coordinator.iocontrol")
|
||||||
@@ -14,6 +15,9 @@ local MGMT_TYPE = comms.MGMT_TYPE
|
|||||||
local FAC_COMMAND = comms.FAC_COMMAND
|
local FAC_COMMAND = comms.FAC_COMMAND
|
||||||
local UNIT_COMMAND = comms.UNIT_COMMAND
|
local UNIT_COMMAND = comms.UNIT_COMMAND
|
||||||
|
|
||||||
|
local AUTO_GROUP = types.AUTO_GROUP
|
||||||
|
local WASTE_MODE = types.WASTE_MODE
|
||||||
|
|
||||||
-- 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
|
||||||
@@ -166,8 +170,26 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout)
|
|||||||
log.info(log_tag .. "FAC ACK ALL ALARMS")
|
log.info(log_tag .. "FAC ACK ALL ALARMS")
|
||||||
self.proc_handle.fac_ack_alarms()
|
self.proc_handle.fac_ack_alarms()
|
||||||
elseif cmd == FAC_COMMAND.SET_WASTE_MODE then
|
elseif cmd == FAC_COMMAND.SET_WASTE_MODE then
|
||||||
|
if pkt.length == 2 then
|
||||||
|
log.info(util.c(log_tag, " SET WASTE ", pkt.data[2]))
|
||||||
|
process.set_process_waste(pkt.data[2])
|
||||||
|
else
|
||||||
|
log.debug(log_tag .. "CRDN set waste mode packet length mismatch")
|
||||||
|
end
|
||||||
elseif cmd == FAC_COMMAND.SET_PU_FB then
|
elseif cmd == FAC_COMMAND.SET_PU_FB then
|
||||||
|
if pkt.length == 2 then
|
||||||
|
log.info(util.c(log_tag, " SET PU FALLBACK ", pkt.data[2]))
|
||||||
|
process.set_pu_fallback(pkt.data[2] == true)
|
||||||
|
else
|
||||||
|
log.debug(log_tag .. "CRDN set pu fallback packet length mismatch")
|
||||||
|
end
|
||||||
elseif cmd == FAC_COMMAND.SET_SPS_LP then
|
elseif cmd == FAC_COMMAND.SET_SPS_LP then
|
||||||
|
if pkt.length == 2 then
|
||||||
|
log.info(util.c(log_tag, " SET SPS LOW POWER ", pkt.data[2]))
|
||||||
|
process.set_sps_low_power(pkt.data[2] == true)
|
||||||
|
else
|
||||||
|
log.debug(log_tag .. "CRDN set sps low power packet length mismatch")
|
||||||
|
end
|
||||||
else
|
else
|
||||||
log.debug(log_tag .. "CRDN facility command unknown")
|
log.debug(log_tag .. "CRDN facility command unknown")
|
||||||
end
|
end
|
||||||
@@ -192,20 +214,28 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout)
|
|||||||
log.info(util.c(log_tag, "UNIT[", uid, "] RESET RPS"))
|
log.info(util.c(log_tag, "UNIT[", uid, "] RESET RPS"))
|
||||||
self.proc_handle.reset_rps(uid)
|
self.proc_handle.reset_rps(uid)
|
||||||
elseif cmd == UNIT_COMMAND.SET_BURN then
|
elseif cmd == UNIT_COMMAND.SET_BURN then
|
||||||
if pkt.length == 3 then
|
if (pkt.length == 3) and (type(pkt.data[3]) == "number") then
|
||||||
log.info(util.c(log_tag, "UNIT[", uid, "] SET BURN ", pkt.data[3]))
|
log.info(util.c(log_tag, "UNIT[", uid, "] SET BURN ", pkt.data[3]))
|
||||||
process.set_rate(uid, pkt.data[3])
|
process.set_rate(uid, pkt.data[3])
|
||||||
else
|
else
|
||||||
log.debug(log_tag .. "CRDN unit command burn rate missing option")
|
log.debug(log_tag .. "CRDN unit command burn rate missing option")
|
||||||
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] >= WASTE_MODE.AUTO) and (pkt.data[3] <= WASTE_MODE.MANUAL_ANTI_MATTER) then
|
||||||
|
log.info(util.c(log_tag, "UNIT[", id, "] SET WASTE ", pkt.data[3]))
|
||||||
|
process.set_unit_waste(uid, pkt.data[3])
|
||||||
|
else
|
||||||
|
log.debug(log_tag .. "CRDN unit command set waste missing/invalid option")
|
||||||
|
end
|
||||||
elseif cmd == UNIT_COMMAND.ACK_ALL_ALARMS then
|
elseif cmd == UNIT_COMMAND.ACK_ALL_ALARMS then
|
||||||
log.info(util.c(log_tag, "UNIT[", uid, "] ACK ALL ALARMS"))
|
log.info(util.c(log_tag, "UNIT[", uid, "] ACK ALL ALARMS"))
|
||||||
self.proc_handle.ack_all_alarms(uid)
|
self.proc_handle.ack_all_alarms(uid)
|
||||||
elseif cmd == UNIT_COMMAND.ACK_ALARM then
|
elseif cmd == UNIT_COMMAND.ACK_ALARM then
|
||||||
elseif cmd == UNIT_COMMAND.RESET_ALARM then
|
elseif cmd == UNIT_COMMAND.RESET_ALARM then
|
||||||
elseif cmd == UNIT_COMMAND.SET_GROUP then
|
elseif cmd == UNIT_COMMAND.SET_GROUP then
|
||||||
if pkt.length == 3 then
|
if (pkt.length == 3) and (type(pkt.data[3]) == "number") and
|
||||||
|
(pkt.data[3] >= AUTO_GROUP.MANUAL) and (pkt.data[3] <= AUTO_GROUP.BACKUP) then
|
||||||
log.info(util.c(log_tag, "UNIT[", uid, "] SET GROUP ", pkt.data[3]))
|
log.info(util.c(log_tag, "UNIT[", uid, "] SET GROUP ", pkt.data[3]))
|
||||||
process.set_group(uid, pkt.data[3])
|
process.set_group(uid, pkt.data[3])
|
||||||
else
|
else
|
||||||
@@ -230,20 +260,67 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout)
|
|||||||
{ fac.auto_ready, fac.auto_active, fac.auto_ramping, fac.auto_saturated },
|
{ fac.auto_ready, fac.auto_active, fac.auto_ramping, fac.auto_saturated },
|
||||||
{ fac.auto_current_waste_product, fac.auto_pu_fallback_active },
|
{ fac.auto_current_waste_product, fac.auto_pu_fallback_active },
|
||||||
util.table_len(fac.tank_data_tbl),
|
util.table_len(fac.tank_data_tbl),
|
||||||
fac.induction_data_tbl[1] ~= nil,
|
fac.induction_data_tbl[1] ~= nil, ---@fixme this means nothing
|
||||||
fac.sps_data_tbl[1] ~= nil,
|
fac.sps_data_tbl[1] ~= nil ---@fixme this means nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
_send(CRDN_TYPE.API_GET_FAC, data)
|
_send(CRDN_TYPE.API_GET_FAC, data)
|
||||||
|
elseif pkt.type == CRDN_TYPE.API_GET_FAC_DTL then
|
||||||
|
local fac = db.facility
|
||||||
|
local mtx_sps = fac.induction_ps_tbl[1]
|
||||||
|
|
||||||
|
local units = {}
|
||||||
|
local tank_statuses = {}
|
||||||
|
|
||||||
|
for i = 1, #db.units do
|
||||||
|
local u = db.units[i]
|
||||||
|
units[i] = { u.connected, u.annunciator, u.reactor_data, u.tank_data_tbl }
|
||||||
|
for t = 1, #u.tank_ps_tbl do table.insert(tank_statuses, u.tank_ps_tbl[t].get("computed_status")) end
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = 1, #fac.tank_ps_tbl do table.insert(tank_statuses, fac.tank_ps_tbl[i].get("computed_status")) end
|
||||||
|
|
||||||
|
local matrix_data = {
|
||||||
|
mtx_sps.get("eta_string"),
|
||||||
|
mtx_sps.get("avg_charge"),
|
||||||
|
mtx_sps.get("avg_inflow"),
|
||||||
|
mtx_sps.get("avg_outflow"),
|
||||||
|
mtx_sps.get("is_charging"),
|
||||||
|
mtx_sps.get("is_discharging"),
|
||||||
|
mtx_sps.get("at_max_io")
|
||||||
|
}
|
||||||
|
|
||||||
|
local data = {
|
||||||
|
fac.all_sys_ok,
|
||||||
|
fac.rtu_count,
|
||||||
|
fac.auto_scram,
|
||||||
|
fac.ascram_status,
|
||||||
|
tank_statuses,
|
||||||
|
fac.tank_data_tbl,
|
||||||
|
fac.induction_ps_tbl[1].get("computed_status") or types.IMATRIX_STATE.OFFLINE,
|
||||||
|
fac.induction_data_tbl[1],
|
||||||
|
matrix_data,
|
||||||
|
fac.sps_ps_tbl[1].get("computed_status") or types.SPS_STATE.OFFLINE,
|
||||||
|
fac.sps_data_tbl[1],
|
||||||
|
units
|
||||||
|
}
|
||||||
|
|
||||||
|
_send(CRDN_TYPE.API_GET_FAC_DTL, data)
|
||||||
elseif pkt.type == CRDN_TYPE.API_GET_UNIT then
|
elseif pkt.type == CRDN_TYPE.API_GET_UNIT then
|
||||||
if pkt.length == 1 and type(pkt.data[1]) == "number" then
|
if pkt.length == 1 and type(pkt.data[1]) == "number" then
|
||||||
local u = db.units[pkt.data[1]]
|
local u = db.units[pkt.data[1]]
|
||||||
|
|
||||||
|
local statuses = { u.unit_ps.get("computed_status") }
|
||||||
|
|
||||||
|
for i = 1, #u.boiler_ps_tbl do table.insert(statuses, u.boiler_ps_tbl[i].get("computed_status")) end
|
||||||
|
for i = 1, #u.turbine_ps_tbl do table.insert(statuses, u.turbine_ps_tbl[i].get("computed_status")) end
|
||||||
|
for i = 1, #u.tank_ps_tbl do table.insert(statuses, u.tank_ps_tbl[i].get("computed_status")) end
|
||||||
|
|
||||||
if u then
|
if u then
|
||||||
local data = {
|
local data = {
|
||||||
u.unit_id,
|
u.unit_id,
|
||||||
u.connected,
|
u.connected,
|
||||||
u.rtu_hw,
|
statuses,
|
||||||
u.a_group,
|
u.a_group,
|
||||||
u.alarms,
|
u.alarms,
|
||||||
u.annunciator,
|
u.annunciator,
|
||||||
@@ -275,7 +352,6 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout)
|
|||||||
u.annunciator.AutoControl,
|
u.annunciator.AutoControl,
|
||||||
u.a_group
|
u.a_group
|
||||||
}
|
}
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
_send(CRDN_TYPE.API_GET_CTRL, data)
|
_send(CRDN_TYPE.API_GET_CTRL, data)
|
||||||
@@ -310,6 +386,54 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout)
|
|||||||
}
|
}
|
||||||
|
|
||||||
_send(CRDN_TYPE.API_GET_PROC, data)
|
_send(CRDN_TYPE.API_GET_PROC, data)
|
||||||
|
elseif pkt.type == CRDN_TYPE.API_GET_WASTE then
|
||||||
|
local data = {}
|
||||||
|
|
||||||
|
local fac = db.facility
|
||||||
|
local proc = process.get_control_states().process
|
||||||
|
|
||||||
|
-- unit data
|
||||||
|
for i = 1, #db.units do
|
||||||
|
local u = db.units[i]
|
||||||
|
|
||||||
|
data[i] = {
|
||||||
|
u.waste_mode,
|
||||||
|
u.waste_product,
|
||||||
|
u.num_snas,
|
||||||
|
u.sna_peak_rate,
|
||||||
|
u.sna_max_rate,
|
||||||
|
u.sna_out_rate,
|
||||||
|
u.waste_stats
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
local process_rate = 0
|
||||||
|
|
||||||
|
if fac.sps_data_tbl[1].state then
|
||||||
|
process_rate = fac.sps_data_tbl[1].state.process_rate
|
||||||
|
end
|
||||||
|
|
||||||
|
-- facility data
|
||||||
|
data[#db.units + 1] = {
|
||||||
|
fac.auto_current_waste_product,
|
||||||
|
fac.auto_pu_fallback_active,
|
||||||
|
fac.auto_sps_disabled,
|
||||||
|
proc.waste_product,
|
||||||
|
proc.pu_fallback,
|
||||||
|
proc.sps_low_power,
|
||||||
|
fac.waste_stats,
|
||||||
|
fac.sps_ps_tbl[1].get("computed_status") or types.SPS_STATE.OFFLINE,
|
||||||
|
process_rate
|
||||||
|
}
|
||||||
|
|
||||||
|
_send(CRDN_TYPE.API_GET_WASTE, data)
|
||||||
|
elseif pkt.type == CRDN_TYPE.API_GET_RAD then
|
||||||
|
local data = {}
|
||||||
|
|
||||||
|
for i = 1, #db.units do data[i] = db.units[i].rad_monitors end
|
||||||
|
data[#db.units + 1] = db.facility.rad_monitors
|
||||||
|
|
||||||
|
_send(CRDN_TYPE.API_GET_RAD, data)
|
||||||
else
|
else
|
||||||
log.debug(log_tag .. "handler received unsupported CRDN packet type " .. pkt.type)
|
log.debug(log_tag .. "handler received unsupported CRDN packet type " .. pkt.type)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ local renderer = require("coordinator.renderer")
|
|||||||
local sounder = require("coordinator.sounder")
|
local sounder = require("coordinator.sounder")
|
||||||
local threads = require("coordinator.threads")
|
local threads = require("coordinator.threads")
|
||||||
|
|
||||||
local COORDINATOR_VERSION = "v1.5.13"
|
local COORDINATOR_VERSION = "v1.6.16"
|
||||||
|
|
||||||
local CHUNK_LOAD_DELAY_S = 30.0
|
local CHUNK_LOAD_DELAY_S = 30.0
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,8 @@ local MAIN_CLOCK = 0.5 -- (2Hz, 10 ticks)
|
|||||||
local RENDER_SLEEP = 100 -- (100ms, 2 ticks)
|
local RENDER_SLEEP = 100 -- (100ms, 2 ticks)
|
||||||
|
|
||||||
local MQ__RENDER_CMD = {
|
local MQ__RENDER_CMD = {
|
||||||
START_MAIN_UI = 1
|
START_MAIN_UI = 1,
|
||||||
|
CLOSE_MAIN_UI = 2
|
||||||
}
|
}
|
||||||
|
|
||||||
local MQ__RENDER_DATA = {
|
local MQ__RENDER_DATA = {
|
||||||
@@ -81,7 +82,7 @@ function threads.thread__main(smem)
|
|||||||
nic.connect(other_modem)
|
nic.connect(other_modem)
|
||||||
else
|
else
|
||||||
-- close out main UI
|
-- close out main UI
|
||||||
renderer.close_ui()
|
smem.q.mq_render.push_command(MQ__RENDER_CMD.CLOSE_MAIN_UI)
|
||||||
|
|
||||||
-- alert user to status
|
-- alert user to status
|
||||||
log_sys("awaiting comms modem reconnect...")
|
log_sys("awaiting comms modem reconnect...")
|
||||||
@@ -167,9 +168,9 @@ function threads.thread__main(smem)
|
|||||||
-- supervisor watchdog timeout
|
-- supervisor watchdog timeout
|
||||||
log_comms("supervisor server timeout")
|
log_comms("supervisor server timeout")
|
||||||
|
|
||||||
-- close connection, main UI, and stop sounder
|
-- close main UI, connection, and stop sounder
|
||||||
|
smem.q.mq_render.push_command(MQ__RENDER_CMD.CLOSE_MAIN_UI)
|
||||||
coord_comms.close()
|
coord_comms.close()
|
||||||
renderer.close_ui()
|
|
||||||
sounder.stop()
|
sounder.stop()
|
||||||
else
|
else
|
||||||
-- a non-clock/main watchdog timer event
|
-- a non-clock/main watchdog timer event
|
||||||
@@ -188,9 +189,9 @@ function threads.thread__main(smem)
|
|||||||
if coord_comms.handle_packet(packet) then
|
if coord_comms.handle_packet(packet) then
|
||||||
log_comms("supervisor closed connection")
|
log_comms("supervisor closed connection")
|
||||||
|
|
||||||
-- close connection, main UI, and stop sounder
|
-- close main UI, connection, and stop sounder
|
||||||
|
smem.q.mq_render.push_command(MQ__RENDER_CMD.CLOSE_MAIN_UI)
|
||||||
coord_comms.close()
|
coord_comms.close()
|
||||||
renderer.close_ui()
|
|
||||||
sounder.stop()
|
sounder.stop()
|
||||||
end
|
end
|
||||||
elseif event == "monitor_touch" or event == "mouse_click" or event == "mouse_up" or
|
elseif event == "monitor_touch" or event == "mouse_click" or event == "mouse_up" or
|
||||||
@@ -302,6 +303,13 @@ function threads.thread__render(smem)
|
|||||||
else
|
else
|
||||||
log_render("main UI draw took " .. (util.time_ms() - draw_start) .. "ms")
|
log_render("main UI draw took " .. (util.time_ms() - draw_start) .. "ms")
|
||||||
end
|
end
|
||||||
|
elseif msg.message == MQ__RENDER_CMD.CLOSE_MAIN_UI then
|
||||||
|
-- close the main UI if it has been drawn
|
||||||
|
if renderer.ui_ready() then
|
||||||
|
log_render("closing main UI...")
|
||||||
|
renderer.close_ui()
|
||||||
|
log_render("main UI closed")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
elseif msg.qtype == mqueue.TYPE.DATA then
|
elseif msg.qtype == mqueue.TYPE.DATA then
|
||||||
-- received data
|
-- received data
|
||||||
|
|||||||
@@ -25,10 +25,9 @@ local ALIGN = core.ALIGN
|
|||||||
---@param root Container parent
|
---@param root Container parent
|
||||||
---@param x integer top left x
|
---@param x integer top left x
|
||||||
---@param y integer top left y
|
---@param y integer top left y
|
||||||
---@param data imatrix_session_db matrix data
|
|
||||||
---@param ps psil ps interface
|
---@param ps psil ps interface
|
||||||
---@param id number? matrix ID
|
---@param id number? matrix ID
|
||||||
local function new_view(root, x, y, data, ps, id)
|
local function new_view(root, x, y, ps, id)
|
||||||
local label_fg = style.theme.label_fg
|
local label_fg = style.theme.label_fg
|
||||||
local text_fg = style.theme.text_fg
|
local text_fg = style.theme.text_fg
|
||||||
local lu_col = style.lu_colors
|
local lu_col = style.lu_colors
|
||||||
@@ -94,6 +93,7 @@ local function new_view(root, x, y, data, ps, id)
|
|||||||
TextBox{parent=rect,text="FILL I/O",x=2,y=20,width=8,fg_bg=label_fg}
|
TextBox{parent=rect,text="FILL I/O",x=2,y=20,width=8,fg_bg=label_fg}
|
||||||
|
|
||||||
local function calc_saturation(val)
|
local function calc_saturation(val)
|
||||||
|
local data = db.facility.induction_data_tbl[id or 1]
|
||||||
if (type(data.build) == "table") and (type(data.build.transfer_cap) == "number") and (data.build.transfer_cap > 0) then
|
if (type(data.build) == "table") and (type(data.build.transfer_cap) == "number") and (data.build.transfer_cap > 0) then
|
||||||
return val / data.build.transfer_cap
|
return val / data.build.transfer_cap
|
||||||
else return 0 end
|
else return 0 end
|
||||||
@@ -105,46 +105,7 @@ local function new_view(root, x, y, data, ps, id)
|
|||||||
|
|
||||||
local eta = TextBox{parent=rect,x=11,y=20,width=20,text="ETA Unknown",alignment=ALIGN.CENTER,fg_bg=style.theme.field_box}
|
local eta = TextBox{parent=rect,x=11,y=20,width=20,text="ETA Unknown",alignment=ALIGN.CENTER,fg_bg=style.theme.field_box}
|
||||||
|
|
||||||
eta.register(ps, "eta_ms", function (eta_ms)
|
eta.register(ps, "eta_string", eta.set_value)
|
||||||
local str, pre = "", util.trinary(eta_ms >= 0, "Full in ", "Empty in ")
|
|
||||||
|
|
||||||
local seconds = math.abs(eta_ms) / 1000
|
|
||||||
local minutes = seconds / 60
|
|
||||||
local hours = minutes / 60
|
|
||||||
local days = hours / 24
|
|
||||||
|
|
||||||
if math.abs(eta_ms) < 1000 or (eta_ms ~= eta_ms) then
|
|
||||||
-- really small or NaN
|
|
||||||
str = "No ETA"
|
|
||||||
elseif days < 1000 then
|
|
||||||
days = math.floor(days)
|
|
||||||
hours = math.floor(hours % 24)
|
|
||||||
minutes = math.floor(minutes % 60)
|
|
||||||
seconds = math.floor(seconds % 60)
|
|
||||||
|
|
||||||
if days > 0 then
|
|
||||||
str = days .. "d"
|
|
||||||
elseif hours > 0 then
|
|
||||||
str = hours .. "h " .. minutes .. "m"
|
|
||||||
elseif minutes > 0 then
|
|
||||||
str = minutes .. "m " .. seconds .. "s"
|
|
||||||
elseif seconds > 0 then
|
|
||||||
str = seconds .. "s"
|
|
||||||
end
|
|
||||||
|
|
||||||
str = pre .. str
|
|
||||||
else
|
|
||||||
local years = math.floor(days / 365.25)
|
|
||||||
|
|
||||||
if years <= 99999999 then
|
|
||||||
str = pre .. years .. "y"
|
|
||||||
else
|
|
||||||
str = pre .. "eras"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
eta.set_value(str)
|
|
||||||
end)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return new_view
|
return new_view
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ local function init(parent, id)
|
|||||||
|
|
||||||
local ps = iocontrol.get_db().fp.ps
|
local ps = iocontrol.get_db().fp.ps
|
||||||
|
|
||||||
|
local term_w, _ = term.getSize()
|
||||||
|
|
||||||
-- root div
|
-- root div
|
||||||
local root = Div{parent=parent,x=2,y=2,height=4,width=parent.get_width()-2}
|
local root = Div{parent=parent,x=2,y=2,height=4,width=parent.get_width()-2}
|
||||||
local entry = Div{parent=root,x=2,y=1,height=3,fg_bg=s_hi_bright}
|
local entry = Div{parent=root,x=2,y=1,height=3,fg_bg=s_hi_bright}
|
||||||
@@ -43,9 +45,9 @@ local function init(parent, id)
|
|||||||
local pkt_fw_v = TextBox{parent=entry,x=14,y=2,text=" ------- ",width=20,fg_bg=label_fg}
|
local pkt_fw_v = TextBox{parent=entry,x=14,y=2,text=" ------- ",width=20,fg_bg=label_fg}
|
||||||
pkt_fw_v.register(ps, ps_prefix .. "fw", pkt_fw_v.set_value)
|
pkt_fw_v.register(ps, ps_prefix .. "fw", pkt_fw_v.set_value)
|
||||||
|
|
||||||
TextBox{parent=entry,x=35,y=2,text="RTT:",width=4}
|
TextBox{parent=entry,x=term_w-16,y=2,text="RTT:",width=4}
|
||||||
local pkt_rtt = DataIndicator{parent=entry,x=40,y=2,label="",unit="",format="%5d",value=0,width=5,fg_bg=label_fg}
|
local pkt_rtt = DataIndicator{parent=entry,x=term_w-11,y=2,label="",unit="",format="%5d",value=0,width=5,fg_bg=label_fg}
|
||||||
TextBox{parent=entry,x=46,y=2,text="ms",width=4,fg_bg=label_fg}
|
TextBox{parent=entry,x=term_w-5,y=2,text="ms",width=4,fg_bg=label_fg}
|
||||||
pkt_rtt.register(ps, ps_prefix .. "rtt", pkt_rtt.update)
|
pkt_rtt.register(ps, ps_prefix .. "rtt", pkt_rtt.update)
|
||||||
pkt_rtt.register(ps, ps_prefix .. "rtt_color", pkt_rtt.recolor)
|
pkt_rtt.register(ps, ps_prefix .. "rtt_color", pkt_rtt.recolor)
|
||||||
|
|
||||||
|
|||||||
@@ -94,14 +94,14 @@ local function new_view(root, x, y)
|
|||||||
main.line_break()
|
main.line_break()
|
||||||
|
|
||||||
local auto_scram = IndicatorLight{parent=main,label="Automatic SCRAM",colors=ind_red,flash=true,period=period.BLINK_250_MS}
|
local auto_scram = IndicatorLight{parent=main,label="Automatic SCRAM",colors=ind_red,flash=true,period=period.BLINK_250_MS}
|
||||||
local matrix_dc = IndicatorLight{parent=main,label="Matrix Disconnected",colors=ind_yel,flash=true,period=period.BLINK_500_MS}
|
local matrix_flt = IndicatorLight{parent=main,label="Induction Matrix Fault",colors=ind_yel,flash=true,period=period.BLINK_500_MS}
|
||||||
local matrix_fill = IndicatorLight{parent=main,label="Matrix Charge High",colors=ind_red,flash=true,period=period.BLINK_500_MS}
|
local matrix_fill = IndicatorLight{parent=main,label="Matrix Charge High",colors=ind_red,flash=true,period=period.BLINK_500_MS}
|
||||||
local unit_crit = IndicatorLight{parent=main,label="Unit Critical Alarm",colors=ind_red,flash=true,period=period.BLINK_250_MS}
|
local unit_crit = IndicatorLight{parent=main,label="Unit Critical Alarm",colors=ind_red,flash=true,period=period.BLINK_250_MS}
|
||||||
local fac_rad_h = IndicatorLight{parent=main,label="Facility Radiation High",colors=ind_red,flash=true,period=period.BLINK_250_MS}
|
local fac_rad_h = IndicatorLight{parent=main,label="Facility Radiation High",colors=ind_red,flash=true,period=period.BLINK_250_MS}
|
||||||
local gen_fault = IndicatorLight{parent=main,label="Gen. Control Fault",colors=ind_yel,flash=true,period=period.BLINK_500_MS}
|
local gen_fault = IndicatorLight{parent=main,label="Gen. Control Fault",colors=ind_yel,flash=true,period=period.BLINK_500_MS}
|
||||||
|
|
||||||
auto_scram.register(facility.ps, "auto_scram", auto_scram.update)
|
auto_scram.register(facility.ps, "auto_scram", auto_scram.update)
|
||||||
matrix_dc.register(facility.ps, "as_matrix_dc", matrix_dc.update)
|
matrix_flt.register(facility.ps, "as_matrix_fault", matrix_flt.update)
|
||||||
matrix_fill.register(facility.ps, "as_matrix_fill", matrix_fill.update)
|
matrix_fill.register(facility.ps, "as_matrix_fill", matrix_fill.update)
|
||||||
unit_crit.register(facility.ps, "as_crit_alarm", unit_crit.update)
|
unit_crit.register(facility.ps, "as_crit_alarm", unit_crit.update)
|
||||||
fac_rad_h.register(facility.ps, "as_radiation", fac_rad_h.update)
|
fac_rad_h.register(facility.ps, "as_radiation", fac_rad_h.update)
|
||||||
@@ -325,7 +325,7 @@ local function new_view(root, x, y)
|
|||||||
|
|
||||||
TextBox{parent=waste_status,y=i,text="U"..i.." Waste",width=8}
|
TextBox{parent=waste_status,y=i,text="U"..i.." Waste",width=8}
|
||||||
local a_waste = IndicatorLight{parent=waste_status,x=10,y=i,label="Auto",colors=ind_wht}
|
local a_waste = IndicatorLight{parent=waste_status,x=10,y=i,label="Auto",colors=ind_wht}
|
||||||
local waste_m = StateIndicator{parent=waste_status,x=17,y=i,states=style.waste.states_abbrv,value=1,min_width=6}
|
local waste_m = StateIndicator{parent=waste_status,x=17,y=i,states=style.get_waste().states_abbrv,value=1,min_width=6}
|
||||||
|
|
||||||
a_waste.register(unit.unit_ps, "U_AutoWaste", a_waste.update)
|
a_waste.register(unit.unit_ps, "U_AutoWaste", a_waste.update)
|
||||||
waste_m.register(unit.unit_ps, "U_WasteProduct", waste_m.update)
|
waste_m.register(unit.unit_ps, "U_WasteProduct", waste_m.update)
|
||||||
@@ -339,11 +339,11 @@ local function new_view(root, x, y)
|
|||||||
TextBox{parent=waste_sel,text="WASTE PRODUCTION",alignment=ALIGN.CENTER,width=21,x=1,y=2,fg_bg=cutout_fg_bg}
|
TextBox{parent=waste_sel,text="WASTE PRODUCTION",alignment=ALIGN.CENTER,width=21,x=1,y=2,fg_bg=cutout_fg_bg}
|
||||||
|
|
||||||
local rect = Rectangle{parent=waste_sel,border=border(1,colors.brown,true),width=21,height=22,x=1,y=3}
|
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}
|
local status = StateIndicator{parent=rect,x=2,y=1,states=style.get_waste().states,value=1,min_width=17}
|
||||||
|
|
||||||
status.register(facility.ps, "current_waste_product", status.update)
|
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(style.theme.accent_dark,style.theme.accent_light),select_color=colors.brown}
|
local waste_prod = RadioButton{parent=rect,x=2,y=3,options=style.get_waste().options,callback=process.set_process_waste,radio_colors=cpair(style.theme.accent_dark,style.theme.accent_light),select_color=colors.brown}
|
||||||
|
|
||||||
waste_prod.register(facility.ps, "process_waste_product", waste_prod.set_value)
|
waste_prod.register(facility.ps, "process_waste_product", waste_prod.set_value)
|
||||||
|
|
||||||
|
|||||||
@@ -398,7 +398,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=style.waste.unit_opts,callback=unit.set_waste,min_width=6}
|
local waste_mode = MultiButton{parent=waste_div,x=1,y=1,options=style.get_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)
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,11 @@
|
|||||||
-- Basic Unit Flow Overview
|
-- Basic Unit Flow Overview
|
||||||
--
|
--
|
||||||
|
|
||||||
|
local types = require("scada-common.types")
|
||||||
local util = require("scada-common.util")
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
|
local iocontrol = require("coordinator.iocontrol")
|
||||||
|
|
||||||
local style = require("coordinator.ui.style")
|
local style = require("coordinator.ui.style")
|
||||||
|
|
||||||
local core = require("graphics.core")
|
local core = require("graphics.core")
|
||||||
@@ -19,6 +22,8 @@ local DataIndicator = require("graphics.elements.indicators.DataIndicator")
|
|||||||
local IndicatorLight = require("graphics.elements.indicators.IndicatorLight")
|
local IndicatorLight = require("graphics.elements.indicators.IndicatorLight")
|
||||||
local TriIndicatorLight = require("graphics.elements.indicators.TriIndicatorLight")
|
local TriIndicatorLight = require("graphics.elements.indicators.TriIndicatorLight")
|
||||||
|
|
||||||
|
local COOLANT_TYPE = types.COOLANT_TYPE
|
||||||
|
|
||||||
local ALIGN = core.ALIGN
|
local ALIGN = core.ALIGN
|
||||||
|
|
||||||
local sprintf = util.sprintf
|
local sprintf = util.sprintf
|
||||||
@@ -35,8 +40,8 @@ local lg_gray = style.lg_gray
|
|||||||
---@param x integer top left x
|
---@param x integer top left x
|
||||||
---@param y integer top left y
|
---@param y integer top left y
|
||||||
---@param wide boolean whether to render wide version
|
---@param wide boolean whether to render wide version
|
||||||
---@param unit ioctl_unit unit database entry
|
---@param unit_id integer unit index
|
||||||
local function make(parent, x, y, wide, unit)
|
local function make(parent, x, y, wide, unit_id)
|
||||||
local s_field = style.theme.field_box
|
local s_field = style.theme.field_box
|
||||||
|
|
||||||
local text_c = style.text_colors
|
local text_c = style.text_colors
|
||||||
@@ -48,7 +53,13 @@ local function make(parent, x, y, wide, unit)
|
|||||||
|
|
||||||
local height = 16
|
local height = 16
|
||||||
|
|
||||||
local v_start = 1 + ((unit.unit_id - 1) * 5)
|
local facility = iocontrol.get_db().facility
|
||||||
|
local unit = iocontrol.get_db().units[unit_id]
|
||||||
|
|
||||||
|
local tank_conns = facility.tank_conns
|
||||||
|
local tank_types = facility.tank_fluid_types
|
||||||
|
|
||||||
|
local v_start = 1 + ((unit.unit_id - 1) * 6)
|
||||||
local prv_start = 1 + ((unit.unit_id - 1) * 3)
|
local prv_start = 1 + ((unit.unit_id - 1) * 3)
|
||||||
local v_fields = { "pu", "po", "pl", "am" }
|
local v_fields = { "pu", "po", "pl", "am" }
|
||||||
local v_names = {
|
local v_names = {
|
||||||
@@ -80,21 +91,32 @@ local function make(parent, x, y, wide, unit)
|
|||||||
|
|
||||||
local rc_pipes = {}
|
local rc_pipes = {}
|
||||||
|
|
||||||
local emc_x = 42 -- emergency coolant connection x point
|
|
||||||
|
|
||||||
if unit.num_boilers > 0 then
|
if unit.num_boilers > 0 then
|
||||||
table.insert(rc_pipes, pipe(0, 1, _wide(28, 19), 1, colors.lightBlue, true))
|
table.insert(rc_pipes, pipe(0, 1, _wide(28, 19), 1, colors.lightBlue, true))
|
||||||
table.insert(rc_pipes, pipe(0, 3, _wide(28, 19), 3, colors.orange, true))
|
table.insert(rc_pipes, pipe(0, 3, _wide(28, 19), 3, colors.orange, true))
|
||||||
table.insert(rc_pipes, pipe(_wide(46 ,39), 1, _wide(72,58), 1, colors.blue, true))
|
table.insert(rc_pipes, pipe(_wide(46, 39), 1, _wide(72, 58), 1, colors.blue, true))
|
||||||
table.insert(rc_pipes, pipe(_wide(46,39), 3, _wide(72,58), 3, colors.white, true))
|
table.insert(rc_pipes, pipe(_wide(46, 39), 3, _wide(72, 58), 3, colors.white, true))
|
||||||
|
|
||||||
|
if unit.aux_coolant then
|
||||||
|
local em_water = facility.tank_fluid_types[facility.tank_conns[unit_id]] == COOLANT_TYPE.WATER
|
||||||
|
local offset = util.trinary(unit.has_tank and em_water, 3, 0)
|
||||||
|
table.insert(rc_pipes, pipe(_wide(51, 41) + offset, 0, _wide(51, 41) + offset, 0, colors.blue, true))
|
||||||
|
end
|
||||||
else
|
else
|
||||||
emc_x = 3
|
table.insert(rc_pipes, pipe(0, 1, _wide(72, 58), 1, colors.blue, true))
|
||||||
table.insert(rc_pipes, pipe(0, 1, _wide(72,58), 1, colors.blue, true))
|
table.insert(rc_pipes, pipe(0, 3, _wide(72, 58), 3, colors.white, true))
|
||||||
table.insert(rc_pipes, pipe(0, 3, _wide(72,58), 3, colors.white, true))
|
|
||||||
|
if unit.aux_coolant then
|
||||||
|
table.insert(rc_pipes, pipe(8, 0, 8, 0, colors.blue, true))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if unit.has_tank then
|
if unit.has_tank then
|
||||||
table.insert(rc_pipes, pipe(emc_x, 1, emc_x, 0, colors.blue, true, true))
|
local is_water = tank_types[tank_conns[unit_id]] == COOLANT_TYPE.WATER
|
||||||
|
-- emergency coolant connection x point
|
||||||
|
local emc_x = util.trinary(is_water and (unit.num_boilers > 0), 42, 3)
|
||||||
|
|
||||||
|
table.insert(rc_pipes, pipe(emc_x, 1, emc_x, 0, util.trinary(is_water, colors.blue, colors.lightBlue), true, true))
|
||||||
end
|
end
|
||||||
|
|
||||||
local prv_yo = math.max(3 - unit.num_turbines, 0)
|
local prv_yo = math.max(3 - unit.num_turbines, 0)
|
||||||
@@ -157,12 +179,12 @@ local function make(parent, x, y, wide, unit)
|
|||||||
pipe(_wide(22, 19), 1, _wide(49, 45), 1, colors.brown, true),
|
pipe(_wide(22, 19), 1, _wide(49, 45), 1, colors.brown, true),
|
||||||
pipe(_wide(22, 19), 5, _wide(28, 24), 5, colors.brown, true),
|
pipe(_wide(22, 19), 5, _wide(28, 24), 5, colors.brown, true),
|
||||||
|
|
||||||
pipe(_wide(64, 53), 1, _wide(95, 81), 1, colors.green, true),
|
pipe(_wide(64, 53), 1, _wide(95, 81), 1, colors.cyan, true),
|
||||||
|
|
||||||
pipe(_wide(48, 43), 4, _wide(71, 61), 4, colors.cyan, true),
|
pipe(_wide(48, 43), 4, _wide(71, 61), 4, colors.green, true),
|
||||||
pipe(_wide(66, 57), 4, _wide(71, 61), 8, colors.cyan, true),
|
pipe(_wide(66, 57), 4, _wide(71, 61), 8, colors.green, true),
|
||||||
pipe(_wide(74, 63), 4, _wide(95, 81), 4, colors.cyan, true),
|
pipe(_wide(74, 63), 4, _wide(95, 81), 4, colors.green, true),
|
||||||
pipe(_wide(74, 63), 8, _wide(133, 111), 8, colors.cyan, true),
|
pipe(_wide(74, 63), 8, _wide(133, 111), 8, colors.green, true),
|
||||||
|
|
||||||
pipe(_wide(108, 94), 1, _wide(132, 110), 6, waste_c, true, true),
|
pipe(_wide(108, 94), 1, _wide(132, 110), 6, waste_c, true, true),
|
||||||
pipe(_wide(108, 94), 4, _wide(111, 95), 1, waste_c, true, true),
|
pipe(_wide(108, 94), 4, _wide(111, 95), 1, waste_c, true, true),
|
||||||
@@ -210,17 +232,21 @@ local function make(parent, x, y, wide, unit)
|
|||||||
_machine(_wide(116, 94), 6, "SPENT WASTE \x1b")
|
_machine(_wide(116, 94), 6, "SPENT WASTE \x1b")
|
||||||
|
|
||||||
TextBox{parent=waste,x=_wide(30,25),y=3,text="SNAs [Po]",alignment=ALIGN.CENTER,width=19,fg_bg=wh_gray}
|
TextBox{parent=waste,x=_wide(30,25),y=3,text="SNAs [Po]",alignment=ALIGN.CENTER,width=19,fg_bg=wh_gray}
|
||||||
local sna_po = Rectangle{parent=waste,x=_wide(30,25),y=4,border=border(1,colors.gray,true),width=19,height=7,thin=true,fg_bg=style.theme.highlight_box_bright}
|
local sna_po = Rectangle{parent=waste,x=_wide(30,25),y=4,border=border(1,colors.gray,true),width=19,height=8,thin=true,fg_bg=style.theme.highlight_box_bright}
|
||||||
local sna_act = IndicatorLight{parent=sna_po,label="ACTIVE",colors=ind_grn}
|
local sna_act = IndicatorLight{parent=sna_po,label="ACTIVE",colors=ind_grn}
|
||||||
local sna_cnt = DataIndicator{parent=sna_po,x=12,y=1,lu_colors=lu_c_d,label="CNT",unit="",format="%2d",value=0,width=7}
|
local sna_cnt = DataIndicator{parent=sna_po,x=12,y=1,lu_colors=lu_c_d,label="CNT",unit="",format="%2d",value=0,width=7}
|
||||||
local sna_pk = DataIndicator{parent=sna_po,y=3,lu_colors=lu_c_d,label="PEAK",unit="mB/t",format="%7.2f",value=0,width=17}
|
TextBox{parent=sna_po,y=3,text="PEAK\x1a",width=5,fg_bg=cpair(style.theme.label_dark,colors._INHERIT)}
|
||||||
local sna_max = DataIndicator{parent=sna_po,lu_colors=lu_c_d,label="MAX",unit="mB/t",format="%8.2f",value=0,width=17}
|
TextBox{parent=sna_po,text="MAX \x1a",width=5,fg_bg=cpair(style.theme.label_dark,colors._INHERIT)}
|
||||||
local sna_in = DataIndicator{parent=sna_po,lu_colors=lu_c_d,label="IN",unit="mB/t",format="%9.2f",value=0,width=17}
|
local sna_pk = DataIndicator{parent=sna_po,x=6,y=3,lu_colors=lu_c_d,label="",unit="mB/t",format="%7.2f",value=0,width=17}
|
||||||
|
local sna_max_o = DataIndicator{parent=sna_po,x=6,lu_colors=lu_c_d,label="",unit="mB/t",format="%7.2f",value=0,width=17}
|
||||||
|
local sna_max_i = DataIndicator{parent=sna_po,lu_colors=lu_c_d,label="\x1aMAX",unit="mB/t",format="%7.2f",value=0,width=17}
|
||||||
|
local sna_in = DataIndicator{parent=sna_po,lu_colors=lu_c_d,label="\x1aIN",unit="mB/t",format="%8.2f",value=0,width=17}
|
||||||
|
|
||||||
sna_act.register(unit.unit_ps, "po_rate", function (r) sna_act.update(r > 0) end)
|
sna_act.register(unit.unit_ps, "po_rate", function (r) sna_act.update(r > 0) end)
|
||||||
sna_cnt.register(unit.unit_ps, "sna_count", sna_cnt.update)
|
sna_cnt.register(unit.unit_ps, "sna_count", sna_cnt.update)
|
||||||
sna_pk.register(unit.unit_ps, "sna_peak_rate", sna_pk.update)
|
sna_pk.register(unit.unit_ps, "sna_peak_rate", sna_pk.update)
|
||||||
sna_max.register(unit.unit_ps, "sna_max_rate", sna_max.update)
|
sna_max_o.register(unit.unit_ps, "sna_max_rate", sna_max_o.update)
|
||||||
|
sna_max_i.register(unit.unit_ps, "sna_max_rate", function (r) sna_max_i.update(r * 10) end)
|
||||||
sna_in.register(unit.unit_ps, "sna_in", sna_in.update)
|
sna_in.register(unit.unit_ps, "sna_in", sna_in.update)
|
||||||
|
|
||||||
return root
|
return root
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ local IndicatorLight = require("graphics.elements.indicators.IndicatorLight")
|
|||||||
local StateIndicator = require("graphics.elements.indicators.StateIndicator")
|
local StateIndicator = require("graphics.elements.indicators.StateIndicator")
|
||||||
|
|
||||||
local CONTAINER_MODE = types.CONTAINER_MODE
|
local CONTAINER_MODE = types.CONTAINER_MODE
|
||||||
|
local COOLANT_TYPE = types.COOLANT_TYPE
|
||||||
|
|
||||||
local ALIGN = core.ALIGN
|
local ALIGN = core.ALIGN
|
||||||
|
|
||||||
@@ -45,8 +46,10 @@ local function init(main)
|
|||||||
local facility = iocontrol.get_db().facility
|
local facility = iocontrol.get_db().facility
|
||||||
local units = iocontrol.get_db().units
|
local units = iocontrol.get_db().units
|
||||||
|
|
||||||
local tank_defs = facility.tank_defs
|
local tank_defs = facility.tank_defs
|
||||||
local tank_list = facility.tank_list
|
local tank_conns = facility.tank_conns
|
||||||
|
local tank_list = facility.tank_list
|
||||||
|
local tank_types = facility.tank_fluid_types
|
||||||
|
|
||||||
-- window header message
|
-- window header message
|
||||||
local header = TextBox{parent=main,y=1,text="Facility Coolant and Waste Flow Monitor",alignment=ALIGN.CENTER,fg_bg=style.theme.header}
|
local header = TextBox{parent=main,y=1,text="Facility Coolant and Waste Flow Monitor",alignment=ALIGN.CENTER,fg_bg=style.theme.header}
|
||||||
@@ -56,12 +59,16 @@ local function init(main)
|
|||||||
datetime.register(facility.ps, "date_time", datetime.set_value)
|
datetime.register(facility.ps, "date_time", datetime.set_value)
|
||||||
|
|
||||||
local po_pipes = {}
|
local po_pipes = {}
|
||||||
local water_pipes = {}
|
local emcool_pipes = {}
|
||||||
|
|
||||||
-- get the y offset for this unit index
|
-- get the y offset for this unit index
|
||||||
---@param idx integer unit index
|
---@param idx integer unit index
|
||||||
local function y_ofs(idx) return ((idx - 1) * 20) end
|
local function y_ofs(idx) return ((idx - 1) * 20) end
|
||||||
|
|
||||||
|
-- get the coolant color
|
||||||
|
---@param idx integer tank index
|
||||||
|
local function c_clr(idx) return util.trinary(tank_types[tank_conns[idx]] == COOLANT_TYPE.WATER, colors.blue, colors.lightBlue) end
|
||||||
|
|
||||||
-- determinte facility tank start/end from the definitions list
|
-- determinte facility tank start/end from the definitions list
|
||||||
---@param start_idx integer start index of table iteration
|
---@param start_idx integer start index of table iteration
|
||||||
---@param end_idx integer end index of table iteration
|
---@param end_idx integer end index of table iteration
|
||||||
@@ -81,11 +88,13 @@ local function init(main)
|
|||||||
for i = 1, facility.num_units do
|
for i = 1, facility.num_units do
|
||||||
if units[i].has_tank then
|
if units[i].has_tank then
|
||||||
local y = y_ofs(i)
|
local y = y_ofs(i)
|
||||||
table.insert(water_pipes, pipe(2, y, 2, y + 3, colors.blue, true))
|
local color = c_clr(i)
|
||||||
table.insert(water_pipes, pipe(2, y, 21, y, colors.blue, true))
|
|
||||||
|
|
||||||
local x = util.trinary(units[i].num_boilers == 0, 45, 84)
|
table.insert(emcool_pipes, pipe(2, y, 2, y + 3, color, true))
|
||||||
table.insert(water_pipes, pipe(21, y, x, y + 2, colors.blue, true, true))
|
table.insert(emcool_pipes, pipe(2, y, 21, y, color, true))
|
||||||
|
|
||||||
|
local x = util.trinary((tank_types[tank_conns[i]] == COOLANT_TYPE.SODIUM) or (units[i].num_boilers == 0), 45, 84)
|
||||||
|
table.insert(emcool_pipes, pipe(21, y, x, y + 2, color, true, true))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
@@ -93,16 +102,17 @@ local function init(main)
|
|||||||
for i = 1, #tank_defs do
|
for i = 1, #tank_defs do
|
||||||
if tank_defs[i] > 0 then
|
if tank_defs[i] > 0 then
|
||||||
local y = y_ofs(i)
|
local y = y_ofs(i)
|
||||||
|
local color = c_clr(i)
|
||||||
|
|
||||||
if tank_defs[i] == 2 then
|
if tank_defs[i] == 2 then
|
||||||
table.insert(water_pipes, pipe(1, y, 21, y, colors.blue, true))
|
table.insert(emcool_pipes, pipe(1, y, 21, y, color, true))
|
||||||
else
|
else
|
||||||
table.insert(water_pipes, pipe(2, y, 2, y + 3, colors.blue, true))
|
table.insert(emcool_pipes, pipe(2, y, 2, y + 3, color, true))
|
||||||
table.insert(water_pipes, pipe(2, y, 21, y, colors.blue, true))
|
table.insert(emcool_pipes, pipe(2, y, 21, y, color, true))
|
||||||
end
|
end
|
||||||
|
|
||||||
local x = util.trinary(units[i].num_boilers == 0, 45, 84)
|
local x = util.trinary((tank_types[tank_conns[i]] == COOLANT_TYPE.SODIUM) or (units[i].num_boilers == 0), 45, 84)
|
||||||
table.insert(water_pipes, pipe(21, y, x, y + 2, colors.blue, true, true))
|
table.insert(emcool_pipes, pipe(21, y, x, y + 2, color, true, true))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -112,13 +122,14 @@ local function init(main)
|
|||||||
|
|
||||||
for i = 1, #tank_defs do
|
for i = 1, #tank_defs do
|
||||||
local y = y_ofs(i)
|
local y = y_ofs(i)
|
||||||
|
|
||||||
if i == first_fdef then
|
if i == first_fdef then
|
||||||
table.insert(water_pipes, pipe(0, y, 1, y + 5, colors.blue, true))
|
table.insert(emcool_pipes, pipe(0, y, 1, y + 5, c_clr(i), true))
|
||||||
elseif i > first_fdef then
|
elseif i > first_fdef then
|
||||||
if i == last_fdef then
|
if i == last_fdef then
|
||||||
table.insert(water_pipes, pipe(0, y - 14, 0, y, colors.blue, true))
|
table.insert(emcool_pipes, pipe(0, y - 14, 0, y, c_clr(first_fdef), true))
|
||||||
elseif i < last_fdef then
|
elseif i < last_fdef then
|
||||||
table.insert(water_pipes, pipe(0, y - 14, 0, y + 5, colors.blue, true))
|
table.insert(emcool_pipes, pipe(0, y - 14, 0, y + 5, c_clr(first_fdef), true))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -128,17 +139,19 @@ local function init(main)
|
|||||||
|
|
||||||
for i = 1, #tank_defs do
|
for i = 1, #tank_defs do
|
||||||
local y = y_ofs(i)
|
local y = y_ofs(i)
|
||||||
|
local color = c_clr(first_fdef)
|
||||||
|
|
||||||
if i == 4 then
|
if i == 4 then
|
||||||
if tank_defs[i] == 2 then
|
if tank_defs[i] == 2 then
|
||||||
table.insert(water_pipes, pipe(0, y, 1, y + 5, colors.blue, true))
|
table.insert(emcool_pipes, pipe(0, y, 1, y + 5, c_clr(i), true))
|
||||||
end
|
end
|
||||||
elseif i == first_fdef then
|
elseif i == first_fdef then
|
||||||
table.insert(water_pipes, pipe(0, y, 1, y + 5, colors.blue, true))
|
table.insert(emcool_pipes, pipe(0, y, 1, y + 5, color, true))
|
||||||
elseif i > first_fdef then
|
elseif i > first_fdef then
|
||||||
if i == last_fdef then
|
if i == last_fdef then
|
||||||
table.insert(water_pipes, pipe(0, y - 14, 0, y, colors.blue, true))
|
table.insert(emcool_pipes, pipe(0, y - 14, 0, y, color, true))
|
||||||
elseif i < last_fdef then
|
elseif i < last_fdef then
|
||||||
table.insert(water_pipes, pipe(0, y - 14, 0, y + 5, colors.blue, true))
|
table.insert(emcool_pipes, pipe(0, y - 14, 0, y + 5, color, true))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -147,12 +160,12 @@ local function init(main)
|
|||||||
for _, a in pairs({ 1, 3 }) do
|
for _, a in pairs({ 1, 3 }) do
|
||||||
local b = a + 1
|
local b = a + 1
|
||||||
if tank_defs[a] == 2 then
|
if tank_defs[a] == 2 then
|
||||||
table.insert(water_pipes, pipe(0, y_ofs(a), 1, y_ofs(a) + 6, colors.blue, true))
|
table.insert(emcool_pipes, pipe(0, y_ofs(a), 1, y_ofs(a) + 6, c_clr(a), true))
|
||||||
if tank_defs[b] == 2 then
|
if tank_defs[b] == 2 then
|
||||||
table.insert(water_pipes, pipe(0, y_ofs(b) - 13, 1, y_ofs(b), colors.blue, true))
|
table.insert(emcool_pipes, pipe(0, y_ofs(b) - 13, 1, y_ofs(b), c_clr(a), true))
|
||||||
end
|
end
|
||||||
elseif tank_defs[b] == 2 then
|
elseif tank_defs[b] == 2 then
|
||||||
table.insert(water_pipes, pipe(0, y_ofs(b), 1, y_ofs(b) + 6, colors.blue, true))
|
table.insert(emcool_pipes, pipe(0, y_ofs(b), 1, y_ofs(b) + 6, c_clr(b), true))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
elseif facility.tank_mode == 4 then
|
elseif facility.tank_mode == 4 then
|
||||||
@@ -161,17 +174,19 @@ local function init(main)
|
|||||||
|
|
||||||
for i = 1, #tank_defs do
|
for i = 1, #tank_defs do
|
||||||
local y = y_ofs(i)
|
local y = y_ofs(i)
|
||||||
|
local color = c_clr(first_fdef)
|
||||||
|
|
||||||
if i == 1 then
|
if i == 1 then
|
||||||
if tank_defs[i] == 2 then
|
if tank_defs[i] == 2 then
|
||||||
table.insert(water_pipes, pipe(0, y, 1, y + 5, colors.blue, true))
|
table.insert(emcool_pipes, pipe(0, y, 1, y + 5, c_clr(i), true))
|
||||||
end
|
end
|
||||||
elseif i == first_fdef then
|
elseif i == first_fdef then
|
||||||
table.insert(water_pipes, pipe(0, y, 1, y + 5, colors.blue, true))
|
table.insert(emcool_pipes, pipe(0, y, 1, y + 5, color, true))
|
||||||
elseif i > first_fdef then
|
elseif i > first_fdef then
|
||||||
if i == last_fdef then
|
if i == last_fdef then
|
||||||
table.insert(water_pipes, pipe(0, y - 14, 0, y, colors.blue, true))
|
table.insert(emcool_pipes, pipe(0, y - 14, 0, y, color, true))
|
||||||
elseif i < last_fdef then
|
elseif i < last_fdef then
|
||||||
table.insert(water_pipes, pipe(0, y - 14, 0, y + 5, colors.blue, true))
|
table.insert(emcool_pipes, pipe(0, y - 14, 0, y + 5, color, true))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -181,17 +196,19 @@ local function init(main)
|
|||||||
|
|
||||||
for i = 1, #tank_defs do
|
for i = 1, #tank_defs do
|
||||||
local y = y_ofs(i)
|
local y = y_ofs(i)
|
||||||
|
local color = c_clr(first_fdef)
|
||||||
|
|
||||||
if i == 3 or i == 4 then
|
if i == 3 or i == 4 then
|
||||||
if tank_defs[i] == 2 then
|
if tank_defs[i] == 2 then
|
||||||
table.insert(water_pipes, pipe(0, y, 1, y + 5, colors.blue, true))
|
table.insert(emcool_pipes, pipe(0, y, 1, y + 5, c_clr(i), true))
|
||||||
end
|
end
|
||||||
elseif i == first_fdef then
|
elseif i == first_fdef then
|
||||||
table.insert(water_pipes, pipe(0, y, 1, y + 5, colors.blue, true))
|
table.insert(emcool_pipes, pipe(0, y, 1, y + 5, color, true))
|
||||||
elseif i > first_fdef then
|
elseif i > first_fdef then
|
||||||
if i == last_fdef then
|
if i == last_fdef then
|
||||||
table.insert(water_pipes, pipe(0, y - 14, 0, y, colors.blue, true))
|
table.insert(emcool_pipes, pipe(0, y - 14, 0, y, color, true))
|
||||||
elseif i < last_fdef then
|
elseif i < last_fdef then
|
||||||
table.insert(water_pipes, pipe(0, y - 14, 0, y + 5, colors.blue, true))
|
table.insert(emcool_pipes, pipe(0, y - 14, 0, y + 5, color, true))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -201,17 +218,19 @@ local function init(main)
|
|||||||
|
|
||||||
for i = 1, #tank_defs do
|
for i = 1, #tank_defs do
|
||||||
local y = y_ofs(i)
|
local y = y_ofs(i)
|
||||||
|
local color = c_clr(first_fdef)
|
||||||
|
|
||||||
if i == 1 or i == 4 then
|
if i == 1 or i == 4 then
|
||||||
if tank_defs[i] == 2 then
|
if tank_defs[i] == 2 then
|
||||||
table.insert(water_pipes, pipe(0, y, 1, y + 5, colors.blue, true))
|
table.insert(emcool_pipes, pipe(0, y, 1, y + 5, c_clr(i), true))
|
||||||
end
|
end
|
||||||
elseif i == first_fdef then
|
elseif i == first_fdef then
|
||||||
table.insert(water_pipes, pipe(0, y, 1, y + 5, colors.blue, true))
|
table.insert(emcool_pipes, pipe(0, y, 1, y + 5, color, true))
|
||||||
elseif i > first_fdef then
|
elseif i > first_fdef then
|
||||||
if i == last_fdef then
|
if i == last_fdef then
|
||||||
table.insert(water_pipes, pipe(0, y - 14, 0, y, colors.blue, true))
|
table.insert(emcool_pipes, pipe(0, y - 14, 0, y, color, true))
|
||||||
elseif i < last_fdef then
|
elseif i < last_fdef then
|
||||||
table.insert(water_pipes, pipe(0, y - 14, 0, y + 5, colors.blue, true))
|
table.insert(emcool_pipes, pipe(0, y - 14, 0, y + 5, color, true))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -221,17 +240,19 @@ local function init(main)
|
|||||||
|
|
||||||
for i = 1, #tank_defs do
|
for i = 1, #tank_defs do
|
||||||
local y = y_ofs(i)
|
local y = y_ofs(i)
|
||||||
|
local color = c_clr(first_fdef)
|
||||||
|
|
||||||
if i == 1 or i == 2 then
|
if i == 1 or i == 2 then
|
||||||
if tank_defs[i] == 2 then
|
if tank_defs[i] == 2 then
|
||||||
table.insert(water_pipes, pipe(0, y, 1, y + 5, colors.blue, true))
|
table.insert(emcool_pipes, pipe(0, y, 1, y + 5, c_clr(i), true))
|
||||||
end
|
end
|
||||||
elseif i == first_fdef then
|
elseif i == first_fdef then
|
||||||
table.insert(water_pipes, pipe(0, y, 1, y + 5, colors.blue, true))
|
table.insert(emcool_pipes, pipe(0, y, 1, y + 5, color, true))
|
||||||
elseif i > first_fdef then
|
elseif i > first_fdef then
|
||||||
if i == last_fdef then
|
if i == last_fdef then
|
||||||
table.insert(water_pipes, pipe(0, y - 14, 0, y, colors.blue, true))
|
table.insert(emcool_pipes, pipe(0, y - 14, 0, y, color, true))
|
||||||
elseif i < last_fdef then
|
elseif i < last_fdef then
|
||||||
table.insert(water_pipes, pipe(0, y - 14, 0, y + 5, colors.blue, true))
|
table.insert(emcool_pipes, pipe(0, y - 14, 0, y + 5, color, true))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -239,15 +260,15 @@ local function init(main)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local flow_x = 3
|
local flow_x = 3
|
||||||
if #water_pipes > 0 then
|
if #emcool_pipes > 0 then
|
||||||
flow_x = 25
|
flow_x = 25
|
||||||
PipeNetwork{parent=main,x=2,y=3,pipes=water_pipes,bg=style.theme.bg}
|
PipeNetwork{parent=main,x=2,y=3,pipes=emcool_pipes,bg=style.theme.bg}
|
||||||
end
|
end
|
||||||
|
|
||||||
for i = 1, facility.num_units do
|
for i = 1, facility.num_units do
|
||||||
local y_offset = y_ofs(i)
|
local y_offset = y_ofs(i)
|
||||||
unit_flow(main, flow_x, 5 + y_offset, #water_pipes == 0, units[i])
|
unit_flow(main, flow_x, 5 + y_offset, #emcool_pipes == 0, i)
|
||||||
table.insert(po_pipes, pipe(0, 3 + y_offset, 4, 0, colors.cyan, true, true))
|
table.insert(po_pipes, pipe(0, 3 + y_offset, 4, 0, colors.green, true, true))
|
||||||
util.nop()
|
util.nop()
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -265,7 +286,7 @@ local function init(main)
|
|||||||
|
|
||||||
TextBox{parent=main,x=12,y=vy,text="\x10\x11",fg_bg=text_col,width=2}
|
TextBox{parent=main,x=12,y=vy,text="\x10\x11",fg_bg=text_col,width=2}
|
||||||
|
|
||||||
local conn = IndicatorLight{parent=main,x=9,y=vy+1,label=util.sprintf("PV%02d-EMC", i * 5),colors=style.ind_grn}
|
local conn = IndicatorLight{parent=main,x=9,y=vy+1,label=util.sprintf("PV%02d-EMC", (i * 6) - 1),colors=style.ind_grn}
|
||||||
local open = IndicatorLight{parent=main,x=9,y=vy+2,label="OPEN",colors=style.ind_wht}
|
local open = IndicatorLight{parent=main,x=9,y=vy+2,label="OPEN",colors=style.ind_wht}
|
||||||
|
|
||||||
conn.register(units[i].unit_ps, "V_emc_conn", conn.update)
|
conn.register(units[i].unit_ps, "V_emc_conn", conn.update)
|
||||||
@@ -273,6 +294,35 @@ local function init(main)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
------------------------------
|
||||||
|
-- auxiliary coolant valves --
|
||||||
|
------------------------------
|
||||||
|
|
||||||
|
for i = 1, facility.num_units do
|
||||||
|
if units[i].aux_coolant then
|
||||||
|
local vx
|
||||||
|
local vy = 3 + y_ofs(i)
|
||||||
|
|
||||||
|
if #emcool_pipes == 0 then
|
||||||
|
vx = util.trinary(units[i].num_boilers == 0, 36, 79)
|
||||||
|
else
|
||||||
|
local em_water = tank_types[tank_conns[i]] == COOLANT_TYPE.WATER
|
||||||
|
vx = util.trinary(units[i].num_boilers == 0, 58, util.trinary(units[i].has_tank and em_water, 94, 91))
|
||||||
|
end
|
||||||
|
|
||||||
|
PipeNetwork{parent=main,x=vx-6,y=vy,pipes={pipe(0,1,9,0,colors.blue,true)},bg=style.theme.bg}
|
||||||
|
|
||||||
|
TextBox{parent=main,x=vx,y=vy,text="\x10\x11",fg_bg=text_col,width=2}
|
||||||
|
TextBox{parent=main,x=vx+5,y=vy,text="\x1b",fg_bg=cpair(colors.blue,text_col.bkg),width=1}
|
||||||
|
|
||||||
|
local conn = IndicatorLight{parent=main,x=vx-3,y=vy+1,label=util.sprintf("PV%02d-AUX", i * 6),colors=style.ind_grn}
|
||||||
|
local open = IndicatorLight{parent=main,x=vx-3,y=vy+2,label="OPEN",colors=style.ind_wht}
|
||||||
|
|
||||||
|
conn.register(units[i].unit_ps, "V_aux_conn", conn.update)
|
||||||
|
open.register(units[i].unit_ps, "V_aux_state", open.update)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-------------------
|
-------------------
|
||||||
-- dynamic tanks --
|
-- dynamic tanks --
|
||||||
-------------------
|
-------------------
|
||||||
@@ -301,8 +351,10 @@ local function init(main)
|
|||||||
local tank_pcnt = DataIndicator{parent=tank_box,x=10,y=3,label="",format="%5.2f",value=100,unit="%",lu_colors=lu_col,width=8,fg_bg=text_col}
|
local tank_pcnt = DataIndicator{parent=tank_box,x=10,y=3,label="",format="%5.2f",value=100,unit="%",lu_colors=lu_col,width=8,fg_bg=text_col}
|
||||||
local tank_amnt = DataIndicator{parent=tank_box,x=2,label="",format="%13d",value=0,commas=true,unit="mB",lu_colors=lu_col,width=16,fg_bg=s_field}
|
local tank_amnt = DataIndicator{parent=tank_box,x=2,label="",format="%13d",value=0,commas=true,unit="mB",lu_colors=lu_col,width=16,fg_bg=s_field}
|
||||||
|
|
||||||
TextBox{parent=tank_box,x=2,y=6,text="Water Level",width=11,fg_bg=style.label}
|
local is_water = tank_types[i] == COOLANT_TYPE.WATER
|
||||||
local level = HorizontalBar{parent=tank_box,x=2,y=7,bar_fg_bg=cpair(colors.blue,colors.gray),height=1,width=16}
|
|
||||||
|
TextBox{parent=tank_box,x=2,y=6,text=util.trinary(is_water,"Water","Sodium").." Level",width=12,fg_bg=style.label}
|
||||||
|
local level = HorizontalBar{parent=tank_box,x=2,y=7,bar_fg_bg=cpair(util.trinary(is_water,colors.blue,colors.lightBlue),colors.gray),height=1,width=16}
|
||||||
|
|
||||||
TextBox{parent=tank_box,x=2,y=9,text="In/Out Mode",width=11,fg_bg=style.label}
|
TextBox{parent=tank_box,x=2,y=9,text="In/Out Mode",width=11,fg_bg=style.label}
|
||||||
local can_fill = IndicatorLight{parent=tank_box,x=2,y=10,label="FILL",colors=style.ind_wht}
|
local can_fill = IndicatorLight{parent=tank_box,x=2,y=10,label="FILL",colors=style.ind_wht}
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ local led_grn = style.led_grn
|
|||||||
local function init(panel, num_units)
|
local function init(panel, num_units)
|
||||||
local ps = iocontrol.get_db().fp.ps
|
local ps = iocontrol.get_db().fp.ps
|
||||||
|
|
||||||
|
local term_w, term_h = term.getSize()
|
||||||
|
|
||||||
TextBox{parent=panel,y=1,text="SCADA COORDINATOR",alignment=ALIGN.CENTER,fg_bg=style.fp_theme.header}
|
TextBox{parent=panel,y=1,text="SCADA COORDINATOR",alignment=ALIGN.CENTER,fg_bg=style.fp_theme.header}
|
||||||
|
|
||||||
local page_div = Div{parent=panel,x=1,y=3}
|
local page_div = Div{parent=panel,x=1,y=3}
|
||||||
@@ -61,7 +63,7 @@ local function init(panel, num_units)
|
|||||||
local modem = LED{parent=system,label="MODEM",colors=led_grn}
|
local modem = LED{parent=system,label="MODEM",colors=led_grn}
|
||||||
|
|
||||||
if not style.colorblind then
|
if not style.colorblind then
|
||||||
local network = RGBLED{parent=system,label="NETWORK",colors={colors.green,colors.red,colors.orange,colors.yellow,style.fp_ind_bkg}}
|
local network = RGBLED{parent=system,label="NETWORK",colors={colors.green,colors.red,colors.yellow,colors.orange,style.fp_ind_bkg}}
|
||||||
network.update(types.PANEL_LINK_STATE.DISCONNECTED)
|
network.update(types.PANEL_LINK_STATE.DISCONNECTED)
|
||||||
network.register(ps, "link_state", network.update)
|
network.register(ps, "link_state", network.update)
|
||||||
else
|
else
|
||||||
@@ -131,9 +133,9 @@ local function init(panel, num_units)
|
|||||||
-- about footer
|
-- about footer
|
||||||
--
|
--
|
||||||
|
|
||||||
local about = Div{parent=main_page,width=15,height=3,x=1,y=16,fg_bg=style.fp.disabled_fg}
|
local about = Div{parent=main_page,width=15,height=2,y=term_h-3,fg_bg=style.fp.disabled_fg}
|
||||||
local fw_v = TextBox{parent=about,x=1,y=1,text="FW: v00.00.00"}
|
local fw_v = TextBox{parent=about,text="FW: v00.00.00"}
|
||||||
local comms_v = TextBox{parent=about,x=1,y=2,text="NT: v00.00.00"}
|
local comms_v = TextBox{parent=about,text="NT: v00.00.00"}
|
||||||
|
|
||||||
fw_v.register(ps, "version", function (version) fw_v.set_value(util.c("FW: ", version)) end)
|
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)
|
comms_v.register(ps, "comms_version", function (version) comms_v.set_value(util.c("NT: v", version)) end)
|
||||||
@@ -145,7 +147,7 @@ local function init(panel, num_units)
|
|||||||
-- API page
|
-- API page
|
||||||
|
|
||||||
local api_page = Div{parent=page_div,x=1,y=1,hidden=true}
|
local api_page = Div{parent=page_div,x=1,y=1,hidden=true}
|
||||||
local api_list = ListBox{parent=api_page,x=1,y=1,height=17,width=51,scroll_height=1000,fg_bg=style.fp.text_fg,nav_fg_bg=cpair(colors.gray,colors.lightGray),nav_active=cpair(colors.black,colors.gray)}
|
local api_list = ListBox{parent=api_page,y=1,height=term_h-2,width=term_w,scroll_height=1000,fg_bg=style.fp.text_fg,nav_fg_bg=cpair(colors.gray,colors.lightGray),nav_active=cpair(colors.black,colors.gray)}
|
||||||
local _ = Div{parent=api_list,height=1} -- padding
|
local _ = Div{parent=api_list,height=1} -- padding
|
||||||
|
|
||||||
-- assemble page panes
|
-- assemble page panes
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ local function init(main)
|
|||||||
|
|
||||||
util.nop()
|
util.nop()
|
||||||
|
|
||||||
imatrix(main, 131, cnc_bottom_align_start, facility.induction_data_tbl[1], facility.induction_ps_tbl[1])
|
imatrix(main, 131, cnc_bottom_align_start, facility.induction_ps_tbl[1])
|
||||||
end
|
end
|
||||||
|
|
||||||
return init
|
return init
|
||||||
|
|||||||
@@ -2,16 +2,20 @@
|
|||||||
-- Graphics Style Options
|
-- Graphics Style Options
|
||||||
--
|
--
|
||||||
|
|
||||||
local util = require("scada-common.util")
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
local core = require("graphics.core")
|
local core = require("graphics.core")
|
||||||
local themes = require("graphics.themes")
|
local themes = require("graphics.themes")
|
||||||
|
|
||||||
|
local coordinator = require("coordinator.coordinator")
|
||||||
|
|
||||||
---@class crd_style
|
---@class crd_style
|
||||||
local style = {}
|
local style = {}
|
||||||
|
|
||||||
local cpair = core.cpair
|
local cpair = core.cpair
|
||||||
|
|
||||||
|
local config = coordinator.config
|
||||||
|
|
||||||
-- front panel styling
|
-- front panel styling
|
||||||
|
|
||||||
style.fp_theme = themes.sandstone
|
style.fp_theme = themes.sandstone
|
||||||
@@ -147,236 +151,110 @@ style.gray_white = cpair(colors.gray, colors.white)
|
|||||||
-- UI COMPONENTS --
|
-- UI COMPONENTS --
|
||||||
|
|
||||||
style.reactor = {
|
style.reactor = {
|
||||||
-- reactor states
|
-- reactor states<br>
|
||||||
|
---@see REACTOR_STATE
|
||||||
states = {
|
states = {
|
||||||
{
|
{ color = cpair(colors.black, colors.yellow), text = "PLC OFF-LINE" },
|
||||||
color = cpair(colors.black, colors.yellow),
|
{ color = cpair(colors.black, colors.orange), text = "NOT FORMED" },
|
||||||
text = "PLC OFF-LINE"
|
{ color = cpair(colors.black, colors.orange), text = "PLC FAULT" },
|
||||||
},
|
{ color = cpair(colors.white, colors.gray), text = "DISABLED" },
|
||||||
{
|
{ color = cpair(colors.black, colors.green), text = "ACTIVE" },
|
||||||
color = cpair(colors.black, colors.orange),
|
{ color = cpair(colors.black, colors.red), text = "SCRAMMED" },
|
||||||
text = "NOT FORMED"
|
{ color = cpair(colors.black, colors.red), text = "FORCE DISABLED" }
|
||||||
},
|
|
||||||
{
|
|
||||||
color = cpair(colors.black, colors.orange),
|
|
||||||
text = "PLC FAULT"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color = cpair(colors.white, colors.gray),
|
|
||||||
text = "DISABLED"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color = cpair(colors.black, colors.green),
|
|
||||||
text = "ACTIVE"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color = cpair(colors.black, colors.red),
|
|
||||||
text = "SCRAMMED"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color = cpair(colors.black, colors.red),
|
|
||||||
text = "FORCE DISABLED"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
style.boiler = {
|
style.boiler = {
|
||||||
-- boiler states
|
-- boiler states<br>
|
||||||
|
---@see BOILER_STATE
|
||||||
states = {
|
states = {
|
||||||
{
|
{ color = cpair(colors.black, colors.yellow), text = "OFF-LINE" },
|
||||||
color = cpair(colors.black, colors.yellow),
|
{ color = cpair(colors.black, colors.orange), text = "NOT FORMED" },
|
||||||
text = "OFF-LINE"
|
{ color = cpair(colors.black, colors.orange), text = "RTU FAULT" },
|
||||||
},
|
{ color = cpair(colors.white, colors.gray), text = "IDLE" },
|
||||||
{
|
{ color = cpair(colors.black, colors.green), text = "ACTIVE" }
|
||||||
color = cpair(colors.black, colors.orange),
|
|
||||||
text = "NOT FORMED"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color = cpair(colors.black, colors.orange),
|
|
||||||
text = "RTU FAULT"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color = cpair(colors.white, colors.gray),
|
|
||||||
text = "IDLE"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color = cpair(colors.black, colors.green),
|
|
||||||
text = "ACTIVE"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
style.turbine = {
|
style.turbine = {
|
||||||
-- turbine states
|
-- turbine states<br>
|
||||||
|
---@see TURBINE_STATE
|
||||||
states = {
|
states = {
|
||||||
{
|
{ color = cpair(colors.black, colors.yellow), text = "OFF-LINE" },
|
||||||
color = cpair(colors.black, colors.yellow),
|
{ color = cpair(colors.black, colors.orange), text = "NOT FORMED" },
|
||||||
text = "OFF-LINE"
|
{ color = cpair(colors.black, colors.orange), text = "RTU FAULT" },
|
||||||
},
|
{ color = cpair(colors.white, colors.gray), text = "IDLE" },
|
||||||
{
|
{ color = cpair(colors.black, colors.green), text = "ACTIVE" },
|
||||||
color = cpair(colors.black, colors.orange),
|
{ color = cpair(colors.black, colors.red), text = "TRIP" }
|
||||||
text = "NOT FORMED"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color = cpair(colors.black, colors.orange),
|
|
||||||
text = "RTU FAULT"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color = cpair(colors.white, colors.gray),
|
|
||||||
text = "IDLE"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color = cpair(colors.black, colors.green),
|
|
||||||
text = "ACTIVE"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color = cpair(colors.black, colors.red),
|
|
||||||
text = "TRIP"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
style.imatrix = {
|
|
||||||
-- induction matrix 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.green),
|
|
||||||
text = "ONLINE"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color = cpair(colors.black, colors.yellow),
|
|
||||||
text = "LOW CHARGE"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color = cpair(colors.black, colors.yellow),
|
|
||||||
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.white, colors.gray),
|
|
||||||
text = "IDLE"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color = cpair(colors.black, colors.green),
|
|
||||||
text = "ACTIVE"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
style.dtank = {
|
style.dtank = {
|
||||||
-- dynamic tank states
|
-- dynamic tank states<br>
|
||||||
|
---@see TANK_STATE
|
||||||
states = {
|
states = {
|
||||||
{
|
{ color = cpair(colors.black, colors.yellow), text = "OFF-LINE" },
|
||||||
color = cpair(colors.black, colors.yellow),
|
{ color = cpair(colors.black, colors.orange), text = "NOT FORMED" },
|
||||||
text = "OFF-LINE"
|
{ color = cpair(colors.black, colors.orange), text = "RTU FAULT" },
|
||||||
},
|
{ color = cpair(colors.black, colors.green), text = "ONLINE" },
|
||||||
{
|
{ color = cpair(colors.black, colors.yellow), text = "LOW FILL" },
|
||||||
color = cpair(colors.black, colors.orange),
|
{ color = cpair(colors.black, colors.green), text = "FILLED" }
|
||||||
text = "NOT FORMED"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color = cpair(colors.black, colors.orange),
|
|
||||||
text = "RTU FAULT"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color = cpair(colors.black, colors.green),
|
|
||||||
text = "ONLINE"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color = cpair(colors.black, colors.yellow),
|
|
||||||
text = "LOW FILL"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color = cpair(colors.black, colors.green),
|
|
||||||
text = "FILLED"
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
style.waste = {
|
style.imatrix = {
|
||||||
-- auto waste processing states
|
-- induction matrix states<br>
|
||||||
|
---@see IMATRIX_STATE
|
||||||
states = {
|
states = {
|
||||||
{
|
{ color = cpair(colors.black, colors.yellow), text = "OFF-LINE" },
|
||||||
color = cpair(colors.black, colors.green),
|
{ color = cpair(colors.black, colors.orange), text = "NOT FORMED" },
|
||||||
text = "PLUTONIUM"
|
{ color = cpair(colors.black, colors.orange), text = "RTU FAULT" },
|
||||||
},
|
{ color = cpair(colors.black, colors.green), text = "ONLINE" },
|
||||||
{
|
{ color = cpair(colors.black, colors.yellow), text = "LOW CHARGE" },
|
||||||
color = cpair(colors.black, colors.cyan),
|
{ color = cpair(colors.black, colors.yellow), text = "HIGH CHARGE" }
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
style.sps = {
|
||||||
|
-- SPS states<br>
|
||||||
|
---@see SPS_STATE
|
||||||
|
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.white, colors.gray), text = "IDLE" },
|
||||||
|
{ color = cpair(colors.black, colors.green), text = "ACTIVE" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
-- get waste styling, which depends on the configuration
|
||||||
|
---@return { states: { color: color, text: string }, states_abbrv: { color: color, text: string }, options: string[], unit_opts: { text: string, fg_bg: cpair, active_fg_bg:cpair } }
|
||||||
|
function style.get_waste()
|
||||||
|
local pu_color = util.trinary(config.GreenPuPellet, colors.green, colors.cyan)
|
||||||
|
local po_color = util.trinary(config.GreenPuPellet, colors.cyan, colors.green)
|
||||||
|
|
||||||
|
return {
|
||||||
|
-- auto waste processing states
|
||||||
|
states = {
|
||||||
|
{ color = cpair(colors.black, pu_color), text = "PLUTONIUM" },
|
||||||
|
{ color = cpair(colors.black, po_color), text = "POLONIUM" },
|
||||||
|
{ color = cpair(colors.black, colors.purple), text = "ANTI MATTER" }
|
||||||
|
},
|
||||||
|
states_abbrv = {
|
||||||
|
{ color = cpair(colors.black, pu_color), text = "Pu" },
|
||||||
|
{ color = cpair(colors.black, po_color), 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, pu_color) },
|
||||||
|
{ text = "Po", fg_bg = cpair(colors.black, colors.lightGray), active_fg_bg = cpair(colors.black, po_color) },
|
||||||
|
{ text = "AM", fg_bg = cpair(colors.black, colors.lightGray), active_fg_bg = cpair(colors.black, colors.purple) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
return style
|
return style
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ local flasher = require("graphics.flasher")
|
|||||||
|
|
||||||
local core = {}
|
local core = {}
|
||||||
|
|
||||||
core.version = "2.4.5"
|
core.version = "2.4.7"
|
||||||
|
|
||||||
core.flasher = flasher
|
core.flasher = flasher
|
||||||
core.events = events
|
core.events = events
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
-- Generic Graphics Element
|
-- Generic Graphics Element
|
||||||
--
|
--
|
||||||
|
|
||||||
-- local log = require("scada-common.log")
|
|
||||||
local util = require("scada-common.util")
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
local core = require("graphics.core")
|
local core = require("graphics.core")
|
||||||
@@ -476,10 +475,7 @@ function element.new(args, constraint, child_offset_x, child_offset_y)
|
|||||||
|
|
||||||
if args.parent ~= nil then
|
if args.parent ~= nil then
|
||||||
-- remove self from parent
|
-- remove self from parent
|
||||||
-- log.debug("removing " .. self.id .. " from parent")
|
|
||||||
args.parent.__remove_child(self.id)
|
args.parent.__remove_child(self.id)
|
||||||
else
|
|
||||||
-- log.debug("no parent for " .. self.id .. " on delete attempt")
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -502,9 +498,12 @@ function element.new(args, constraint, child_offset_x, child_offset_y)
|
|||||||
self.next_id = self.next_id + 1
|
self.next_id = self.next_id + 1
|
||||||
end
|
end
|
||||||
|
|
||||||
table.insert(protected.children, child)
|
-- see #539 on GitHub
|
||||||
|
-- using #protected.children after inserting may give the wrong index, since if it inserts in a hole that completes the list then
|
||||||
|
-- the length will jump up to the full length of the list, possibly making two map entries point to the same child
|
||||||
|
protected.child_id_map[id] = #protected.children + 1
|
||||||
|
|
||||||
protected.child_id_map[id] = #protected.children
|
table.insert(protected.children, child)
|
||||||
|
|
||||||
return id
|
return id
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
-- Scroll-able List Box Display Graphics Element
|
-- Scroll-able List Box Display Graphics Element
|
||||||
|
|
||||||
-- local log = require("scada-common.log")
|
|
||||||
local tcd = require("scada-common.tcd")
|
local tcd = require("scada-common.tcd")
|
||||||
|
|
||||||
local core = require("graphics.core")
|
local core = require("graphics.core")
|
||||||
@@ -153,7 +152,6 @@ return function (args)
|
|||||||
next_y = next_y + item.h + item_pad
|
next_y = next_y + item.h + item_pad
|
||||||
item.e.reposition(1, item.y)
|
item.e.reposition(1, item.y)
|
||||||
item.e.show()
|
item.e.show()
|
||||||
-- log.debug("iterated " .. item.e.get_id())
|
|
||||||
end
|
end
|
||||||
|
|
||||||
content_height = next_y
|
content_height = next_y
|
||||||
@@ -212,7 +210,6 @@ return function (args)
|
|||||||
---@param child graphics_element child element
|
---@param child graphics_element child element
|
||||||
function e.on_added(id, child)
|
function e.on_added(id, child)
|
||||||
table.insert(list, { id = id, e = child, y = 0, h = child.get_height() })
|
table.insert(list, { id = id, e = child, y = 0, h = child.get_height() })
|
||||||
-- log.debug("added child " .. id .. " into slot " .. #list)
|
|
||||||
update_positions()
|
update_positions()
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -222,12 +219,10 @@ return function (args)
|
|||||||
for idx, elem in ipairs(list) do
|
for idx, elem in ipairs(list) do
|
||||||
if elem.id == id then
|
if elem.id == id then
|
||||||
table.remove(list, idx)
|
table.remove(list, idx)
|
||||||
-- log.debug("removed child " .. id .. " from slot " .. idx)
|
|
||||||
update_positions()
|
update_positions()
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
-- log.debug("failed to remove child " .. id)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- handle focus
|
-- handle focus
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ local element = require("graphics.element")
|
|||||||
---@class checkbox_args
|
---@class checkbox_args
|
||||||
---@field label string checkbox text
|
---@field label string checkbox text
|
||||||
---@field box_fg_bg cpair colors for checkbox
|
---@field box_fg_bg cpair colors for checkbox
|
||||||
|
---@field disable_fg_bg? cpair text colors when disabled
|
||||||
---@field default? boolean default value
|
---@field default? boolean default value
|
||||||
---@field callback? function function to call on press
|
---@field callback? function function to call on press
|
||||||
---@field parent graphics_element
|
---@field parent graphics_element
|
||||||
@@ -35,20 +36,27 @@ return function (args)
|
|||||||
local function draw()
|
local function draw()
|
||||||
e.w_set_cur(1, 1)
|
e.w_set_cur(1, 1)
|
||||||
|
|
||||||
|
local fgd, bkg = args.box_fg_bg.fgd, args.box_fg_bg.bkg
|
||||||
|
|
||||||
|
if (not e.enabled) and type(args.disable_fg_bg) == "table" then
|
||||||
|
fgd = args.disable_fg_bg.bkg
|
||||||
|
bkg = args.disable_fg_bg.fgd
|
||||||
|
end
|
||||||
|
|
||||||
if e.value then
|
if e.value then
|
||||||
-- show as selected
|
-- show as selected
|
||||||
e.w_set_fgd(args.box_fg_bg.bkg)
|
e.w_set_fgd(bkg)
|
||||||
e.w_set_bkg(args.box_fg_bg.fgd)
|
e.w_set_bkg(fgd)
|
||||||
e.w_write("\x88")
|
e.w_write("\x88")
|
||||||
e.w_set_fgd(args.box_fg_bg.fgd)
|
e.w_set_fgd(fgd)
|
||||||
e.w_set_bkg(e.fg_bg.bkg)
|
e.w_set_bkg(e.fg_bg.bkg)
|
||||||
e.w_write("\x95")
|
e.w_write("\x95")
|
||||||
else
|
else
|
||||||
-- show as unselected
|
-- show as unselected
|
||||||
e.w_set_fgd(e.fg_bg.bkg)
|
e.w_set_fgd(e.fg_bg.bkg)
|
||||||
e.w_set_bkg(args.box_fg_bg.bkg)
|
e.w_set_bkg(bkg)
|
||||||
e.w_write("\x88")
|
e.w_write("\x88")
|
||||||
e.w_set_fgd(args.box_fg_bg.bkg)
|
e.w_set_fgd(bkg)
|
||||||
e.w_set_bkg(e.fg_bg.bkg)
|
e.w_set_bkg(e.fg_bg.bkg)
|
||||||
e.w_write("\x95")
|
e.w_write("\x95")
|
||||||
end
|
end
|
||||||
@@ -57,16 +65,18 @@ return function (args)
|
|||||||
-- write label text
|
-- write label text
|
||||||
local function draw_label()
|
local function draw_label()
|
||||||
if e.enabled and e.is_focused() then
|
if e.enabled and e.is_focused() then
|
||||||
e.w_set_cur(3, 1)
|
|
||||||
e.w_set_fgd(e.fg_bg.bkg)
|
e.w_set_fgd(e.fg_bg.bkg)
|
||||||
e.w_set_bkg(e.fg_bg.fgd)
|
e.w_set_bkg(e.fg_bg.fgd)
|
||||||
e.w_write(args.label)
|
elseif (not e.enabled) and type(args.disable_fg_bg) == "table" then
|
||||||
|
e.w_set_fgd(args.disable_fg_bg.fgd)
|
||||||
|
e.w_set_bkg(args.disable_fg_bg.bkg)
|
||||||
else
|
else
|
||||||
e.w_set_cur(3, 1)
|
|
||||||
e.w_set_fgd(e.fg_bg.fgd)
|
e.w_set_fgd(e.fg_bg.fgd)
|
||||||
e.w_set_bkg(e.fg_bg.bkg)
|
e.w_set_bkg(e.fg_bg.bkg)
|
||||||
e.w_write(args.label)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
e.w_set_cur(3, 1)
|
||||||
|
e.w_write(args.label)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- handle mouse interaction
|
-- handle mouse interaction
|
||||||
@@ -98,20 +108,20 @@ return function (args)
|
|||||||
draw()
|
draw()
|
||||||
end
|
end
|
||||||
|
|
||||||
-- handle focus
|
|
||||||
e.on_focused = draw_label
|
|
||||||
e.on_unfocused = draw_label
|
|
||||||
|
|
||||||
-- handle enable
|
|
||||||
e.on_enabled = draw_label
|
|
||||||
e.on_disabled = draw_label
|
|
||||||
|
|
||||||
-- element redraw
|
-- element redraw
|
||||||
function e.redraw()
|
function e.redraw()
|
||||||
draw()
|
draw()
|
||||||
draw_label()
|
draw_label()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- handle focus
|
||||||
|
e.on_focused = draw_label
|
||||||
|
e.on_unfocused = draw_label
|
||||||
|
|
||||||
|
-- handle enable
|
||||||
|
e.on_enabled = e.redraw
|
||||||
|
e.on_disabled = e.redraw
|
||||||
|
|
||||||
---@class Checkbox:graphics_element
|
---@class Checkbox:graphics_element
|
||||||
local Checkbox, id = e.complete(true)
|
local Checkbox, id = e.complete(true)
|
||||||
|
|
||||||
|
|||||||
@@ -53,25 +53,44 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit)
|
|||||||
--#region Pocket UI
|
--#region Pocket UI
|
||||||
|
|
||||||
local ui_c_1 = Div{parent=ui_cfg,x=2,y=4,width=24}
|
local ui_c_1 = Div{parent=ui_cfg,x=2,y=4,width=24}
|
||||||
|
local ui_c_2 = Div{parent=ui_cfg,x=2,y=4,width=24}
|
||||||
|
|
||||||
|
local ui_pane = MultiPane{parent=net_cfg,x=1,y=4,panes={ui_c_1,ui_c_2}}
|
||||||
|
|
||||||
TextBox{parent=ui_cfg,x=1,y=2,text=" Pocket UI",fg_bg=cpair(colors.black,colors.lime)}
|
TextBox{parent=ui_cfg,x=1,y=2,text=" Pocket UI",fg_bg=cpair(colors.black,colors.lime)}
|
||||||
|
|
||||||
TextBox{parent=ui_c_1,x=1,y=1,height=3,text="You may customize units below."}
|
TextBox{parent=ui_c_1,x=1,y=1,height=3,text="You may customize UI options below."}
|
||||||
|
|
||||||
TextBox{parent=ui_c_1,x=1,y=4,text="Temperature Scale"}
|
TextBox{parent=ui_c_1,y=4,text="Po/Pu Pellet Color"}
|
||||||
local temp_scale = RadioButton{parent=ui_c_1,x=1,y=5,default=ini_cfg.TempScale,options=types.TEMP_SCALE_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime}
|
TextBox{parent=ui_c_1,x=20,y=4,text="new!",fg_bg=cpair(colors.red,colors._INHERIT)} ---@todo remove NEW tag on next revision
|
||||||
|
local pellet_color = RadioButton{parent=ui_c_1,y=5,default=util.trinary(ini_cfg.GreenPuPellet,1,2),options={"Green Pu/Cyan Po","Cyan Pu/Green Po"},callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime}
|
||||||
|
|
||||||
TextBox{parent=ui_c_1,x=1,y=10,text="Energy Scale"}
|
TextBox{parent=ui_c_1,y=8,height=4,text="In Mekanism 10.4 and later, pellet colors now match gas colors (Cyan Pu/Green Po).",fg_bg=g_lg_fg_bg}
|
||||||
local energy_scale = RadioButton{parent=ui_c_1,x=1,y=11,default=ini_cfg.EnergyScale,options=types.ENERGY_SCALE_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime}
|
|
||||||
|
|
||||||
local function submit_ui_opts()
|
local function submit_ui_opts()
|
||||||
|
tmp_cfg.GreenPuPellet = pellet_color.get_value() == 1
|
||||||
|
ui_pane.set_value(2)
|
||||||
|
end
|
||||||
|
|
||||||
|
PushButton{parent=ui_c_1,x=1,y=15,text="\x1b Back",callback=function()main_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
PushButton{parent=ui_c_1,x=19,y=15,text="Next \x1a",callback=submit_ui_opts,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
|
TextBox{parent=ui_c_2,x=1,y=1,height=3,text="You may customize units below."}
|
||||||
|
|
||||||
|
TextBox{parent=ui_c_2,x=1,y=4,text="Temperature Scale"}
|
||||||
|
local temp_scale = RadioButton{parent=ui_c_2,x=1,y=5,default=ini_cfg.TempScale,options=types.TEMP_SCALE_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime}
|
||||||
|
|
||||||
|
TextBox{parent=ui_c_2,x=1,y=10,text="Energy Scale"}
|
||||||
|
local energy_scale = RadioButton{parent=ui_c_2,x=1,y=11,default=ini_cfg.EnergyScale,options=types.ENERGY_SCALE_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime}
|
||||||
|
|
||||||
|
local function submit_ui_units()
|
||||||
tmp_cfg.TempScale = temp_scale.get_value()
|
tmp_cfg.TempScale = temp_scale.get_value()
|
||||||
tmp_cfg.EnergyScale = energy_scale.get_value()
|
tmp_cfg.EnergyScale = energy_scale.get_value()
|
||||||
main_pane.set_value(3)
|
main_pane.set_value(3)
|
||||||
end
|
end
|
||||||
|
|
||||||
PushButton{parent=ui_c_1,x=1,y=15,text="\x1b Back",callback=function()main_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=ui_c_2,x=1,y=15,text="\x1b Back",callback=function()ui_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
PushButton{parent=ui_c_1,x=19,y=15,text="Next \x1a",callback=submit_ui_opts,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=ui_c_2,x=19,y=15,text="Next \x1a",callback=submit_ui_units,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
--#endregion
|
--#endregion
|
||||||
|
|
||||||
@@ -266,6 +285,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit)
|
|||||||
load_settings(settings_cfg, true)
|
load_settings(settings_cfg, true)
|
||||||
load_settings(ini_cfg)
|
load_settings(ini_cfg)
|
||||||
|
|
||||||
|
try_set(pellet_color, ini_cfg.GreenPuPellet)
|
||||||
try_set(temp_scale, ini_cfg.TempScale)
|
try_set(temp_scale, ini_cfg.TempScale)
|
||||||
try_set(energy_scale, ini_cfg.EnergyScale)
|
try_set(energy_scale, ini_cfg.EnergyScale)
|
||||||
try_set(svr_chan, ini_cfg.SVR_Channel)
|
try_set(svr_chan, ini_cfg.SVR_Channel)
|
||||||
@@ -374,6 +394,8 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit)
|
|||||||
val = string.rep("*", string.len(val))
|
val = string.rep("*", string.len(val))
|
||||||
elseif f[1] == "LogMode" then
|
elseif f[1] == "LogMode" then
|
||||||
val = tri(raw == log.MODE.APPEND, "append", "replace")
|
val = tri(raw == log.MODE.APPEND, "append", "replace")
|
||||||
|
elseif f[1] == "GreenPuPellet" then
|
||||||
|
val = tri(raw, "Green Pu/Cyan Po", "Cyan Pu/Green Po")
|
||||||
elseif f[1] == "TempScale" then
|
elseif f[1] == "TempScale" then
|
||||||
val = util.strval(types.TEMP_SCALE_NAMES[raw])
|
val = util.strval(types.TEMP_SCALE_NAMES[raw])
|
||||||
elseif f[1] == "EnergyScale" then
|
elseif f[1] == "EnergyScale" then
|
||||||
@@ -385,7 +407,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit)
|
|||||||
local c = tri(alternate, g_lg_fg_bg, cpair(colors.gray,colors.white))
|
local c = tri(alternate, g_lg_fg_bg, cpair(colors.gray,colors.white))
|
||||||
alternate = not alternate
|
alternate = not alternate
|
||||||
|
|
||||||
if string.len(val) > val_max_w then
|
if (string.len(val) > val_max_w) or string.find(val, "\n") then
|
||||||
local lines = util.strwrap(val, inner_width)
|
local lines = util.strwrap(val, inner_width)
|
||||||
height = #lines + 1
|
height = #lines + 1
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -29,7 +29,8 @@ local CENTER = core.ALIGN.CENTER
|
|||||||
-- changes to the config data/format to let the user know
|
-- changes to the config data/format to let the user know
|
||||||
local changes = {
|
local changes = {
|
||||||
{ "v0.9.2", { "Added temperature scale options" } },
|
{ "v0.9.2", { "Added temperature scale options" } },
|
||||||
{ "v0.11.3", { "Added energy scale options" } }
|
{ "v0.11.3", { "Added energy scale options" } },
|
||||||
|
{ "v0.13.2", { "Added option for Po/Pu pellet green/cyan pairing" } }
|
||||||
}
|
}
|
||||||
|
|
||||||
---@class pkt_configurator
|
---@class pkt_configurator
|
||||||
@@ -50,6 +51,7 @@ style.btn_dis_fg_bg = cpair(colors.lightGray, colors.white)
|
|||||||
|
|
||||||
---@class _pkt_cfg_tool_ctl
|
---@class _pkt_cfg_tool_ctl
|
||||||
local tool_ctl = {
|
local tool_ctl = {
|
||||||
|
launch_startup = false,
|
||||||
ask_config = false,
|
ask_config = false,
|
||||||
has_config = false,
|
has_config = false,
|
||||||
viewing_config = false,
|
viewing_config = false,
|
||||||
@@ -63,6 +65,7 @@ local tool_ctl = {
|
|||||||
|
|
||||||
---@class pkt_config
|
---@class pkt_config
|
||||||
local tmp_cfg = {
|
local tmp_cfg = {
|
||||||
|
GreenPuPellet = false,
|
||||||
TempScale = 1, ---@type TEMP_SCALE
|
TempScale = 1, ---@type TEMP_SCALE
|
||||||
EnergyScale = 1, ---@type ENERGY_SCALE
|
EnergyScale = 1, ---@type ENERGY_SCALE
|
||||||
SVR_Channel = nil, ---@type integer
|
SVR_Channel = nil, ---@type integer
|
||||||
@@ -83,6 +86,7 @@ local settings_cfg = {}
|
|||||||
|
|
||||||
-- all settings fields, their nice names, and their default values
|
-- all settings fields, their nice names, and their default values
|
||||||
local fields = {
|
local fields = {
|
||||||
|
{ "GreenPuPellet", "Pellet Colors", false },
|
||||||
{ "TempScale", "Temperature Scale", types.TEMP_SCALE.KELVIN },
|
{ "TempScale", "Temperature Scale", types.TEMP_SCALE.KELVIN },
|
||||||
{ "EnergyScale", "Energy Scale", types.ENERGY_SCALE.FE },
|
{ "EnergyScale", "Energy Scale", types.ENERGY_SCALE.FE },
|
||||||
{ "SVR_Channel", "SVR Channel", 16240 },
|
{ "SVR_Channel", "SVR Channel", 16240 },
|
||||||
@@ -162,8 +166,16 @@ local function config_view(display)
|
|||||||
|
|
||||||
if not tool_ctl.has_config then tool_ctl.view_cfg.disable() end
|
if not tool_ctl.has_config then tool_ctl.view_cfg.disable() end
|
||||||
|
|
||||||
|
local function startup()
|
||||||
|
tool_ctl.launch_startup = true
|
||||||
|
exit()
|
||||||
|
end
|
||||||
|
|
||||||
PushButton{parent=main_page,x=2,y=18,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=main_page,x=2,y=18,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=btn_act_fg_bg}
|
||||||
PushButton{parent=main_page,x=14,y=18,min_width=12,text="Change Log",callback=function()main_pane.set_value(6)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
local start_btn = PushButton{parent=main_page,x=17,y=18,min_width=9,text="Startup",callback=startup,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
||||||
|
PushButton{parent=main_page,x=2,y=y_start+4,min_width=12,text="Change Log",callback=function()main_pane.set_value(6)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
|
if tool_ctl.ask_config then start_btn.disable() end
|
||||||
|
|
||||||
--#endregion
|
--#endregion
|
||||||
|
|
||||||
@@ -254,7 +266,7 @@ function configurator.configure(ask_config)
|
|||||||
println("configurator error: " .. error)
|
println("configurator error: " .. error)
|
||||||
end
|
end
|
||||||
|
|
||||||
return status, error
|
return status, error, tool_ctl.launch_startup
|
||||||
end
|
end
|
||||||
|
|
||||||
return configurator
|
return configurator
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
-- I/O Control for Pocket Integration with Supervisor & Coordinator
|
-- I/O Control for Pocket Integration with Supervisor & Coordinator
|
||||||
--
|
--
|
||||||
|
|
||||||
local const = require("scada-common.constants")
|
|
||||||
local psil = require("scada-common.psil")
|
local psil = require("scada-common.psil")
|
||||||
local types = require("scada-common.types")
|
local types = require("scada-common.types")
|
||||||
local util = require("scada-common.util")
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
|
local iorx = require("pocket.iorx")
|
||||||
local process = require("pocket.process")
|
local process = require("pocket.process")
|
||||||
|
|
||||||
local ALARM = types.ALARM
|
local ALARM = types.ALARM
|
||||||
@@ -50,6 +50,8 @@ function iocontrol.init_core(pkt_comms, nav, cfg)
|
|||||||
comms = pkt_comms
|
comms = pkt_comms
|
||||||
config = cfg
|
config = cfg
|
||||||
|
|
||||||
|
iocontrol.rx = iorx(io)
|
||||||
|
|
||||||
io.nav = nav
|
io.nav = nav
|
||||||
|
|
||||||
---@class pocket_ioctl_diag
|
---@class pocket_ioctl_diag
|
||||||
@@ -92,9 +94,12 @@ function iocontrol.init_core(pkt_comms, nav, cfg)
|
|||||||
-- API access
|
-- API access
|
||||||
---@class pocket_ioctl_api
|
---@class pocket_ioctl_api
|
||||||
io.api = {
|
io.api = {
|
||||||
|
get_fac = function () comms.api__get_facility() end,
|
||||||
get_unit = function (unit) comms.api__get_unit(unit) end,
|
get_unit = function (unit) comms.api__get_unit(unit) end,
|
||||||
get_ctrl = function () comms.api__get_control() end,
|
get_ctrl = function () comms.api__get_control() end,
|
||||||
get_proc = function () comms.api__get_process() end
|
get_proc = function () comms.api__get_process() end,
|
||||||
|
get_waste = function () comms.api__get_waste() end,
|
||||||
|
get_rad = function () comms.api__get_rad() end
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -135,6 +140,9 @@ function iocontrol.init_fac(conf)
|
|||||||
num_units = conf.num_units,
|
num_units = conf.num_units,
|
||||||
tank_mode = conf.cooling.fac_tank_mode,
|
tank_mode = conf.cooling.fac_tank_mode,
|
||||||
tank_defs = conf.cooling.fac_tank_defs,
|
tank_defs = conf.cooling.fac_tank_defs,
|
||||||
|
tank_list = conf.cooling.fac_tank_list,
|
||||||
|
tank_conns = conf.cooling.fac_tank_conns,
|
||||||
|
tank_fluid_types = conf.cooling.tank_fluid_types,
|
||||||
all_sys_ok = false,
|
all_sys_ok = false,
|
||||||
rtu_count = 0,
|
rtu_count = 0,
|
||||||
|
|
||||||
@@ -148,7 +156,7 @@ function iocontrol.init_fac(conf)
|
|||||||
auto_scram = false,
|
auto_scram = false,
|
||||||
---@type ascram_status
|
---@type ascram_status
|
||||||
ascram_status = {
|
ascram_status = {
|
||||||
matrix_dc = false,
|
matrix_fault = false,
|
||||||
matrix_fill = false,
|
matrix_fill = false,
|
||||||
crit_alarm = false,
|
crit_alarm = false,
|
||||||
radiation = false,
|
radiation = false,
|
||||||
@@ -158,6 +166,8 @@ function iocontrol.init_fac(conf)
|
|||||||
---@type WASTE_PRODUCT
|
---@type WASTE_PRODUCT
|
||||||
auto_current_waste_product = types.WASTE_PRODUCT.PLUTONIUM,
|
auto_current_waste_product = types.WASTE_PRODUCT.PLUTONIUM,
|
||||||
auto_pu_fallback_active = false,
|
auto_pu_fallback_active = false,
|
||||||
|
auto_sps_disabled = false,
|
||||||
|
waste_stats = { 0, 0, 0, 0, 0, 0 }, -- waste in, pu, po, po pellets, am, spent waste
|
||||||
|
|
||||||
radiation = types.new_zero_radiation_reading(),
|
radiation = types.new_zero_radiation_reading(),
|
||||||
|
|
||||||
@@ -175,7 +185,9 @@ function iocontrol.init_fac(conf)
|
|||||||
sps_data_tbl = {}, ---@type sps_session_db[]
|
sps_data_tbl = {}, ---@type sps_session_db[]
|
||||||
|
|
||||||
tank_ps_tbl = {}, ---@type psil[]
|
tank_ps_tbl = {}, ---@type psil[]
|
||||||
tank_data_tbl = {} ---@type dynamicv_session_db[]
|
tank_data_tbl = {}, ---@type dynamicv_session_db[]
|
||||||
|
|
||||||
|
rad_monitors = {} ---@type { radiation: radiation_reading, raw: number }[]
|
||||||
}
|
}
|
||||||
|
|
||||||
-- create induction and SPS tables (currently only 1 of each is supported)
|
-- create induction and SPS tables (currently only 1 of each is supported)
|
||||||
@@ -184,6 +196,14 @@ function iocontrol.init_fac(conf)
|
|||||||
table.insert(io.facility.sps_ps_tbl, psil.create())
|
table.insert(io.facility.sps_ps_tbl, psil.create())
|
||||||
table.insert(io.facility.sps_data_tbl, {})
|
table.insert(io.facility.sps_data_tbl, {})
|
||||||
|
|
||||||
|
-- create facility tank tables
|
||||||
|
for i = 1, #io.facility.tank_list do
|
||||||
|
if io.facility.tank_list[i] == 2 then
|
||||||
|
table.insert(io.facility.tank_ps_tbl, psil.create())
|
||||||
|
table.insert(io.facility.tank_data_tbl, {})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- create unit data structures
|
-- create unit data structures
|
||||||
io.units = {} ---@type pioctl_unit[]
|
io.units = {} ---@type pioctl_unit[]
|
||||||
for i = 1, conf.num_units do
|
for i = 1, conf.num_units do
|
||||||
@@ -191,8 +211,6 @@ function iocontrol.init_fac(conf)
|
|||||||
local entry = {
|
local entry = {
|
||||||
unit_id = i,
|
unit_id = i,
|
||||||
connected = false,
|
connected = false,
|
||||||
---@type { boilers: { connected: boolean, faulted: boolean }[], turbines: { connected: boolean, faulted: boolean }[] }
|
|
||||||
rtu_hw = {},
|
|
||||||
|
|
||||||
num_boilers = 0,
|
num_boilers = 0,
|
||||||
num_turbines = 0,
|
num_turbines = 0,
|
||||||
@@ -217,6 +235,7 @@ function iocontrol.init_fac(conf)
|
|||||||
|
|
||||||
last_rate_change_ms = 0,
|
last_rate_change_ms = 0,
|
||||||
turbine_flow_stable = false,
|
turbine_flow_stable = false,
|
||||||
|
waste_stats = { 0, 0, 0 }, -- plutonium, polonium, po pellets
|
||||||
|
|
||||||
-- auto control group
|
-- auto control group
|
||||||
a_group = types.AUTO_GROUP.MANUAL,
|
a_group = types.AUTO_GROUP.MANUAL,
|
||||||
@@ -235,6 +254,7 @@ function iocontrol.init_fac(conf)
|
|||||||
---@type { [ALARM]: ALARM_STATE }
|
---@type { [ALARM]: ALARM_STATE }
|
||||||
alarms = { ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE },
|
alarms = { ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE },
|
||||||
|
|
||||||
|
---@diagnostic disable-next-line: missing-fields
|
||||||
annunciator = {}, ---@type annunciator
|
annunciator = {}, ---@type annunciator
|
||||||
|
|
||||||
unit_ps = psil.create(),
|
unit_ps = psil.create(),
|
||||||
@@ -247,7 +267,9 @@ function iocontrol.init_fac(conf)
|
|||||||
turbine_data_tbl = {}, ---@type turbinev_session_db[]
|
turbine_data_tbl = {}, ---@type turbinev_session_db[]
|
||||||
|
|
||||||
tank_ps_tbl = {}, ---@type psil[]
|
tank_ps_tbl = {}, ---@type psil[]
|
||||||
tank_data_tbl = {} ---@type dynamicv_session_db[]
|
tank_data_tbl = {}, ---@type dynamicv_session_db[]
|
||||||
|
|
||||||
|
rad_monitors = {} ---@type { radiation: radiation_reading, raw: number }[]
|
||||||
}
|
}
|
||||||
|
|
||||||
-- on other facility modes, overwrite unit TANK option with facility tank defs
|
-- on other facility modes, overwrite unit TANK option with facility tank defs
|
||||||
@@ -343,583 +365,6 @@ function iocontrol.report_crd_tt(trip_time)
|
|||||||
io.ps.publish("crd_conn_quality", state)
|
io.ps.publish("crd_conn_quality", state)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- populate facility data from API_GET_FAC
|
|
||||||
---@param data table
|
|
||||||
---@return boolean valid
|
|
||||||
function iocontrol.record_facility_data(data)
|
|
||||||
local valid = true
|
|
||||||
|
|
||||||
local fac = io.facility
|
|
||||||
|
|
||||||
fac.all_sys_ok = data[1]
|
|
||||||
fac.rtu_count = data[2]
|
|
||||||
fac.radiation = data[3]
|
|
||||||
|
|
||||||
-- auto control
|
|
||||||
if type(data[4]) == "table" and #data[4] == 4 then
|
|
||||||
fac.auto_ready = data[4][1]
|
|
||||||
fac.auto_active = data[4][2]
|
|
||||||
fac.auto_ramping = data[4][3]
|
|
||||||
fac.auto_saturated = data[4][4]
|
|
||||||
end
|
|
||||||
|
|
||||||
-- waste
|
|
||||||
if type(data[5]) == "table" and #data[5] == 2 then
|
|
||||||
fac.auto_current_waste_product = data[5][1]
|
|
||||||
fac.auto_pu_fallback_active = data[5][2]
|
|
||||||
end
|
|
||||||
|
|
||||||
fac.num_tanks = data[6]
|
|
||||||
fac.has_imatrix = data[7]
|
|
||||||
fac.has_sps = data[8]
|
|
||||||
|
|
||||||
return valid
|
|
||||||
end
|
|
||||||
|
|
||||||
local function tripped(state) return state == ALARM_STATE.TRIPPED or state == ALARM_STATE.ACKED end
|
|
||||||
|
|
||||||
local function _record_multiblock_status(faulted, data, ps)
|
|
||||||
ps.publish("formed", data.formed)
|
|
||||||
ps.publish("faulted", 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
|
|
||||||
end
|
|
||||||
|
|
||||||
-- update unit status data from API_GET_UNIT
|
|
||||||
---@param data table
|
|
||||||
function iocontrol.record_unit_data(data)
|
|
||||||
local unit = io.units[data[1]]
|
|
||||||
|
|
||||||
unit.connected = data[2]
|
|
||||||
unit.rtu_hw = data[3]
|
|
||||||
unit.a_group = data[4]
|
|
||||||
unit.alarms = data[5]
|
|
||||||
|
|
||||||
unit.unit_ps.publish("auto_group_id", unit.a_group)
|
|
||||||
unit.unit_ps.publish("auto_group", types.AUTO_GROUP_NAMES[unit.a_group + 1])
|
|
||||||
|
|
||||||
--#region Annunciator
|
|
||||||
|
|
||||||
unit.annunciator = data[6]
|
|
||||||
|
|
||||||
local rcs_disconn, rcs_warn, rcs_hazard = false, false, false
|
|
||||||
|
|
||||||
for key, val in pairs(unit.annunciator) do
|
|
||||||
if key == "BoilerOnline" or key == "TurbineOnline" then
|
|
||||||
local every = true
|
|
||||||
|
|
||||||
-- split up online arrays
|
|
||||||
for id = 1, #val do
|
|
||||||
every = every and val[id]
|
|
||||||
|
|
||||||
if key == "BoilerOnline" then
|
|
||||||
unit.boiler_ps_tbl[id].publish(key, val[id])
|
|
||||||
else
|
|
||||||
unit.turbine_ps_tbl[id].publish(key, val[id])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if not every then rcs_disconn = true end
|
|
||||||
|
|
||||||
unit.unit_ps.publish("U_" .. key, every)
|
|
||||||
elseif key == "HeatingRateLow" or key == "WaterLevelLow" then
|
|
||||||
-- split up array for all boilers
|
|
||||||
local any = false
|
|
||||||
for id = 1, #val do
|
|
||||||
any = any or val[id]
|
|
||||||
unit.boiler_ps_tbl[id].publish(key, val[id])
|
|
||||||
end
|
|
||||||
|
|
||||||
if key == "HeatingRateLow" and any then
|
|
||||||
rcs_warn = true
|
|
||||||
elseif key == "WaterLevelLow" and any then
|
|
||||||
rcs_hazard = true
|
|
||||||
end
|
|
||||||
|
|
||||||
unit.unit_ps.publish("U_" .. key, any)
|
|
||||||
elseif key == "SteamDumpOpen" or key == "TurbineOverSpeed" or key == "GeneratorTrip" or key == "TurbineTrip" then
|
|
||||||
-- split up array for all turbines
|
|
||||||
local any = false
|
|
||||||
for id = 1, #val do
|
|
||||||
any = any or val[id]
|
|
||||||
unit.turbine_ps_tbl[id].publish(key, val[id])
|
|
||||||
end
|
|
||||||
|
|
||||||
if key == "GeneratorTrip" and any then
|
|
||||||
rcs_warn = true
|
|
||||||
elseif (key == "TurbineOverSpeed" or key == "TurbineTrip") and any then
|
|
||||||
rcs_hazard = true
|
|
||||||
end
|
|
||||||
|
|
||||||
unit.unit_ps.publish("U_" .. key, any)
|
|
||||||
else
|
|
||||||
-- non-table fields
|
|
||||||
unit.unit_ps.publish(key, val)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local anc = unit.annunciator
|
|
||||||
rcs_hazard = rcs_hazard or anc.RCPTrip
|
|
||||||
rcs_warn = rcs_warn or anc.RCSFlowLow or anc.CoolantLevelLow or anc.RCSFault or anc.MaxWaterReturnFeed or
|
|
||||||
anc.CoolantFeedMismatch or anc.BoilRateMismatch or anc.SteamFeedMismatch
|
|
||||||
|
|
||||||
local rcs_status = 4
|
|
||||||
if rcs_hazard then
|
|
||||||
rcs_status = 2
|
|
||||||
elseif rcs_warn then
|
|
||||||
rcs_status = 3
|
|
||||||
elseif rcs_disconn then
|
|
||||||
rcs_status = 1
|
|
||||||
end
|
|
||||||
|
|
||||||
unit.unit_ps.publish("U_RCS", rcs_status)
|
|
||||||
|
|
||||||
--#endregion
|
|
||||||
|
|
||||||
--#region Reactor Data
|
|
||||||
|
|
||||||
unit.reactor_data = data[7]
|
|
||||||
|
|
||||||
local control_status = 1
|
|
||||||
local reactor_status = 1
|
|
||||||
local reactor_state = 1
|
|
||||||
local rps_status = 1
|
|
||||||
|
|
||||||
if unit.connected then
|
|
||||||
-- update RPS status
|
|
||||||
if unit.reactor_data.rps_tripped then
|
|
||||||
control_status = 2
|
|
||||||
|
|
||||||
if unit.reactor_data.rps_trip_cause == "manual" then
|
|
||||||
reactor_state = 4 -- disabled
|
|
||||||
rps_status = 3
|
|
||||||
else
|
|
||||||
reactor_state = 6 -- SCRAM
|
|
||||||
rps_status = 2
|
|
||||||
end
|
|
||||||
else
|
|
||||||
rps_status = 4
|
|
||||||
reactor_state = 4
|
|
||||||
end
|
|
||||||
|
|
||||||
-- update reactor/control status
|
|
||||||
if unit.reactor_data.mek_status.status then
|
|
||||||
reactor_status = 4
|
|
||||||
reactor_state = 5 -- running
|
|
||||||
control_status = util.trinary(unit.annunciator.AutoControl, 4, 3)
|
|
||||||
else
|
|
||||||
if unit.reactor_data.no_reactor then
|
|
||||||
reactor_status = 2
|
|
||||||
reactor_state = 3 -- faulted
|
|
||||||
elseif not unit.reactor_data.formed then
|
|
||||||
reactor_status = 3
|
|
||||||
reactor_state = 2 -- not formed
|
|
||||||
elseif unit.reactor_data.rps_status.force_dis then
|
|
||||||
reactor_status = 3
|
|
||||||
reactor_state = 7 -- force disabled
|
|
||||||
else
|
|
||||||
reactor_status = 4
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
for key, val in pairs(unit.reactor_data) do
|
|
||||||
if key ~= "rps_status" and key ~= "mek_struct" and key ~= "mek_status" then
|
|
||||||
unit.unit_ps.publish(key, val)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
for key, val in pairs(unit.reactor_data.rps_status) do
|
|
||||||
unit.unit_ps.publish(key, val)
|
|
||||||
end
|
|
||||||
|
|
||||||
for key, val in pairs(unit.reactor_data.mek_struct) do
|
|
||||||
unit.unit_ps.publish(key, val)
|
|
||||||
end
|
|
||||||
|
|
||||||
for key, val in pairs(unit.reactor_data.mek_status) do
|
|
||||||
unit.unit_ps.publish(key, val)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
unit.unit_ps.publish("U_ControlStatus", control_status)
|
|
||||||
unit.unit_ps.publish("U_ReactorStatus", reactor_status)
|
|
||||||
unit.unit_ps.publish("U_ReactorStateStatus", reactor_state)
|
|
||||||
unit.unit_ps.publish("U_RPS", rps_status)
|
|
||||||
|
|
||||||
--#endregion
|
|
||||||
|
|
||||||
--#region RTU Devices
|
|
||||||
|
|
||||||
unit.boiler_data_tbl = data[8]
|
|
||||||
|
|
||||||
for id = 1, #unit.boiler_data_tbl do
|
|
||||||
local boiler = unit.boiler_data_tbl[id]
|
|
||||||
local ps = unit.boiler_ps_tbl[id]
|
|
||||||
|
|
||||||
local boiler_status = 1
|
|
||||||
local computed_status = 1
|
|
||||||
|
|
||||||
if unit.rtu_hw.boilers[id].connected then
|
|
||||||
if unit.rtu_hw.boilers[id].faulted then
|
|
||||||
boiler_status = 3
|
|
||||||
computed_status = 3
|
|
||||||
elseif boiler.formed then
|
|
||||||
boiler_status = 4
|
|
||||||
|
|
||||||
if boiler.state.boil_rate > 0 then
|
|
||||||
computed_status = 5
|
|
||||||
else
|
|
||||||
computed_status = 4
|
|
||||||
end
|
|
||||||
else
|
|
||||||
boiler_status = 2
|
|
||||||
computed_status = 2
|
|
||||||
end
|
|
||||||
|
|
||||||
_record_multiblock_status(unit.rtu_hw.boilers[id].faulted, boiler, ps)
|
|
||||||
end
|
|
||||||
|
|
||||||
ps.publish("BoilerStatus", boiler_status)
|
|
||||||
ps.publish("BoilerStateStatus", computed_status)
|
|
||||||
end
|
|
||||||
|
|
||||||
unit.turbine_data_tbl = data[9]
|
|
||||||
|
|
||||||
for id = 1, #unit.turbine_data_tbl do
|
|
||||||
local turbine = unit.turbine_data_tbl[id]
|
|
||||||
local ps = unit.turbine_ps_tbl[id]
|
|
||||||
|
|
||||||
local turbine_status = 1
|
|
||||||
local computed_status = 1
|
|
||||||
|
|
||||||
if unit.rtu_hw.turbines[id].connected then
|
|
||||||
if unit.rtu_hw.turbines[id].faulted then
|
|
||||||
turbine_status = 3
|
|
||||||
computed_status = 3
|
|
||||||
elseif turbine.formed then
|
|
||||||
turbine_status = 4
|
|
||||||
|
|
||||||
if turbine.tanks.energy_fill >= 0.99 then
|
|
||||||
computed_status = 6
|
|
||||||
elseif turbine.state.flow_rate < 100 then
|
|
||||||
computed_status = 4
|
|
||||||
else
|
|
||||||
computed_status = 5
|
|
||||||
end
|
|
||||||
else
|
|
||||||
turbine_status = 2
|
|
||||||
computed_status = 2
|
|
||||||
end
|
|
||||||
|
|
||||||
_record_multiblock_status(unit.rtu_hw.turbines[id].faulted, turbine, ps)
|
|
||||||
end
|
|
||||||
|
|
||||||
ps.publish("TurbineStatus", turbine_status)
|
|
||||||
ps.publish("TurbineStateStatus", computed_status)
|
|
||||||
end
|
|
||||||
|
|
||||||
unit.tank_data_tbl = data[10]
|
|
||||||
|
|
||||||
unit.last_rate_change_ms = data[11]
|
|
||||||
unit.turbine_flow_stable = data[12]
|
|
||||||
|
|
||||||
--#endregion
|
|
||||||
|
|
||||||
--#region Status Information Display
|
|
||||||
|
|
||||||
local ecam = {} -- aviation reference :)
|
|
||||||
|
|
||||||
-- local function red(text) return { text = text, color = colors.red } end
|
|
||||||
local function white(text) return { text = text, color = colors.white } end
|
|
||||||
local function blue(text) return { text = text, color = colors.blue } end
|
|
||||||
|
|
||||||
-- if unit.reactor_data.rps_status then
|
|
||||||
-- for k, v in pairs(unit.alarms) do
|
|
||||||
-- unit.alarms[k] = ALARM_STATE.TRIPPED
|
|
||||||
-- end
|
|
||||||
-- end
|
|
||||||
|
|
||||||
if tripped(unit.alarms[ALARM.ContainmentBreach]) then
|
|
||||||
local items = { white("REACTOR MELTDOWN"), blue("DON HAZMAT SUIT") }
|
|
||||||
table.insert(ecam, { color = colors.red, text = "CONTAINMENT BREACH", help = "ContainmentBreach", items = items })
|
|
||||||
end
|
|
||||||
|
|
||||||
if tripped(unit.alarms[ALARM.ContainmentRadiation]) then
|
|
||||||
local items = {
|
|
||||||
white("RADIATION DETECTED"),
|
|
||||||
blue("DON HAZMAT SUIT"),
|
|
||||||
blue("RESOLVE LEAK"),
|
|
||||||
blue("AWAIT SAFE LEVELS")
|
|
||||||
}
|
|
||||||
|
|
||||||
table.insert(ecam, { color = colors.red, text = "RADIATION LEAK", help = "ContainmentRadiation", items = items })
|
|
||||||
end
|
|
||||||
|
|
||||||
if tripped(unit.alarms[ALARM.CriticalDamage]) then
|
|
||||||
local items = { white("MELTDOWN IMMINENT"), blue("EVACUATE") }
|
|
||||||
table.insert(ecam, { color = colors.red, text = "RCT DAMAGE CRITICAL", help = "CriticalDamage", items = items })
|
|
||||||
end
|
|
||||||
|
|
||||||
if tripped(unit.alarms[ALARM.ReactorLost]) then
|
|
||||||
local items = { white("REACTOR OFF-LINE"), blue("CHECK PLC") }
|
|
||||||
table.insert(ecam, { color = colors.red, text = "REACTOR CONN LOST", help = "ReactorLost", items = items })
|
|
||||||
end
|
|
||||||
|
|
||||||
if tripped(unit.alarms[ALARM.ReactorDamage]) then
|
|
||||||
local items = { white("REACTOR DAMAGED"), blue("CHECK RCS"), blue("AWAIT DMG REDUCED") }
|
|
||||||
table.insert(ecam, { color = colors.red, text = "REACTOR DAMAGE", help = "ReactorDamage", items = items })
|
|
||||||
end
|
|
||||||
|
|
||||||
if tripped(unit.alarms[ALARM.ReactorOverTemp]) then
|
|
||||||
local items = { white("DAMAGING TEMP"), blue("CHECK RCS"), blue("AWAIT COOLDOWN") }
|
|
||||||
table.insert(ecam, { color = colors.red, text = "REACTOR OVER TEMP", help = "ReactorOverTemp", items = items })
|
|
||||||
end
|
|
||||||
|
|
||||||
if tripped(unit.alarms[ALARM.ReactorHighTemp]) then
|
|
||||||
local items = { white("OVER EXPECTED TEMP"), blue("CHECK RCS") }
|
|
||||||
table.insert(ecam, { color = colors.yellow, text = "REACTOR HIGH TEMP", help = "ReactorHighTemp", items = items})
|
|
||||||
end
|
|
||||||
|
|
||||||
if tripped(unit.alarms[ALARM.ReactorWasteLeak]) then
|
|
||||||
local items = { white("AT WASTE CAPACITY"), blue("CHECK WASTE OUTPUT"), blue("KEEP RCT DISABLED") }
|
|
||||||
table.insert(ecam, { color = colors.red, text = "REACTOR WASTE LEAK", help = "ReactorWasteLeak", items = items})
|
|
||||||
end
|
|
||||||
|
|
||||||
if tripped(unit.alarms[ALARM.ReactorHighWaste]) then
|
|
||||||
local items = { blue("CHECK WASTE OUTPUT") }
|
|
||||||
table.insert(ecam, { color = colors.yellow, text = "REACTOR WASTE HIGH", help = "ReactorHighWaste", items = items})
|
|
||||||
end
|
|
||||||
|
|
||||||
if tripped(unit.alarms[ALARM.RPSTransient]) then
|
|
||||||
local items = {}
|
|
||||||
local stat = unit.reactor_data.rps_status
|
|
||||||
|
|
||||||
-- for k, _ in pairs(stat) do stat[k] = true end
|
|
||||||
|
|
||||||
local function insert(cond, key, text, color) if cond[key] then table.insert(items, { text = text, help = key, color = color }) end end
|
|
||||||
|
|
||||||
table.insert(items, white("REACTOR SCRAMMED"))
|
|
||||||
insert(stat, "high_dmg", "HIGH DAMAGE", colors.red)
|
|
||||||
insert(stat, "high_temp", "HIGH TEMPERATURE", colors.red)
|
|
||||||
insert(stat, "low_cool", "CRIT LOW COOLANT")
|
|
||||||
insert(stat, "ex_waste", "EXCESS WASTE")
|
|
||||||
insert(stat, "ex_hcool", "EXCESS HEATED COOL")
|
|
||||||
insert(stat, "no_fuel", "NO FUEL")
|
|
||||||
insert(stat, "fault", "HARDWARE FAULT")
|
|
||||||
insert(stat, "timeout", "SUPERVISOR DISCONN")
|
|
||||||
insert(stat, "manual", "MANUAL SCRAM", colors.white)
|
|
||||||
insert(stat, "automatic", "AUTOMATIC SCRAM")
|
|
||||||
insert(stat, "sys_fail", "NOT FORMED", colors.red)
|
|
||||||
insert(stat, "force_dis", "FORCE DISABLED", colors.red)
|
|
||||||
table.insert(items, blue("RESOLVE PROBLEM"))
|
|
||||||
table.insert(items, blue("RESET RPS"))
|
|
||||||
|
|
||||||
table.insert(ecam, { color = colors.yellow, text = "RPS TRANSIENT", help = "RPSTransient", items = items})
|
|
||||||
end
|
|
||||||
|
|
||||||
if tripped(unit.alarms[ALARM.RCSTransient]) then
|
|
||||||
local items = {}
|
|
||||||
local annunc = unit.annunciator
|
|
||||||
|
|
||||||
-- for k, v in pairs(annunc) do
|
|
||||||
-- if type(v) == "boolean" then annunc[k] = true end
|
|
||||||
-- if type(v) == "table" then
|
|
||||||
-- for a, _ in pairs(v) do
|
|
||||||
-- v[a] = true
|
|
||||||
-- end
|
|
||||||
-- end
|
|
||||||
-- end
|
|
||||||
|
|
||||||
local function insert(cond, key, text, color)
|
|
||||||
if cond == true or (type(cond) == "table" and cond[key]) then table.insert(items, { text = text, help = key, color = color }) end
|
|
||||||
end
|
|
||||||
|
|
||||||
table.insert(items, white("COOLANT PROBLEM"))
|
|
||||||
|
|
||||||
insert(annunc, "RCPTrip", "RCP TRIP", colors.red)
|
|
||||||
insert(annunc, "CoolantLevelLow", "LOW COOLANT")
|
|
||||||
|
|
||||||
if unit.num_boilers == 0 then
|
|
||||||
if (util.time_ms() - unit.last_rate_change_ms) > const.FLOW_STABILITY_DELAY_MS then
|
|
||||||
insert(annunc, "BoilRateMismatch", "BOIL RATE MISMATCH")
|
|
||||||
end
|
|
||||||
|
|
||||||
if unit.turbine_flow_stable then
|
|
||||||
insert(annunc, "RCSFlowLow", "RCS FLOW LOW")
|
|
||||||
insert(annunc, "CoolantFeedMismatch", "COOL FEED MISMATCH")
|
|
||||||
insert(annunc, "SteamFeedMismatch", "STM FEED MISMATCH")
|
|
||||||
end
|
|
||||||
else
|
|
||||||
if (util.time_ms() - unit.last_rate_change_ms) > const.FLOW_STABILITY_DELAY_MS then
|
|
||||||
insert(annunc, "RCSFlowLow", "RCS FLOW LOW")
|
|
||||||
insert(annunc, "BoilRateMismatch", "BOIL RATE MISMATCH")
|
|
||||||
insert(annunc, "CoolantFeedMismatch", "COOL FEED MISMATCH")
|
|
||||||
end
|
|
||||||
|
|
||||||
if unit.turbine_flow_stable then
|
|
||||||
insert(annunc, "SteamFeedMismatch", "STM FEED MISMATCH")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
insert(annunc, "MaxWaterReturnFeed", "MAX WTR RTRN FEED")
|
|
||||||
|
|
||||||
for k, v in ipairs(annunc.WaterLevelLow) do insert(v, "WaterLevelLow", "BOILER " .. k .. " WTR LOW", colors.red) end
|
|
||||||
for k, v in ipairs(annunc.HeatingRateLow) do insert(v, "HeatingRateLow", "BOILER " .. k .. " HEAT RATE") end
|
|
||||||
for k, v in ipairs(annunc.TurbineOverSpeed) do insert(v, "TurbineOverSpeed", "TURBINE " .. k .. " OVERSPD", colors.red) end
|
|
||||||
for k, v in ipairs(annunc.GeneratorTrip) do insert(v, "GeneratorTrip", "TURBINE " .. k .. " GEN TRIP") end
|
|
||||||
|
|
||||||
table.insert(items, blue("CHECK COOLING SYS"))
|
|
||||||
|
|
||||||
table.insert(ecam, { color = colors.yellow, text = "RCS TRANSIENT", help = "RCSTransient", items = items})
|
|
||||||
end
|
|
||||||
|
|
||||||
if tripped(unit.alarms[ALARM.TurbineTrip]) then
|
|
||||||
local items = {}
|
|
||||||
|
|
||||||
for k, v in ipairs(unit.annunciator.TurbineTrip) do
|
|
||||||
if v then table.insert(items, { text = "TURBINE " .. k .. " TRIP", help = "TurbineTrip" }) end
|
|
||||||
end
|
|
||||||
|
|
||||||
table.insert(items, blue("CHECK ENERGY OUT"))
|
|
||||||
table.insert(ecam, { color = colors.red, text = "TURBINE TRIP", help = "TurbineTripAlarm", items = items})
|
|
||||||
end
|
|
||||||
|
|
||||||
if not (tripped(unit.alarms[ALARM.ReactorLost]) or unit.connected) then
|
|
||||||
local items = { blue("CHECK PLC") }
|
|
||||||
table.insert(ecam, { color = colors.yellow, text = "REACTOR OFF-LINE", items = items })
|
|
||||||
end
|
|
||||||
|
|
||||||
for k, v in ipairs(unit.annunciator.BoilerOnline) do
|
|
||||||
if not v then
|
|
||||||
local items = { blue("CHECK RTU") }
|
|
||||||
table.insert(ecam, { color = colors.yellow, text = "BOILER " .. k .. " OFF-LINE", items = items})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
for k, v in ipairs(unit.annunciator.TurbineOnline) do
|
|
||||||
if not v then
|
|
||||||
local items = { blue("CHECK RTU") }
|
|
||||||
table.insert(ecam, { color = colors.yellow, text = "TURBINE " .. k .. " OFF-LINE", items = items})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- if no alarms, put some basic status messages in
|
|
||||||
if #ecam == 0 then
|
|
||||||
table.insert(ecam, { color = colors.green, text = "REACTOR " .. util.trinary(unit.reactor_data.mek_status.status, "NOMINAL", "IDLE"), items = {}})
|
|
||||||
|
|
||||||
local plural = util.trinary(unit.num_turbines > 1, "S", "")
|
|
||||||
table.insert(ecam, { color = colors.green, text = "TURBINE" .. plural .. util.trinary(unit.turbine_flow_stable, " STABLE", " STABILIZING"), items = {}})
|
|
||||||
end
|
|
||||||
|
|
||||||
unit.unit_ps.publish("U_ECAM", textutils.serialize(ecam))
|
|
||||||
|
|
||||||
--#endregion
|
|
||||||
end
|
|
||||||
|
|
||||||
-- update control app with unit data from API_GET_CTRL
|
|
||||||
---@param data table
|
|
||||||
function iocontrol.record_control_data(data)
|
|
||||||
for u_id = 1, #data do
|
|
||||||
local unit = io.units[u_id]
|
|
||||||
local u_data = data[u_id]
|
|
||||||
|
|
||||||
unit.connected = u_data[1]
|
|
||||||
|
|
||||||
unit.reactor_data.rps_tripped = u_data[2]
|
|
||||||
unit.unit_ps.publish("rps_tripped", u_data[2])
|
|
||||||
unit.reactor_data.mek_status.status = u_data[3]
|
|
||||||
unit.unit_ps.publish("status", u_data[3])
|
|
||||||
unit.reactor_data.mek_status.temp = u_data[4]
|
|
||||||
unit.unit_ps.publish("temp", u_data[4])
|
|
||||||
unit.reactor_data.mek_status.burn_rate = u_data[5]
|
|
||||||
unit.unit_ps.publish("burn_rate", u_data[5])
|
|
||||||
unit.reactor_data.mek_status.act_burn_rate = u_data[6]
|
|
||||||
unit.unit_ps.publish("act_burn_rate", u_data[6])
|
|
||||||
unit.reactor_data.mek_struct.max_burn = u_data[7]
|
|
||||||
unit.unit_ps.publish("max_burn", u_data[7])
|
|
||||||
|
|
||||||
unit.annunciator.AutoControl = u_data[8]
|
|
||||||
unit.unit_ps.publish("AutoControl", u_data[8])
|
|
||||||
|
|
||||||
unit.a_group = u_data[9]
|
|
||||||
unit.unit_ps.publish("auto_group_id", unit.a_group)
|
|
||||||
unit.unit_ps.publish("auto_group", types.AUTO_GROUP_NAMES[unit.a_group + 1])
|
|
||||||
|
|
||||||
local control_status = 1
|
|
||||||
|
|
||||||
if unit.connected then
|
|
||||||
if unit.reactor_data.rps_tripped then
|
|
||||||
control_status = 2
|
|
||||||
end
|
|
||||||
|
|
||||||
if unit.reactor_data.mek_status.status then
|
|
||||||
control_status = util.trinary(unit.annunciator.AutoControl, 4, 3)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
unit.unit_ps.publish("U_ControlStatus", control_status)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- update process app with unit data from API_GET_PROC
|
|
||||||
---@param data table
|
|
||||||
function iocontrol.record_process_data(data)
|
|
||||||
-- get unit data
|
|
||||||
for u_id = 1, #io.units do
|
|
||||||
local unit = io.units[u_id]
|
|
||||||
local u_data = data[u_id]
|
|
||||||
|
|
||||||
unit.reactor_data.mek_status.status = u_data[1]
|
|
||||||
unit.reactor_data.mek_struct.max_burn = u_data[2]
|
|
||||||
unit.annunciator.AutoControl = u_data[6]
|
|
||||||
unit.a_group = u_data[7]
|
|
||||||
|
|
||||||
unit.unit_ps.publish("status", u_data[1])
|
|
||||||
unit.unit_ps.publish("max_burn", u_data[2])
|
|
||||||
unit.unit_ps.publish("burn_limit", u_data[3])
|
|
||||||
unit.unit_ps.publish("U_AutoReady", u_data[4])
|
|
||||||
unit.unit_ps.publish("U_AutoDegraded", u_data[5])
|
|
||||||
unit.unit_ps.publish("AutoControl", u_data[6])
|
|
||||||
unit.unit_ps.publish("auto_group_id", unit.a_group)
|
|
||||||
unit.unit_ps.publish("auto_group", types.AUTO_GROUP_NAMES[unit.a_group + 1])
|
|
||||||
end
|
|
||||||
|
|
||||||
-- get facility data
|
|
||||||
local fac = io.facility
|
|
||||||
local f_data = data[#io.units + 1]
|
|
||||||
|
|
||||||
fac.status_lines = f_data[1]
|
|
||||||
|
|
||||||
fac.auto_ready = f_data[2][1]
|
|
||||||
fac.auto_active = f_data[2][2]
|
|
||||||
fac.auto_ramping = f_data[2][3]
|
|
||||||
fac.auto_saturated = f_data[2][4]
|
|
||||||
|
|
||||||
fac.auto_scram = f_data[3]
|
|
||||||
fac.ascram_status = f_data[4]
|
|
||||||
|
|
||||||
fac.ps.publish("status_line_1", fac.status_lines[1])
|
|
||||||
fac.ps.publish("status_line_2", fac.status_lines[2])
|
|
||||||
|
|
||||||
fac.ps.publish("auto_ready", fac.auto_ready)
|
|
||||||
fac.ps.publish("auto_active", fac.auto_active)
|
|
||||||
fac.ps.publish("auto_ramping", fac.auto_ramping)
|
|
||||||
fac.ps.publish("auto_saturated", fac.auto_saturated)
|
|
||||||
|
|
||||||
fac.ps.publish("auto_scram", fac.auto_scram)
|
|
||||||
fac.ps.publish("as_matrix_dc", fac.ascram_status.matrix_dc)
|
|
||||||
fac.ps.publish("as_matrix_fill", fac.ascram_status.matrix_fill)
|
|
||||||
fac.ps.publish("as_crit_alarm", fac.ascram_status.crit_alarm)
|
|
||||||
fac.ps.publish("as_radiation", fac.ascram_status.radiation)
|
|
||||||
fac.ps.publish("as_gen_fault", fac.ascram_status.gen_fault)
|
|
||||||
|
|
||||||
fac.ps.publish("process_mode", f_data[5][1])
|
|
||||||
fac.ps.publish("process_burn_target", f_data[5][2])
|
|
||||||
fac.ps.publish("process_charge_target", f_data[5][3])
|
|
||||||
fac.ps.publish("process_gen_target", f_data[5][4])
|
|
||||||
end
|
|
||||||
|
|
||||||
-- get the IO controller database
|
-- get the IO controller database
|
||||||
function iocontrol.get_db() return io end
|
function iocontrol.get_db() return io end
|
||||||
|
|
||||||
|
|||||||
877
pocket/iorx.lua
Normal file
877
pocket/iorx.lua
Normal file
@@ -0,0 +1,877 @@
|
|||||||
|
--
|
||||||
|
-- I/O Control's Data Receive (Rx) Handlers
|
||||||
|
--
|
||||||
|
|
||||||
|
local const = require("scada-common.constants")
|
||||||
|
local types = require("scada-common.types")
|
||||||
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
|
local ALARM = types.ALARM
|
||||||
|
local ALARM_STATE = types.ALARM_STATE
|
||||||
|
|
||||||
|
local BLR_STATE = types.BOILER_STATE
|
||||||
|
local TRB_STATE = types.TURBINE_STATE
|
||||||
|
local TNK_STATE = types.TANK_STATE
|
||||||
|
local MTX_STATE = types.IMATRIX_STATE
|
||||||
|
local SPS_STATE = types.SPS_STATE
|
||||||
|
|
||||||
|
local io ---@type pocket_ioctl
|
||||||
|
local iorx = {} ---@class iorx
|
||||||
|
|
||||||
|
-- populate facility data from API_GET_FAC
|
||||||
|
---@param data table
|
||||||
|
---@return boolean valid
|
||||||
|
function iorx.record_facility_data(data)
|
||||||
|
local valid = true
|
||||||
|
|
||||||
|
local fac = io.facility
|
||||||
|
|
||||||
|
fac.all_sys_ok = data[1]
|
||||||
|
fac.rtu_count = data[2]
|
||||||
|
fac.radiation = data[3]
|
||||||
|
|
||||||
|
-- auto control
|
||||||
|
if type(data[4]) == "table" and #data[4] == 4 then
|
||||||
|
fac.auto_ready = data[4][1]
|
||||||
|
fac.auto_active = data[4][2]
|
||||||
|
fac.auto_ramping = data[4][3]
|
||||||
|
fac.auto_saturated = data[4][4]
|
||||||
|
end
|
||||||
|
|
||||||
|
-- waste
|
||||||
|
if type(data[5]) == "table" and #data[5] == 2 then
|
||||||
|
fac.auto_current_waste_product = data[5][1]
|
||||||
|
fac.auto_pu_fallback_active = data[5][2]
|
||||||
|
end
|
||||||
|
|
||||||
|
fac.num_tanks = data[6]
|
||||||
|
fac.has_imatrix = data[7]
|
||||||
|
fac.has_sps = data[8]
|
||||||
|
|
||||||
|
return valid
|
||||||
|
end
|
||||||
|
|
||||||
|
local function tripped(state) return state == ALARM_STATE.TRIPPED or state == ALARM_STATE.ACKED end
|
||||||
|
|
||||||
|
local function _record_multiblock_status(faulted, data, ps)
|
||||||
|
ps.publish("formed", data.formed)
|
||||||
|
ps.publish("faulted", faulted)
|
||||||
|
|
||||||
|
---@todo revisit this
|
||||||
|
if data.build then
|
||||||
|
for key, val in pairs(data.build) do ps.publish(key, val) end
|
||||||
|
end
|
||||||
|
|
||||||
|
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
|
||||||
|
end
|
||||||
|
|
||||||
|
-- update unit status data from API_GET_UNIT
|
||||||
|
---@param data table
|
||||||
|
function iorx.record_unit_data(data)
|
||||||
|
local unit = io.units[data[1]]
|
||||||
|
|
||||||
|
unit.connected = data[2]
|
||||||
|
local comp_statuses = data[3]
|
||||||
|
unit.a_group = data[4]
|
||||||
|
unit.alarms = data[5]
|
||||||
|
|
||||||
|
local next_c_stat = 1
|
||||||
|
|
||||||
|
unit.unit_ps.publish("auto_group_id", unit.a_group)
|
||||||
|
unit.unit_ps.publish("auto_group", types.AUTO_GROUP_NAMES[unit.a_group + 1])
|
||||||
|
|
||||||
|
--#region Annunciator
|
||||||
|
|
||||||
|
unit.annunciator = data[6]
|
||||||
|
|
||||||
|
local rcs_disconn, rcs_warn, rcs_hazard = false, false, false
|
||||||
|
|
||||||
|
for key, val in pairs(unit.annunciator) do
|
||||||
|
if key == "BoilerOnline" or key == "TurbineOnline" then
|
||||||
|
local every = true
|
||||||
|
|
||||||
|
-- split up online arrays
|
||||||
|
for id = 1, #val do
|
||||||
|
every = every and val[id]
|
||||||
|
|
||||||
|
if key == "BoilerOnline" then
|
||||||
|
unit.boiler_ps_tbl[id].publish(key, val[id])
|
||||||
|
else
|
||||||
|
unit.turbine_ps_tbl[id].publish(key, val[id])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not every then rcs_disconn = true end
|
||||||
|
|
||||||
|
unit.unit_ps.publish("U_" .. key, every)
|
||||||
|
elseif key == "HeatingRateLow" or key == "WaterLevelLow" then
|
||||||
|
-- split up array for all boilers
|
||||||
|
local any = false
|
||||||
|
for id = 1, #val do
|
||||||
|
any = any or val[id]
|
||||||
|
unit.boiler_ps_tbl[id].publish(key, val[id])
|
||||||
|
end
|
||||||
|
|
||||||
|
if key == "HeatingRateLow" and any then
|
||||||
|
rcs_warn = true
|
||||||
|
elseif key == "WaterLevelLow" and any then
|
||||||
|
rcs_hazard = true
|
||||||
|
end
|
||||||
|
|
||||||
|
unit.unit_ps.publish("U_" .. key, any)
|
||||||
|
elseif key == "SteamDumpOpen" or key == "TurbineOverSpeed" or key == "GeneratorTrip" or key == "TurbineTrip" then
|
||||||
|
-- split up array for all turbines
|
||||||
|
local any = false
|
||||||
|
for id = 1, #val do
|
||||||
|
any = any or val[id]
|
||||||
|
unit.turbine_ps_tbl[id].publish(key, val[id])
|
||||||
|
end
|
||||||
|
|
||||||
|
if key == "GeneratorTrip" and any then
|
||||||
|
rcs_warn = true
|
||||||
|
elseif (key == "TurbineOverSpeed" or key == "TurbineTrip") and any then
|
||||||
|
rcs_hazard = true
|
||||||
|
end
|
||||||
|
|
||||||
|
unit.unit_ps.publish("U_" .. key, any)
|
||||||
|
else
|
||||||
|
-- non-table fields
|
||||||
|
unit.unit_ps.publish(key, val)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local anc = unit.annunciator
|
||||||
|
rcs_hazard = rcs_hazard or anc.RCPTrip
|
||||||
|
rcs_warn = rcs_warn or anc.RCSFlowLow or anc.CoolantLevelLow or anc.RCSFault or anc.MaxWaterReturnFeed or
|
||||||
|
anc.CoolantFeedMismatch or anc.BoilRateMismatch or anc.SteamFeedMismatch
|
||||||
|
|
||||||
|
local rcs_status = 4
|
||||||
|
if rcs_hazard then
|
||||||
|
rcs_status = 2
|
||||||
|
elseif rcs_warn then
|
||||||
|
rcs_status = 3
|
||||||
|
elseif rcs_disconn then
|
||||||
|
rcs_status = 1
|
||||||
|
end
|
||||||
|
|
||||||
|
unit.unit_ps.publish("U_RCS", rcs_status)
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
|
--#region Reactor Data
|
||||||
|
|
||||||
|
unit.reactor_data = data[7]
|
||||||
|
|
||||||
|
local control_status = 1
|
||||||
|
local reactor_status = 1
|
||||||
|
local rps_status = 1
|
||||||
|
|
||||||
|
if unit.connected then
|
||||||
|
-- update RPS status
|
||||||
|
if unit.reactor_data.rps_tripped then
|
||||||
|
control_status = 2
|
||||||
|
rps_status = util.trinary(unit.reactor_data.rps_trip_cause == "manual", 3, 2)
|
||||||
|
else
|
||||||
|
rps_status = 4
|
||||||
|
end
|
||||||
|
|
||||||
|
reactor_status = 4 -- ok, until proven otherwise
|
||||||
|
|
||||||
|
-- update reactor/control status
|
||||||
|
if unit.reactor_data.mek_status.status then
|
||||||
|
control_status = util.trinary(unit.annunciator.AutoControl, 4, 3)
|
||||||
|
else
|
||||||
|
if unit.reactor_data.no_reactor then
|
||||||
|
reactor_status = 2
|
||||||
|
elseif (not unit.reactor_data.formed) or unit.reactor_data.rps_status.force_dis then
|
||||||
|
reactor_status = 3
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for key, val in pairs(unit.reactor_data) do
|
||||||
|
if key ~= "rps_status" and key ~= "mek_struct" and key ~= "mek_status" then
|
||||||
|
unit.unit_ps.publish(key, val)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for key, val in pairs(unit.reactor_data.rps_status) do
|
||||||
|
unit.unit_ps.publish(key, val)
|
||||||
|
end
|
||||||
|
|
||||||
|
for key, val in pairs(unit.reactor_data.mek_struct) do
|
||||||
|
unit.unit_ps.publish(key, val)
|
||||||
|
end
|
||||||
|
|
||||||
|
for key, val in pairs(unit.reactor_data.mek_status) do
|
||||||
|
unit.unit_ps.publish(key, val)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
unit.unit_ps.publish("U_ControlStatus", control_status)
|
||||||
|
unit.unit_ps.publish("U_ReactorStatus", reactor_status)
|
||||||
|
unit.unit_ps.publish("U_ReactorStateStatus", comp_statuses[next_c_stat])
|
||||||
|
unit.unit_ps.publish("U_RPS", rps_status)
|
||||||
|
|
||||||
|
next_c_stat = next_c_stat + 1
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
|
--#region RTU Devices
|
||||||
|
|
||||||
|
unit.boiler_data_tbl = data[8]
|
||||||
|
|
||||||
|
for id = 1, #unit.boiler_data_tbl do
|
||||||
|
local boiler = unit.boiler_data_tbl[id]
|
||||||
|
local ps = unit.boiler_ps_tbl[id]
|
||||||
|
local c_stat = comp_statuses[next_c_stat]
|
||||||
|
|
||||||
|
local boiler_status = 1
|
||||||
|
|
||||||
|
if c_stat ~= BLR_STATE.OFFLINE then
|
||||||
|
if c_stat == BLR_STATE.FAULT then
|
||||||
|
boiler_status = 3
|
||||||
|
elseif c_stat ~= BLR_STATE.UNFORMED then
|
||||||
|
boiler_status = 4
|
||||||
|
else
|
||||||
|
boiler_status = 2
|
||||||
|
end
|
||||||
|
|
||||||
|
_record_multiblock_status(c_stat == BLR_STATE.FAULT, boiler, ps)
|
||||||
|
end
|
||||||
|
|
||||||
|
ps.publish("BoilerStatus", boiler_status)
|
||||||
|
ps.publish("BoilerStateStatus", c_stat)
|
||||||
|
|
||||||
|
next_c_stat = next_c_stat + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
unit.turbine_data_tbl = data[9]
|
||||||
|
|
||||||
|
for id = 1, #unit.turbine_data_tbl do
|
||||||
|
local turbine = unit.turbine_data_tbl[id]
|
||||||
|
local ps = unit.turbine_ps_tbl[id]
|
||||||
|
local c_stat = comp_statuses[next_c_stat]
|
||||||
|
|
||||||
|
local turbine_status = 1
|
||||||
|
|
||||||
|
if c_stat ~= TRB_STATE.OFFLINE then
|
||||||
|
if c_stat == TRB_STATE.FAULT then
|
||||||
|
turbine_status = 3
|
||||||
|
elseif turbine.formed then
|
||||||
|
turbine_status = 4
|
||||||
|
else
|
||||||
|
turbine_status = 2
|
||||||
|
end
|
||||||
|
|
||||||
|
_record_multiblock_status(c_stat == TRB_STATE.FAULT, turbine, ps)
|
||||||
|
end
|
||||||
|
|
||||||
|
ps.publish("TurbineStatus", turbine_status)
|
||||||
|
ps.publish("TurbineStateStatus", c_stat)
|
||||||
|
|
||||||
|
next_c_stat = next_c_stat + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
unit.tank_data_tbl = data[10]
|
||||||
|
|
||||||
|
for id = 1, #unit.tank_data_tbl do
|
||||||
|
local tank = unit.tank_data_tbl[id]
|
||||||
|
local ps = unit.tank_ps_tbl[id]
|
||||||
|
local c_stat = comp_statuses[next_c_stat]
|
||||||
|
|
||||||
|
local tank_status = 1
|
||||||
|
|
||||||
|
if c_stat ~= TNK_STATE.OFFLINE then
|
||||||
|
if c_stat == TNK_STATE.FAULT then
|
||||||
|
tank_status = 3
|
||||||
|
elseif tank.formed then
|
||||||
|
tank_status = 4
|
||||||
|
else
|
||||||
|
tank_status = 2
|
||||||
|
end
|
||||||
|
|
||||||
|
_record_multiblock_status(c_stat == TNK_STATE.FAULT, tank, ps)
|
||||||
|
end
|
||||||
|
|
||||||
|
ps.publish("DynamicTankStatus", tank_status)
|
||||||
|
ps.publish("DynamicTankStateStatus", c_stat)
|
||||||
|
|
||||||
|
next_c_stat = next_c_stat + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
unit.last_rate_change_ms = data[11]
|
||||||
|
unit.turbine_flow_stable = data[12]
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
|
--#region Status Information Display
|
||||||
|
|
||||||
|
local ecam = {} -- aviation reference :)
|
||||||
|
|
||||||
|
-- local function red(text) return { text = text, color = colors.red } end
|
||||||
|
local function white(text) return { text = text, color = colors.white } end
|
||||||
|
local function blue(text) return { text = text, color = colors.blue } end
|
||||||
|
|
||||||
|
-- if unit.reactor_data.rps_status then
|
||||||
|
-- for k, _ in pairs(unit.alarms) do
|
||||||
|
-- unit.alarms[k] = ALARM_STATE.TRIPPED
|
||||||
|
-- end
|
||||||
|
-- end
|
||||||
|
|
||||||
|
if tripped(unit.alarms[ALARM.ContainmentBreach]) then
|
||||||
|
local items = { white("REACTOR MELTDOWN"), blue("DON HAZMAT SUIT") }
|
||||||
|
table.insert(ecam, { color = colors.red, text = "CONTAINMENT BREACH", help = "ContainmentBreach", items = items })
|
||||||
|
end
|
||||||
|
|
||||||
|
if tripped(unit.alarms[ALARM.ContainmentRadiation]) then
|
||||||
|
local items = {
|
||||||
|
white("RADIATION DETECTED"),
|
||||||
|
blue("DON HAZMAT SUIT"),
|
||||||
|
blue("RESOLVE LEAK"),
|
||||||
|
blue("AWAIT SAFE LEVELS")
|
||||||
|
}
|
||||||
|
|
||||||
|
table.insert(ecam, { color = colors.red, text = "RADIATION LEAK", help = "ContainmentRadiation", items = items })
|
||||||
|
end
|
||||||
|
|
||||||
|
if tripped(unit.alarms[ALARM.CriticalDamage]) then
|
||||||
|
local items = { white("MELTDOWN IMMINENT"), blue("EVACUATE") }
|
||||||
|
table.insert(ecam, { color = colors.red, text = "RCT DAMAGE CRITICAL", help = "CriticalDamage", items = items })
|
||||||
|
end
|
||||||
|
|
||||||
|
if tripped(unit.alarms[ALARM.ReactorLost]) then
|
||||||
|
local items = { white("REACTOR OFF-LINE"), blue("CHECK PLC") }
|
||||||
|
table.insert(ecam, { color = colors.red, text = "REACTOR CONN LOST", help = "ReactorLost", items = items })
|
||||||
|
end
|
||||||
|
|
||||||
|
if tripped(unit.alarms[ALARM.ReactorDamage]) then
|
||||||
|
local items = { white("REACTOR DAMAGED"), blue("CHECK RCS"), blue("AWAIT DMG REDUCED") }
|
||||||
|
table.insert(ecam, { color = colors.red, text = "REACTOR DAMAGE", help = "ReactorDamage", items = items })
|
||||||
|
end
|
||||||
|
|
||||||
|
if tripped(unit.alarms[ALARM.ReactorOverTemp]) then
|
||||||
|
local items = { white("DAMAGING TEMP"), blue("CHECK RCS"), blue("AWAIT COOLDOWN") }
|
||||||
|
table.insert(ecam, { color = colors.red, text = "REACTOR OVER TEMP", help = "ReactorOverTemp", items = items })
|
||||||
|
end
|
||||||
|
|
||||||
|
if tripped(unit.alarms[ALARM.ReactorHighTemp]) then
|
||||||
|
local items = { white("OVER EXPECTED TEMP"), blue("CHECK RCS") }
|
||||||
|
table.insert(ecam, { color = colors.yellow, text = "REACTOR HIGH TEMP", help = "ReactorHighTemp", items = items})
|
||||||
|
end
|
||||||
|
|
||||||
|
if tripped(unit.alarms[ALARM.ReactorWasteLeak]) then
|
||||||
|
local items = { white("AT WASTE CAPACITY"), blue("CHECK WASTE OUTPUT"), blue("KEEP RCT DISABLED") }
|
||||||
|
table.insert(ecam, { color = colors.red, text = "REACTOR WASTE LEAK", help = "ReactorWasteLeak", items = items})
|
||||||
|
end
|
||||||
|
|
||||||
|
if tripped(unit.alarms[ALARM.ReactorHighWaste]) then
|
||||||
|
local items = { blue("CHECK WASTE OUTPUT") }
|
||||||
|
table.insert(ecam, { color = colors.yellow, text = "REACTOR WASTE HIGH", help = "ReactorHighWaste", items = items})
|
||||||
|
end
|
||||||
|
|
||||||
|
if tripped(unit.alarms[ALARM.RPSTransient]) then
|
||||||
|
local items = {}
|
||||||
|
local stat = unit.reactor_data.rps_status
|
||||||
|
|
||||||
|
-- for k, _ in pairs(stat) do stat[k] = true end
|
||||||
|
|
||||||
|
local function insert(cond, key, text, color) if cond[key] then table.insert(items, { text = text, help = key, color = color }) end end
|
||||||
|
|
||||||
|
table.insert(items, white("REACTOR SCRAMMED"))
|
||||||
|
insert(stat, "high_dmg", "HIGH DAMAGE", colors.red)
|
||||||
|
insert(stat, "high_temp", "HIGH TEMPERATURE", colors.red)
|
||||||
|
insert(stat, "low_cool", "CRIT LOW COOLANT")
|
||||||
|
insert(stat, "ex_waste", "EXCESS WASTE")
|
||||||
|
insert(stat, "ex_hcool", "EXCESS HEATED COOL")
|
||||||
|
insert(stat, "no_fuel", "NO FUEL")
|
||||||
|
insert(stat, "fault", "HARDWARE FAULT")
|
||||||
|
insert(stat, "timeout", "SUPERVISOR DISCONN")
|
||||||
|
insert(stat, "manual", "MANUAL SCRAM", colors.white)
|
||||||
|
insert(stat, "automatic", "AUTOMATIC SCRAM")
|
||||||
|
insert(stat, "sys_fail", "NOT FORMED", colors.red)
|
||||||
|
insert(stat, "force_dis", "FORCE DISABLED", colors.red)
|
||||||
|
table.insert(items, blue("RESOLVE PROBLEM"))
|
||||||
|
table.insert(items, blue("RESET RPS"))
|
||||||
|
|
||||||
|
table.insert(ecam, { color = colors.yellow, text = "RPS TRANSIENT", help = "RPSTransient", items = items})
|
||||||
|
end
|
||||||
|
|
||||||
|
if tripped(unit.alarms[ALARM.RCSTransient]) then
|
||||||
|
local items = {}
|
||||||
|
local annunc = unit.annunciator
|
||||||
|
|
||||||
|
-- for k, v in pairs(annunc) do
|
||||||
|
-- if type(v) == "boolean" then annunc[k] = true end
|
||||||
|
-- if type(v) == "table" then
|
||||||
|
-- for a, _ in pairs(v) do
|
||||||
|
-- v[a] = true
|
||||||
|
-- end
|
||||||
|
-- end
|
||||||
|
-- end
|
||||||
|
|
||||||
|
local function insert(cond, key, text, color)
|
||||||
|
if cond == true or (type(cond) == "table" and cond[key]) then table.insert(items, { text = text, help = key, color = color }) end
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(items, white("COOLANT PROBLEM"))
|
||||||
|
|
||||||
|
insert(annunc, "RCPTrip", "RCP TRIP", colors.red)
|
||||||
|
insert(annunc, "CoolantLevelLow", "LOW COOLANT")
|
||||||
|
|
||||||
|
if unit.num_boilers == 0 then
|
||||||
|
if (util.time_ms() - unit.last_rate_change_ms) > const.FLOW_STABILITY_DELAY_MS then
|
||||||
|
insert(annunc, "BoilRateMismatch", "BOIL RATE MISMATCH")
|
||||||
|
end
|
||||||
|
|
||||||
|
if unit.turbine_flow_stable then
|
||||||
|
insert(annunc, "RCSFlowLow", "RCS FLOW LOW")
|
||||||
|
insert(annunc, "CoolantFeedMismatch", "COOL FEED MISMATCH")
|
||||||
|
insert(annunc, "SteamFeedMismatch", "STM FEED MISMATCH")
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if (util.time_ms() - unit.last_rate_change_ms) > const.FLOW_STABILITY_DELAY_MS then
|
||||||
|
insert(annunc, "RCSFlowLow", "RCS FLOW LOW")
|
||||||
|
insert(annunc, "BoilRateMismatch", "BOIL RATE MISMATCH")
|
||||||
|
insert(annunc, "CoolantFeedMismatch", "COOL FEED MISMATCH")
|
||||||
|
end
|
||||||
|
|
||||||
|
if unit.turbine_flow_stable then
|
||||||
|
insert(annunc, "SteamFeedMismatch", "STM FEED MISMATCH")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
insert(annunc, "MaxWaterReturnFeed", "MAX WTR RTRN FEED")
|
||||||
|
|
||||||
|
for k, v in ipairs(annunc.WaterLevelLow) do insert(v, "WaterLevelLow", "BOILER " .. k .. " WTR LOW", colors.red) end
|
||||||
|
for k, v in ipairs(annunc.HeatingRateLow) do insert(v, "HeatingRateLow", "BOILER " .. k .. " HEAT RATE") end
|
||||||
|
for k, v in ipairs(annunc.TurbineOverSpeed) do insert(v, "TurbineOverSpeed", "TURBINE " .. k .. " OVERSPD", colors.red) end
|
||||||
|
for k, v in ipairs(annunc.GeneratorTrip) do insert(v, "GeneratorTrip", "TURBINE " .. k .. " GEN TRIP") end
|
||||||
|
|
||||||
|
table.insert(items, blue("CHECK COOLING SYS"))
|
||||||
|
|
||||||
|
table.insert(ecam, { color = colors.yellow, text = "RCS TRANSIENT", help = "RCSTransient", items = items})
|
||||||
|
end
|
||||||
|
|
||||||
|
if tripped(unit.alarms[ALARM.TurbineTrip]) then
|
||||||
|
local items = {}
|
||||||
|
|
||||||
|
for k, v in ipairs(unit.annunciator.TurbineTrip) do
|
||||||
|
if v then table.insert(items, { text = "TURBINE " .. k .. " TRIP", help = "TurbineTrip" }) end
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(items, blue("CHECK ENERGY OUT"))
|
||||||
|
table.insert(ecam, { color = colors.red, text = "TURBINE TRIP", help = "TurbineTripAlarm", items = items})
|
||||||
|
end
|
||||||
|
|
||||||
|
if not (tripped(unit.alarms[ALARM.ReactorLost]) or unit.connected) then
|
||||||
|
local items = { blue("CHECK PLC") }
|
||||||
|
table.insert(ecam, { color = colors.yellow, text = "REACTOR OFF-LINE", items = items })
|
||||||
|
end
|
||||||
|
|
||||||
|
for k, v in ipairs(unit.annunciator.BoilerOnline) do
|
||||||
|
if not v then
|
||||||
|
local items = { blue("CHECK RTU") }
|
||||||
|
table.insert(ecam, { color = colors.yellow, text = "BOILER " .. k .. " OFF-LINE", items = items})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for k, v in ipairs(unit.annunciator.TurbineOnline) do
|
||||||
|
if not v then
|
||||||
|
local items = { blue("CHECK RTU") }
|
||||||
|
table.insert(ecam, { color = colors.yellow, text = "TURBINE " .. k .. " OFF-LINE", items = items})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- if no alarms, put some basic status messages in
|
||||||
|
if #ecam == 0 then
|
||||||
|
table.insert(ecam, { color = colors.green, text = "REACTOR " .. util.trinary(unit.reactor_data.mek_status.status, "NOMINAL", "IDLE"), items = {}})
|
||||||
|
|
||||||
|
local plural = util.trinary(unit.num_turbines > 1, "S", "")
|
||||||
|
table.insert(ecam, { color = colors.green, text = "TURBINE" .. plural .. util.trinary(unit.turbine_flow_stable, " STABLE", " STABILIZING"), items = {}})
|
||||||
|
end
|
||||||
|
|
||||||
|
unit.unit_ps.publish("U_ECAM", textutils.serialize(ecam))
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
end
|
||||||
|
|
||||||
|
-- update control app with unit data from API_GET_CTRL
|
||||||
|
---@param data table
|
||||||
|
function iorx.record_control_data(data)
|
||||||
|
for u_id = 1, #data do
|
||||||
|
local unit = io.units[u_id]
|
||||||
|
local u_data = data[u_id]
|
||||||
|
|
||||||
|
unit.connected = u_data[1]
|
||||||
|
|
||||||
|
unit.reactor_data.rps_tripped = u_data[2]
|
||||||
|
unit.unit_ps.publish("rps_tripped", u_data[2])
|
||||||
|
unit.reactor_data.mek_status.status = u_data[3]
|
||||||
|
unit.unit_ps.publish("status", u_data[3])
|
||||||
|
unit.reactor_data.mek_status.temp = u_data[4]
|
||||||
|
unit.unit_ps.publish("temp", u_data[4])
|
||||||
|
unit.reactor_data.mek_status.burn_rate = u_data[5]
|
||||||
|
unit.unit_ps.publish("burn_rate", u_data[5])
|
||||||
|
unit.reactor_data.mek_status.act_burn_rate = u_data[6]
|
||||||
|
unit.unit_ps.publish("act_burn_rate", u_data[6])
|
||||||
|
unit.reactor_data.mek_struct.max_burn = u_data[7]
|
||||||
|
unit.unit_ps.publish("max_burn", u_data[7])
|
||||||
|
|
||||||
|
unit.annunciator.AutoControl = u_data[8]
|
||||||
|
unit.unit_ps.publish("AutoControl", u_data[8])
|
||||||
|
|
||||||
|
unit.a_group = u_data[9]
|
||||||
|
unit.unit_ps.publish("auto_group_id", unit.a_group)
|
||||||
|
unit.unit_ps.publish("auto_group", types.AUTO_GROUP_NAMES[unit.a_group + 1])
|
||||||
|
|
||||||
|
local control_status = 1
|
||||||
|
|
||||||
|
if unit.connected then
|
||||||
|
if unit.reactor_data.rps_tripped then
|
||||||
|
control_status = 2
|
||||||
|
end
|
||||||
|
|
||||||
|
if unit.reactor_data.mek_status.status then
|
||||||
|
control_status = util.trinary(unit.annunciator.AutoControl, 4, 3)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
unit.unit_ps.publish("U_ControlStatus", control_status)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- update process app with unit data from API_GET_PROC
|
||||||
|
---@param data table
|
||||||
|
function iorx.record_process_data(data)
|
||||||
|
-- get unit data
|
||||||
|
for u_id = 1, #io.units do
|
||||||
|
local unit = io.units[u_id]
|
||||||
|
local u_data = data[u_id]
|
||||||
|
|
||||||
|
unit.reactor_data.mek_status.status = u_data[1]
|
||||||
|
unit.reactor_data.mek_struct.max_burn = u_data[2]
|
||||||
|
unit.annunciator.AutoControl = u_data[6]
|
||||||
|
unit.a_group = u_data[7]
|
||||||
|
|
||||||
|
unit.unit_ps.publish("status", u_data[1])
|
||||||
|
unit.unit_ps.publish("max_burn", u_data[2])
|
||||||
|
unit.unit_ps.publish("burn_limit", u_data[3])
|
||||||
|
unit.unit_ps.publish("U_AutoReady", u_data[4])
|
||||||
|
unit.unit_ps.publish("U_AutoDegraded", u_data[5])
|
||||||
|
unit.unit_ps.publish("AutoControl", u_data[6])
|
||||||
|
unit.unit_ps.publish("auto_group_id", unit.a_group)
|
||||||
|
unit.unit_ps.publish("auto_group", types.AUTO_GROUP_NAMES[unit.a_group + 1])
|
||||||
|
end
|
||||||
|
|
||||||
|
-- get facility data
|
||||||
|
local fac = io.facility
|
||||||
|
local f_data = data[#io.units + 1]
|
||||||
|
|
||||||
|
fac.status_lines = f_data[1]
|
||||||
|
|
||||||
|
fac.auto_ready = f_data[2][1]
|
||||||
|
fac.auto_active = f_data[2][2]
|
||||||
|
fac.auto_ramping = f_data[2][3]
|
||||||
|
fac.auto_saturated = f_data[2][4]
|
||||||
|
|
||||||
|
fac.auto_scram = f_data[3]
|
||||||
|
fac.ascram_status = f_data[4]
|
||||||
|
|
||||||
|
fac.ps.publish("status_line_1", fac.status_lines[1])
|
||||||
|
fac.ps.publish("status_line_2", fac.status_lines[2])
|
||||||
|
|
||||||
|
fac.ps.publish("auto_ready", fac.auto_ready)
|
||||||
|
fac.ps.publish("auto_active", fac.auto_active)
|
||||||
|
fac.ps.publish("auto_ramping", fac.auto_ramping)
|
||||||
|
fac.ps.publish("auto_saturated", fac.auto_saturated)
|
||||||
|
|
||||||
|
fac.ps.publish("auto_scram", fac.auto_scram)
|
||||||
|
fac.ps.publish("as_matrix_fault", fac.ascram_status.matrix_fault)
|
||||||
|
fac.ps.publish("as_matrix_fill", fac.ascram_status.matrix_fill)
|
||||||
|
fac.ps.publish("as_crit_alarm", fac.ascram_status.crit_alarm)
|
||||||
|
fac.ps.publish("as_radiation", fac.ascram_status.radiation)
|
||||||
|
fac.ps.publish("as_gen_fault", fac.ascram_status.gen_fault)
|
||||||
|
|
||||||
|
fac.ps.publish("process_mode", f_data[5][1])
|
||||||
|
fac.ps.publish("process_burn_target", f_data[5][2])
|
||||||
|
fac.ps.publish("process_charge_target", f_data[5][3])
|
||||||
|
fac.ps.publish("process_gen_target", f_data[5][4])
|
||||||
|
end
|
||||||
|
|
||||||
|
-- update waste app with unit data from API_GET_WASTE
|
||||||
|
---@param data table
|
||||||
|
function iorx.record_waste_data(data)
|
||||||
|
-- get unit data
|
||||||
|
for u_id = 1, #io.units do
|
||||||
|
local unit = io.units[u_id]
|
||||||
|
local u_data = data[u_id]
|
||||||
|
|
||||||
|
unit.waste_mode = u_data[1]
|
||||||
|
unit.waste_product = u_data[2]
|
||||||
|
unit.num_snas = u_data[3]
|
||||||
|
unit.sna_peak_rate = u_data[4]
|
||||||
|
unit.sna_max_rate = u_data[5]
|
||||||
|
unit.sna_out_rate = u_data[6]
|
||||||
|
unit.waste_stats = u_data[7]
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
unit.unit_ps.publish("sna_count", unit.num_snas)
|
||||||
|
unit.unit_ps.publish("sna_peak_rate", unit.sna_peak_rate)
|
||||||
|
unit.unit_ps.publish("sna_max_rate", unit.sna_max_rate)
|
||||||
|
unit.unit_ps.publish("sna_out_rate", unit.sna_out_rate)
|
||||||
|
|
||||||
|
unit.unit_ps.publish("pu_rate", unit.waste_stats[1])
|
||||||
|
unit.unit_ps.publish("po_rate", unit.waste_stats[2])
|
||||||
|
unit.unit_ps.publish("po_pl_rate", unit.waste_stats[3])
|
||||||
|
end
|
||||||
|
|
||||||
|
-- get facility data
|
||||||
|
local fac = io.facility
|
||||||
|
local f_data = data[#io.units + 1]
|
||||||
|
|
||||||
|
fac.auto_current_waste_product = f_data[1]
|
||||||
|
fac.auto_pu_fallback_active = f_data[2]
|
||||||
|
fac.auto_sps_disabled = f_data[3]
|
||||||
|
|
||||||
|
fac.ps.publish("current_waste_product", fac.auto_current_waste_product)
|
||||||
|
fac.ps.publish("pu_fallback_active", fac.auto_pu_fallback_active)
|
||||||
|
fac.ps.publish("sps_disabled_low_power", fac.auto_sps_disabled)
|
||||||
|
|
||||||
|
fac.ps.publish("process_waste_product", f_data[4])
|
||||||
|
fac.ps.publish("process_pu_fallback", f_data[5])
|
||||||
|
fac.ps.publish("process_sps_low_power", f_data[6])
|
||||||
|
|
||||||
|
fac.waste_stats = f_data[7]
|
||||||
|
|
||||||
|
fac.ps.publish("burn_sum", fac.waste_stats[1])
|
||||||
|
fac.ps.publish("pu_rate", fac.waste_stats[2])
|
||||||
|
fac.ps.publish("po_rate", fac.waste_stats[3])
|
||||||
|
fac.ps.publish("po_pl_rate", fac.waste_stats[4])
|
||||||
|
fac.ps.publish("po_am_rate", fac.waste_stats[5])
|
||||||
|
fac.ps.publish("spent_waste_rate", fac.waste_stats[6])
|
||||||
|
|
||||||
|
fac.sps_ps_tbl[1].publish("SPSStateStatus", f_data[8])
|
||||||
|
fac.ps.publish("sps_process_rate", f_data[9])
|
||||||
|
end
|
||||||
|
|
||||||
|
-- update facility app with facility and unit data from API_GET_FAC_DTL
|
||||||
|
---@param data table
|
||||||
|
function iorx.record_fac_detail_data(data)
|
||||||
|
local fac = io.facility
|
||||||
|
|
||||||
|
local tank_statuses = data[5]
|
||||||
|
local next_t_stat = 1
|
||||||
|
|
||||||
|
-- annunciator
|
||||||
|
|
||||||
|
fac.all_sys_ok = data[1]
|
||||||
|
fac.rtu_count = data[2]
|
||||||
|
fac.auto_scram = data[3]
|
||||||
|
fac.ascram_status = data[4]
|
||||||
|
|
||||||
|
fac.ps.publish("all_sys_ok", fac.all_sys_ok)
|
||||||
|
fac.ps.publish("rtu_count", fac.rtu_count)
|
||||||
|
fac.ps.publish("auto_scram", fac.auto_scram)
|
||||||
|
fac.ps.publish("as_matrix_fault", fac.ascram_status.matrix_fault)
|
||||||
|
fac.ps.publish("as_matrix_fill", fac.ascram_status.matrix_fill)
|
||||||
|
fac.ps.publish("as_crit_alarm", fac.ascram_status.crit_alarm)
|
||||||
|
fac.ps.publish("as_radiation", fac.ascram_status.radiation)
|
||||||
|
fac.ps.publish("as_gen_fault", fac.ascram_status.gen_fault)
|
||||||
|
|
||||||
|
-- unit data
|
||||||
|
|
||||||
|
local units = data[12]
|
||||||
|
|
||||||
|
for i = 1, io.facility.num_units do
|
||||||
|
local unit = io.units[i]
|
||||||
|
local u_rx = units[i]
|
||||||
|
|
||||||
|
unit.connected = u_rx[1]
|
||||||
|
unit.annunciator = u_rx[2]
|
||||||
|
unit.reactor_data = u_rx[3]
|
||||||
|
|
||||||
|
local control_status = 1
|
||||||
|
if unit.connected then
|
||||||
|
if unit.reactor_data.rps_tripped then control_status = 2 end
|
||||||
|
if unit.reactor_data.mek_status.status then
|
||||||
|
control_status = util.trinary(unit.annunciator.AutoControl, 4, 3)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
unit.unit_ps.publish("U_ControlStatus", control_status)
|
||||||
|
|
||||||
|
unit.tank_data_tbl = u_rx[4]
|
||||||
|
|
||||||
|
for id = 1, #unit.tank_data_tbl do
|
||||||
|
local tank = unit.tank_data_tbl[id]
|
||||||
|
local ps = unit.tank_ps_tbl[id]
|
||||||
|
local c_stat = tank_statuses[next_t_stat]
|
||||||
|
|
||||||
|
local tank_status = 1
|
||||||
|
|
||||||
|
if c_stat ~= TNK_STATE.OFFLINE then
|
||||||
|
if c_stat == TNK_STATE.FAULT then
|
||||||
|
tank_status = 3
|
||||||
|
elseif tank.formed then
|
||||||
|
tank_status = 4
|
||||||
|
else
|
||||||
|
tank_status = 2
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
ps.publish("DynamicTankStatus", tank_status)
|
||||||
|
ps.publish("DynamicTankStateStatus", c_stat)
|
||||||
|
|
||||||
|
next_t_stat = next_t_stat + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- facility dynamic tank data
|
||||||
|
|
||||||
|
fac.tank_data_tbl = data[6]
|
||||||
|
|
||||||
|
for id = 1, #fac.tank_data_tbl do
|
||||||
|
local tank = fac.tank_data_tbl[id]
|
||||||
|
local ps = fac.tank_ps_tbl[id]
|
||||||
|
local c_stat = tank_statuses[next_t_stat]
|
||||||
|
|
||||||
|
local tank_status = 1
|
||||||
|
|
||||||
|
if c_stat ~= TNK_STATE.OFFLINE then
|
||||||
|
if c_stat == TNK_STATE.FAULT then
|
||||||
|
tank_status = 3
|
||||||
|
elseif tank.formed then
|
||||||
|
tank_status = 4
|
||||||
|
else
|
||||||
|
tank_status = 2
|
||||||
|
end
|
||||||
|
|
||||||
|
_record_multiblock_status(c_stat == TNK_STATE.FAULT, tank, ps)
|
||||||
|
end
|
||||||
|
|
||||||
|
ps.publish("DynamicTankStatus", tank_status)
|
||||||
|
ps.publish("DynamicTankStateStatus", c_stat)
|
||||||
|
|
||||||
|
next_t_stat = next_t_stat + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
-- induction matrix data
|
||||||
|
|
||||||
|
fac.induction_data_tbl[1] = data[8]
|
||||||
|
|
||||||
|
local matrix = fac.induction_data_tbl[1]
|
||||||
|
local m_ps = fac.induction_ps_tbl[1]
|
||||||
|
local m_stat = data[7]
|
||||||
|
|
||||||
|
local mtx_status = 1
|
||||||
|
|
||||||
|
if m_stat ~= MTX_STATE.OFFLINE then
|
||||||
|
if m_stat == MTX_STATE.FAULT then
|
||||||
|
mtx_status = 3
|
||||||
|
elseif matrix.formed then
|
||||||
|
mtx_status = 4
|
||||||
|
else
|
||||||
|
mtx_status = 2
|
||||||
|
end
|
||||||
|
|
||||||
|
_record_multiblock_status(m_stat == MTX_STATE.FAULT, matrix, m_ps)
|
||||||
|
end
|
||||||
|
|
||||||
|
m_ps.publish("InductionMatrixStatus", mtx_status)
|
||||||
|
m_ps.publish("InductionMatrixStateStatus", m_stat)
|
||||||
|
|
||||||
|
m_ps.publish("eta_string", data[9][1])
|
||||||
|
m_ps.publish("avg_charge", data[9][2])
|
||||||
|
m_ps.publish("avg_inflow", data[9][3])
|
||||||
|
m_ps.publish("avg_outflow", data[9][4])
|
||||||
|
m_ps.publish("is_charging", data[9][5])
|
||||||
|
m_ps.publish("is_discharging", data[9][6])
|
||||||
|
m_ps.publish("at_max_io", data[9][7])
|
||||||
|
|
||||||
|
-- sps data
|
||||||
|
|
||||||
|
fac.sps_data_tbl[1] = data[11]
|
||||||
|
|
||||||
|
local sps = fac.sps_data_tbl[1]
|
||||||
|
local s_ps = fac.sps_ps_tbl[1]
|
||||||
|
local s_stat = data[10]
|
||||||
|
|
||||||
|
local sps_status = 1
|
||||||
|
|
||||||
|
if s_stat ~= SPS_STATE.OFFLINE then
|
||||||
|
if s_stat == SPS_STATE.FAULT then
|
||||||
|
sps_status = 3
|
||||||
|
elseif sps.formed then
|
||||||
|
sps_status = 4
|
||||||
|
else
|
||||||
|
sps_status = 2
|
||||||
|
end
|
||||||
|
|
||||||
|
_record_multiblock_status(s_stat == SPS_STATE.FAULT, sps, s_ps)
|
||||||
|
end
|
||||||
|
|
||||||
|
s_ps.publish("SPSStatus", sps_status)
|
||||||
|
s_ps.publish("SPSStateStatus", s_stat)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- update the radiation monitor app with radiation monitor data from API_GET_RAD
|
||||||
|
---@param data table
|
||||||
|
function iorx.record_radiation_data(data)
|
||||||
|
-- unit radiation monitors
|
||||||
|
|
||||||
|
for u_id = 1, #io.units do
|
||||||
|
local unit = io.units[u_id]
|
||||||
|
local max_rad = 0
|
||||||
|
local connected = {}
|
||||||
|
|
||||||
|
unit.radiation = types.new_zero_radiation_reading()
|
||||||
|
unit.rad_monitors = data[u_id]
|
||||||
|
|
||||||
|
for id, mon in pairs(unit.rad_monitors) do
|
||||||
|
table.insert(connected, id)
|
||||||
|
|
||||||
|
unit.unit_ps.publish("radiation@" .. id, mon.radiation)
|
||||||
|
|
||||||
|
if mon.raw > max_rad then
|
||||||
|
max_rad = mon.raw
|
||||||
|
unit.radiation = mon.radiation
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
unit.unit_ps.publish("radiation", unit.radiation)
|
||||||
|
unit.unit_ps.publish("radiation_monitors", textutils.serialize(connected))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- facility radiation monitors
|
||||||
|
|
||||||
|
local fac = io.facility
|
||||||
|
|
||||||
|
fac.radiation = types.new_zero_radiation_reading()
|
||||||
|
fac.rad_monitors = data[#io.units + 1]
|
||||||
|
|
||||||
|
local max_rad = 0
|
||||||
|
local connected = {}
|
||||||
|
|
||||||
|
for id, mon in pairs(fac.rad_monitors) do
|
||||||
|
table.insert(connected, id)
|
||||||
|
|
||||||
|
fac.ps.publish("radiation@" .. id, mon.radiation)
|
||||||
|
|
||||||
|
if mon.raw > max_rad then
|
||||||
|
max_rad = mon.raw
|
||||||
|
fac.radiation = mon.radiation
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
fac.ps.publish("radiation", fac.radiation)
|
||||||
|
fac.ps.publish("radiation_monitors", textutils.serialize(connected))
|
||||||
|
end
|
||||||
|
|
||||||
|
return function (io_obj)
|
||||||
|
io = io_obj
|
||||||
|
return iorx
|
||||||
|
end
|
||||||
@@ -29,6 +29,7 @@ pocket.MQ__RENDER_CMD = MQ__RENDER_CMD
|
|||||||
pocket.MQ__RENDER_DATA = MQ__RENDER_DATA
|
pocket.MQ__RENDER_DATA = MQ__RENDER_DATA
|
||||||
|
|
||||||
---@type pkt_config
|
---@type pkt_config
|
||||||
|
---@diagnostic disable-next-line: missing-fields
|
||||||
local config = {}
|
local config = {}
|
||||||
|
|
||||||
pocket.config = config
|
pocket.config = config
|
||||||
@@ -37,6 +38,7 @@ pocket.config = config
|
|||||||
function pocket.load_config()
|
function pocket.load_config()
|
||||||
if not settings.load("/pocket.settings") then return false end
|
if not settings.load("/pocket.settings") then return false end
|
||||||
|
|
||||||
|
config.GreenPuPellet = settings.get("GreenPuPellet")
|
||||||
config.TempScale = settings.get("TempScale")
|
config.TempScale = settings.get("TempScale")
|
||||||
config.EnergyScale = settings.get("EnergyScale")
|
config.EnergyScale = settings.get("EnergyScale")
|
||||||
|
|
||||||
@@ -53,6 +55,7 @@ function pocket.load_config()
|
|||||||
|
|
||||||
local cfv = util.new_validator()
|
local cfv = util.new_validator()
|
||||||
|
|
||||||
|
cfv.assert_type_bool(config.GreenPuPellet)
|
||||||
cfv.assert_type_int(config.TempScale)
|
cfv.assert_type_int(config.TempScale)
|
||||||
cfv.assert_range(config.TempScale, 1, 4)
|
cfv.assert_range(config.TempScale, 1, 4)
|
||||||
cfv.assert_type_int(config.EnergyScale)
|
cfv.assert_type_int(config.EnergyScale)
|
||||||
@@ -87,15 +90,18 @@ local APP_ID = {
|
|||||||
LOADER = 2,
|
LOADER = 2,
|
||||||
-- main app pages
|
-- main app pages
|
||||||
UNITS = 3,
|
UNITS = 3,
|
||||||
CONTROL = 4,
|
FACILITY = 4,
|
||||||
PROCESS = 5,
|
CONTROL = 5,
|
||||||
GUIDE = 6,
|
PROCESS = 6,
|
||||||
ABOUT = 7,
|
WASTE = 7,
|
||||||
|
GUIDE = 8,
|
||||||
|
ABOUT = 9,
|
||||||
|
RADMON = 10,
|
||||||
-- diagnostic app pages
|
-- diagnostic app pages
|
||||||
ALARMS = 8,
|
ALARMS = 11,
|
||||||
-- other
|
-- other
|
||||||
DUMMY = 9,
|
DUMMY = 12,
|
||||||
NUM_APPS = 9
|
NUM_APPS = 12
|
||||||
}
|
}
|
||||||
|
|
||||||
pocket.APP_ID = APP_ID
|
pocket.APP_ID = APP_ID
|
||||||
@@ -264,7 +270,8 @@ function pocket.init_nav(smem)
|
|||||||
|
|
||||||
-- open an app
|
-- open an app
|
||||||
---@param app_id POCKET_APP_ID
|
---@param app_id POCKET_APP_ID
|
||||||
function nav.open_app(app_id)
|
---@param on_ready? function
|
||||||
|
function nav.open_app(app_id, on_ready)
|
||||||
-- reset help return on navigating out of an app
|
-- reset help return on navigating out of an app
|
||||||
if app_id == APP_ID.ROOT then self.help_return = nil end
|
if app_id == APP_ID.ROOT then self.help_return = nil end
|
||||||
|
|
||||||
@@ -277,7 +284,7 @@ function pocket.init_nav(smem)
|
|||||||
app = self.apps[app_id]
|
app = self.apps[app_id]
|
||||||
else self.loader_return = nil end
|
else self.loader_return = nil end
|
||||||
|
|
||||||
if not app.loaded then smem.q.mq_render.push_data(MQ__RENDER_DATA.LOAD_APP, app_id) end
|
if not app.loaded then smem.q.mq_render.push_data(MQ__RENDER_DATA.LOAD_APP, { app_id, on_ready }) end
|
||||||
|
|
||||||
self.cur_app = app_id
|
self.cur_app = app_id
|
||||||
self.pane.set_value(app_id)
|
self.pane.set_value(app_id)
|
||||||
@@ -285,6 +292,8 @@ function pocket.init_nav(smem)
|
|||||||
if #app.sidebar_items > 0 then
|
if #app.sidebar_items > 0 then
|
||||||
self.sidebar.update(app.sidebar_items)
|
self.sidebar.update(app.sidebar_items)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if app.loaded and on_ready then on_ready() end
|
||||||
else
|
else
|
||||||
log.debug("tried to open unknown app")
|
log.debug("tried to open unknown app")
|
||||||
end
|
end
|
||||||
@@ -360,10 +369,9 @@ function pocket.init_nav(smem)
|
|||||||
function nav.open_help(key)
|
function nav.open_help(key)
|
||||||
self.help_return = self.cur_app
|
self.help_return = self.cur_app
|
||||||
|
|
||||||
nav.open_app(APP_ID.GUIDE)
|
nav.open_app(APP_ID.GUIDE, function ()
|
||||||
|
if self.help_map[key] then self.help_map[key]() end
|
||||||
local load = self.help_map[key]
|
end)
|
||||||
if load then load() end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- link the help map from the guide app
|
-- link the help map from the guide app
|
||||||
@@ -550,6 +558,11 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav)
|
|||||||
if self.sv.linked then _send_sv(MGMT_TYPE.DIAG_ALARM_SET, { id, state }) end
|
if self.sv.linked then _send_sv(MGMT_TYPE.DIAG_ALARM_SET, { id, state }) end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- coordinator get facility app data
|
||||||
|
function public.api__get_facility()
|
||||||
|
if self.api.linked then _send_api(CRDN_TYPE.API_GET_FAC_DTL, {}) end
|
||||||
|
end
|
||||||
|
|
||||||
-- coordinator get unit data
|
-- coordinator get unit data
|
||||||
function public.api__get_unit(unit)
|
function public.api__get_unit(unit)
|
||||||
if self.api.linked then _send_api(CRDN_TYPE.API_GET_UNIT, { unit }) end
|
if self.api.linked then _send_api(CRDN_TYPE.API_GET_UNIT, { unit }) end
|
||||||
@@ -565,6 +578,16 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav)
|
|||||||
if self.api.linked then _send_api(CRDN_TYPE.API_GET_PROC, {}) end
|
if self.api.linked then _send_api(CRDN_TYPE.API_GET_PROC, {}) end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- coordinator get waste app data
|
||||||
|
function public.api__get_waste()
|
||||||
|
if self.api.linked then _send_api(CRDN_TYPE.API_GET_WASTE, {}) end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- coordinator get radiation app data
|
||||||
|
function public.api__get_rad()
|
||||||
|
if self.api.linked then _send_api(CRDN_TYPE.API_GET_RAD, {}) end
|
||||||
|
end
|
||||||
|
|
||||||
-- send a facility command
|
-- send a facility command
|
||||||
---@param cmd FAC_COMMAND command
|
---@param cmd FAC_COMMAND command
|
||||||
---@param option any? optional option options for the optional options (like waste mode)
|
---@param option any? optional option options for the optional options (like waste mode)
|
||||||
@@ -719,19 +742,31 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav)
|
|||||||
end
|
end
|
||||||
elseif packet.type == CRDN_TYPE.API_GET_FAC then
|
elseif packet.type == CRDN_TYPE.API_GET_FAC then
|
||||||
if _check_length(packet, 11) then
|
if _check_length(packet, 11) then
|
||||||
iocontrol.record_facility_data(packet.data)
|
iocontrol.rx.record_facility_data(packet.data)
|
||||||
|
end
|
||||||
|
elseif packet.type == CRDN_TYPE.API_GET_FAC_DTL then
|
||||||
|
if _check_length(packet, 12) then
|
||||||
|
iocontrol.rx.record_fac_detail_data(packet.data)
|
||||||
end
|
end
|
||||||
elseif packet.type == CRDN_TYPE.API_GET_UNIT then
|
elseif packet.type == CRDN_TYPE.API_GET_UNIT then
|
||||||
if _check_length(packet, 12) and type(packet.data[1]) == "number" and iocontrol.get_db().units[packet.data[1]] then
|
if _check_length(packet, 12) and type(packet.data[1]) == "number" and iocontrol.get_db().units[packet.data[1]] then
|
||||||
iocontrol.record_unit_data(packet.data)
|
iocontrol.rx.record_unit_data(packet.data)
|
||||||
end
|
end
|
||||||
elseif packet.type == CRDN_TYPE.API_GET_CTRL then
|
elseif packet.type == CRDN_TYPE.API_GET_CTRL then
|
||||||
if _check_length(packet, #iocontrol.get_db().units) then
|
if _check_length(packet, #iocontrol.get_db().units) then
|
||||||
iocontrol.record_control_data(packet.data)
|
iocontrol.rx.record_control_data(packet.data)
|
||||||
end
|
end
|
||||||
elseif packet.type == CRDN_TYPE.API_GET_PROC then
|
elseif packet.type == CRDN_TYPE.API_GET_PROC then
|
||||||
if _check_length(packet, #iocontrol.get_db().units + 1) then
|
if _check_length(packet, #iocontrol.get_db().units + 1) then
|
||||||
iocontrol.record_process_data(packet.data)
|
iocontrol.rx.record_process_data(packet.data)
|
||||||
|
end
|
||||||
|
elseif packet.type == CRDN_TYPE.API_GET_WASTE then
|
||||||
|
if _check_length(packet, #iocontrol.get_db().units + 1) then
|
||||||
|
iocontrol.rx.record_waste_data(packet.data)
|
||||||
|
end
|
||||||
|
elseif packet.type == CRDN_TYPE.API_GET_RAD then
|
||||||
|
if _check_length(packet, #iocontrol.get_db().units + 1) then
|
||||||
|
iocontrol.rx.record_radiation_data(packet.data)
|
||||||
end
|
end
|
||||||
else _fail_type(packet) end
|
else _fail_type(packet) end
|
||||||
else
|
else
|
||||||
@@ -891,7 +926,7 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav)
|
|||||||
local ready = packet.data[1]
|
local ready = packet.data[1]
|
||||||
local states = packet.data[2]
|
local states = packet.data[2]
|
||||||
|
|
||||||
diag.tone_test.ready_warn.set_value(util.trinary(ready, "", "system not ready"))
|
diag.tone_test.ready_warn.set_value(util.trinary(ready, "", "system not idle"))
|
||||||
|
|
||||||
for i = 1, #states do
|
for i = 1, #states do
|
||||||
if diag.tone_test.tone_buttons[i] ~= nil then
|
if diag.tone_test.tone_buttons[i] ~= nil then
|
||||||
@@ -910,7 +945,7 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav)
|
|||||||
local ready = packet.data[1]
|
local ready = packet.data[1]
|
||||||
local states = packet.data[2]
|
local states = packet.data[2]
|
||||||
|
|
||||||
diag.tone_test.ready_warn.set_value(util.trinary(ready, "", "system not ready"))
|
diag.tone_test.ready_warn.set_value(util.trinary(ready, "", "system not idle"))
|
||||||
|
|
||||||
for i = 1, #states do
|
for i = 1, #states do
|
||||||
if diag.tone_test.alarm_buttons[i] ~= nil then
|
if diag.tone_test.alarm_buttons[i] ~= nil then
|
||||||
|
|||||||
@@ -85,6 +85,14 @@ function process.set_group(unit_id, group_id)
|
|||||||
log.debug(util.c("PROCESS: UNIT[", unit_id, "] SET GROUP ", group_id))
|
log.debug(util.c("PROCESS: UNIT[", unit_id, "] SET GROUP ", group_id))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- set waste mode
|
||||||
|
---@param id integer unit ID
|
||||||
|
---@param mode integer waste mode
|
||||||
|
function process.set_unit_waste(id, mode)
|
||||||
|
self.comms.send_unit_command(U_CMD.SET_WASTE, id, mode)
|
||||||
|
log.debug(util.c("PROCESS: UNIT[", id, "] SET WASTE ", mode))
|
||||||
|
end
|
||||||
|
|
||||||
-- acknowledge all alarms
|
-- acknowledge all alarms
|
||||||
---@param id integer unit ID
|
---@param id integer unit ID
|
||||||
function process.ack_all_alarms(id)
|
function process.ack_all_alarms(id)
|
||||||
@@ -131,6 +139,27 @@ function process.process_stop()
|
|||||||
log.debug("PROCESS: STOP AUTO CTRL")
|
log.debug("PROCESS: STOP AUTO CTRL")
|
||||||
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(F_CMD.SET_WASTE_MODE, product)
|
||||||
|
log.debug(util.c("PROCESS: SET WASTE ", product))
|
||||||
|
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(F_CMD.SET_PU_FB, enabled)
|
||||||
|
log.debug(util.c("PROCESS: SET PU FALLBACK ", enabled))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- set automatic process control SPS usage at low power
|
||||||
|
---@param enabled boolean whether to enable SPS usage at low power
|
||||||
|
function process.set_sps_low_power(enabled)
|
||||||
|
self.comms.send_fac_command(F_CMD.SET_SPS_LP, enabled)
|
||||||
|
log.debug(util.c("PROCESS: SET SPS LOW POWER ", enabled))
|
||||||
|
end
|
||||||
|
|
||||||
-- #endregion
|
-- #endregion
|
||||||
---------------------------------
|
---------------------------------
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,10 @@
|
|||||||
-- SCADA System Access on a Pocket Computer
|
-- SCADA System Access on a Pocket Computer
|
||||||
--
|
--
|
||||||
|
|
||||||
---@diagnostic disable-next-line: undefined-global
|
---@diagnostic disable-next-line: lowercase-global
|
||||||
local _is_pocket_env = pocket or periphemu -- luacheck: ignore pocket
|
pocket = pocket or periphemu -- luacheck: ignore pocket
|
||||||
|
|
||||||
|
local _is_pocket_env = pocket -- luacheck: ignore pocket
|
||||||
|
|
||||||
require("/initenv").init_env()
|
require("/initenv").init_env()
|
||||||
|
|
||||||
@@ -20,7 +22,7 @@ local pocket = require("pocket.pocket")
|
|||||||
local renderer = require("pocket.renderer")
|
local renderer = require("pocket.renderer")
|
||||||
local threads = require("pocket.threads")
|
local threads = require("pocket.threads")
|
||||||
|
|
||||||
local POCKET_VERSION = "v0.12.7-alpha"
|
local POCKET_VERSION = "v0.13.5-beta"
|
||||||
|
|
||||||
local println = util.println
|
local println = util.println
|
||||||
local println_ts = util.println_ts
|
local println_ts = util.println_ts
|
||||||
|
|||||||
@@ -165,15 +165,18 @@ function threads.thread__render(smem)
|
|||||||
local cmd = msg.message ---@type queue_data
|
local cmd = msg.message ---@type queue_data
|
||||||
|
|
||||||
if cmd.key == MQ__RENDER_DATA.LOAD_APP then
|
if cmd.key == MQ__RENDER_DATA.LOAD_APP then
|
||||||
log.debug("RENDER: load app " .. cmd.val)
|
log.debug("RENDER: load app " .. cmd.val[1])
|
||||||
|
|
||||||
local draw_start = util.time_ms()
|
local draw_start = util.time_ms()
|
||||||
|
|
||||||
pkt_state.ui_ok, pkt_state.ui_error = pcall(function () nav.load_app(cmd.val) end)
|
pkt_state.ui_ok, pkt_state.ui_error = pcall(function () nav.load_app(cmd.val[1]) end)
|
||||||
if not pkt_state.ui_ok then
|
if not pkt_state.ui_ok then
|
||||||
log.fatal(util.c("RENDER: app load failed with error ", pkt_state.ui_error))
|
log.fatal(util.c("RENDER: app load failed with error ", pkt_state.ui_error))
|
||||||
else
|
else
|
||||||
log.debug("RENDER: app loaded in " .. (util.time_ms() - draw_start) .. "ms")
|
log.debug("RENDER: app loaded in " .. (util.time_ms() - draw_start) .. "ms")
|
||||||
|
|
||||||
|
-- call the on loaded function if provided
|
||||||
|
if type(cmd.val[2]) == "function" then cmd.val[2]() end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
elseif msg.qtype == mqueue.TYPE.PACKET then
|
elseif msg.qtype == mqueue.TYPE.PACKET then
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
--
|
--
|
||||||
-- Unit Control Page
|
-- Facility & Unit Control App
|
||||||
--
|
--
|
||||||
|
|
||||||
local types = require("scada-common.types")
|
local types = require("scada-common.types")
|
||||||
|
|||||||
258
pocket/ui/apps/facility.lua
Normal file
258
pocket/ui/apps/facility.lua
Normal file
@@ -0,0 +1,258 @@
|
|||||||
|
--
|
||||||
|
-- Facility Overview App
|
||||||
|
--
|
||||||
|
|
||||||
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
|
local iocontrol = require("pocket.iocontrol")
|
||||||
|
local pocket = require("pocket.pocket")
|
||||||
|
|
||||||
|
local style = require("pocket.ui.style")
|
||||||
|
|
||||||
|
local dyn_tank = require("pocket.ui.pages.dynamic_tank")
|
||||||
|
local facility_sps = require("pocket.ui.pages.facility_sps")
|
||||||
|
local induction_mtx = require("pocket.ui.pages.facility_matrix")
|
||||||
|
|
||||||
|
local core = require("graphics.core")
|
||||||
|
|
||||||
|
local Div = require("graphics.elements.Div")
|
||||||
|
local MultiPane = require("graphics.elements.MultiPane")
|
||||||
|
local TextBox = require("graphics.elements.TextBox")
|
||||||
|
|
||||||
|
local WaitingAnim = require("graphics.elements.animations.Waiting")
|
||||||
|
|
||||||
|
local DataIndicator = require("graphics.elements.indicators.DataIndicator")
|
||||||
|
local IconIndicator = require("graphics.elements.indicators.IconIndicator")
|
||||||
|
|
||||||
|
local ALIGN = core.ALIGN
|
||||||
|
local cpair = core.cpair
|
||||||
|
|
||||||
|
local APP_ID = pocket.APP_ID
|
||||||
|
|
||||||
|
local label_fg_bg = style.label
|
||||||
|
local lu_col = style.label_unit_pair
|
||||||
|
|
||||||
|
local basic_states = style.icon_states.basic_states
|
||||||
|
local mode_states = style.icon_states.mode_states
|
||||||
|
local red_ind_s = style.icon_states.red_ind_s
|
||||||
|
local yel_ind_s = style.icon_states.yel_ind_s
|
||||||
|
local grn_ind_s = style.icon_states.grn_ind_s
|
||||||
|
|
||||||
|
-- new unit page view
|
||||||
|
---@param root Container parent
|
||||||
|
local function new_view(root)
|
||||||
|
local db = iocontrol.get_db()
|
||||||
|
|
||||||
|
local frame = Div{parent=root,x=1,y=1}
|
||||||
|
|
||||||
|
local app = db.nav.register_app(APP_ID.FACILITY, frame, nil, false, true)
|
||||||
|
|
||||||
|
local load_div = Div{parent=frame,x=1,y=1}
|
||||||
|
local main = Div{parent=frame,x=1,y=1}
|
||||||
|
|
||||||
|
TextBox{parent=load_div,y=12,text="Loading...",alignment=ALIGN.CENTER}
|
||||||
|
WaitingAnim{parent=load_div,x=math.floor(main.get_width()/2)-1,y=8,fg_bg=cpair(colors.orange,colors._INHERIT)}
|
||||||
|
|
||||||
|
local load_pane = MultiPane{parent=main,x=1,y=1,panes={load_div,main}}
|
||||||
|
|
||||||
|
app.set_sidebar({ { label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = db.nav.go_home } })
|
||||||
|
|
||||||
|
local tank_page_navs = {}
|
||||||
|
local page_div = nil ---@type Div|nil
|
||||||
|
|
||||||
|
-- load the app (create the elements)
|
||||||
|
local function load()
|
||||||
|
local fac = db.facility
|
||||||
|
local f_ps = fac.ps
|
||||||
|
|
||||||
|
page_div = Div{parent=main,y=2,width=main.get_width()}
|
||||||
|
|
||||||
|
local panes = {} ---@type Div[]
|
||||||
|
|
||||||
|
-- refresh data callback, every 500ms it will re-send the query
|
||||||
|
local last_update = 0
|
||||||
|
local function update()
|
||||||
|
if util.time_ms() - last_update >= 500 then
|
||||||
|
db.api.get_fac()
|
||||||
|
last_update = util.time_ms()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--#region facility overview
|
||||||
|
|
||||||
|
local main_pane = Div{parent=page_div}
|
||||||
|
local f_div = Div{parent=main_pane,x=2,width=main.get_width()-2}
|
||||||
|
table.insert(panes, main_pane)
|
||||||
|
|
||||||
|
local fac_page = app.new_page(nil, #panes)
|
||||||
|
fac_page.tasks = { update }
|
||||||
|
|
||||||
|
TextBox{parent=f_div,y=1,text="Facility",alignment=ALIGN.CENTER}
|
||||||
|
|
||||||
|
local mtx_state = IconIndicator{parent=f_div,y=3,label="Matrix Status",states=basic_states}
|
||||||
|
local sps_state = IconIndicator{parent=f_div,label="SPS Status",states=basic_states}
|
||||||
|
mtx_state.register(fac.induction_ps_tbl[1], "InductionMatrixStatus", mtx_state.update)
|
||||||
|
sps_state.register(fac.sps_ps_tbl[1], "SPSStatus", sps_state.update)
|
||||||
|
|
||||||
|
TextBox{parent=f_div,y=6,text="RTU Gateways",fg_bg=label_fg_bg}
|
||||||
|
local rtu_count = DataIndicator{parent=f_div,x=19,y=6,label="",format="%3d",value=0,lu_colors=lu_col,width=3}
|
||||||
|
rtu_count.register(f_ps, "rtu_count", rtu_count.update)
|
||||||
|
|
||||||
|
TextBox{parent=f_div,y=8,text="Induction Matrix",alignment=ALIGN.CENTER}
|
||||||
|
|
||||||
|
local eta = TextBox{parent=f_div,x=1,y=10,text="ETA Unknown",alignment=ALIGN.CENTER,fg_bg=cpair(colors.white,colors.gray)}
|
||||||
|
eta.register(fac.induction_ps_tbl[1], "eta_string", eta.set_value)
|
||||||
|
|
||||||
|
TextBox{parent=f_div,y=12,text="Unit Statuses",alignment=ALIGN.CENTER}
|
||||||
|
|
||||||
|
f_div.line_break()
|
||||||
|
|
||||||
|
for i = 1, fac.num_units do
|
||||||
|
local ctrl = IconIndicator{parent=f_div,label="U"..i.." Control State",states=mode_states}
|
||||||
|
ctrl.register(db.units[i].unit_ps, "U_ControlStatus", ctrl.update)
|
||||||
|
end
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
|
--#region facility annunciator
|
||||||
|
|
||||||
|
local a_pane = Div{parent=page_div}
|
||||||
|
local a_div = Div{parent=a_pane,x=2,width=main.get_width()-2}
|
||||||
|
table.insert(panes, a_pane)
|
||||||
|
|
||||||
|
local annunc_page = app.new_page(nil, #panes)
|
||||||
|
annunc_page.tasks = { update }
|
||||||
|
|
||||||
|
TextBox{parent=a_div,y=1,text="Annunciator",alignment=ALIGN.CENTER}
|
||||||
|
|
||||||
|
local all_ok = IconIndicator{parent=a_div,y=3,label="Units Online",states=grn_ind_s}
|
||||||
|
local ind_mat = IconIndicator{parent=a_div,label="Induction Matrix",states=grn_ind_s}
|
||||||
|
local sps = IconIndicator{parent=a_div,label="SPS Connected",states=grn_ind_s}
|
||||||
|
|
||||||
|
all_ok.register(f_ps, "all_sys_ok", all_ok.update)
|
||||||
|
ind_mat.register(fac.induction_ps_tbl[1], "InductionMatrixStateStatus", function (status) ind_mat.update(status > 1) end)
|
||||||
|
sps.register(fac.sps_ps_tbl[1], "SPSStateStatus", function (status) sps.update(status > 1) end)
|
||||||
|
|
||||||
|
a_div.line_break()
|
||||||
|
|
||||||
|
local auto_scram = IconIndicator{parent=a_div,label="Automatic SCRAM",states=red_ind_s}
|
||||||
|
local matrix_flt = IconIndicator{parent=a_div,label="Ind. Matrix Fault",states=yel_ind_s}
|
||||||
|
local matrix_fill = IconIndicator{parent=a_div,label="Matrix Charge Hi",states=red_ind_s}
|
||||||
|
local unit_crit = IconIndicator{parent=a_div,label="Unit Crit. Alarm",states=red_ind_s}
|
||||||
|
local fac_rad_h = IconIndicator{parent=a_div,label="FAC Radiation Hi",states=red_ind_s}
|
||||||
|
local gen_fault = IconIndicator{parent=a_div,label="Gen Control Fault",states=yel_ind_s}
|
||||||
|
|
||||||
|
auto_scram.register(f_ps, "auto_scram", auto_scram.update)
|
||||||
|
matrix_flt.register(f_ps, "as_matrix_fault", matrix_flt.update)
|
||||||
|
matrix_fill.register(f_ps, "as_matrix_fill", matrix_fill.update)
|
||||||
|
unit_crit.register(f_ps, "as_crit_alarm", unit_crit.update)
|
||||||
|
fac_rad_h.register(f_ps, "as_radiation", fac_rad_h.update)
|
||||||
|
gen_fault.register(f_ps, "as_gen_fault", gen_fault.update)
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
|
--#region induction matrix
|
||||||
|
|
||||||
|
local mtx_page_nav = induction_mtx(app, panes, Div{parent=page_div}, fac.induction_ps_tbl[1], update)
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
|
--#region SPS
|
||||||
|
|
||||||
|
local sps_page_nav = facility_sps(app, panes, Div{parent=page_div}, fac.sps_ps_tbl[1], update)
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
|
--#region facility tank pages
|
||||||
|
|
||||||
|
local t_pane = Div{parent=page_div}
|
||||||
|
local t_div = Div{parent=t_pane,x=2,width=main.get_width()-2}
|
||||||
|
table.insert(panes, t_pane)
|
||||||
|
|
||||||
|
local tank_page = app.new_page(nil, #panes)
|
||||||
|
tank_page.tasks = { update }
|
||||||
|
|
||||||
|
TextBox{parent=t_div,y=1,text="Facility Tanks",alignment=ALIGN.CENTER}
|
||||||
|
|
||||||
|
local f_tank_id = 1
|
||||||
|
for t = 1, #fac.tank_list do
|
||||||
|
if fac.tank_list[t] == 1 then
|
||||||
|
t_div.line_break()
|
||||||
|
|
||||||
|
local tank = IconIndicator{parent=t_div,x=1,label="Unit Tank "..t.." (U-"..t..")",states=basic_states}
|
||||||
|
tank.register(db.units[t].tank_ps_tbl[1], "DynamicTankStatus", tank.update)
|
||||||
|
|
||||||
|
TextBox{parent=t_div,x=5,text="\x07 Unit "..t,fg_bg=label_fg_bg}
|
||||||
|
elseif fac.tank_list[t] == 2 then
|
||||||
|
tank_page_navs[f_tank_id] = dyn_tank(app, nil, panes, Div{parent=page_div}, t, fac.tank_ps_tbl[f_tank_id], update)
|
||||||
|
|
||||||
|
t_div.line_break()
|
||||||
|
|
||||||
|
local tank = IconIndicator{parent=t_div,x=1,label="Fac. Tank "..f_tank_id.." (F-"..f_tank_id..")",states=basic_states}
|
||||||
|
tank.register(fac.tank_ps_tbl[f_tank_id], "DynamicTankStatus", tank.update)
|
||||||
|
|
||||||
|
local connections = ""
|
||||||
|
for i = 1, #fac.tank_conns do
|
||||||
|
if fac.tank_conns[i] == t then
|
||||||
|
if connections ~= "" then
|
||||||
|
connections = connections .. "\n\x07 Unit " .. i
|
||||||
|
else
|
||||||
|
connections = "\x07 Unit " .. i
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
TextBox{parent=t_div,x=5,text=connections,fg_bg=label_fg_bg}
|
||||||
|
|
||||||
|
f_tank_id = f_tank_id + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
|
-- setup multipane
|
||||||
|
local f_pane = MultiPane{parent=page_div,x=1,y=1,panes=panes}
|
||||||
|
app.set_root_pane(f_pane)
|
||||||
|
|
||||||
|
-- setup sidebar
|
||||||
|
|
||||||
|
local list = {
|
||||||
|
{ label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = db.nav.go_home },
|
||||||
|
{ label = "FAC", tall = true, color = core.cpair(colors.black, colors.orange), callback = fac_page.nav_to },
|
||||||
|
{ label = "ANN", color = core.cpair(colors.black, colors.yellow), callback = annunc_page.nav_to },
|
||||||
|
{ label = "MTX", color = core.cpair(colors.black, colors.white), callback = mtx_page_nav },
|
||||||
|
{ label = "SPS", color = core.cpair(colors.black, colors.purple), callback = sps_page_nav },
|
||||||
|
{ label = "TNK", tall = true, color = core.cpair(colors.black, colors.blue), callback = tank_page.nav_to }
|
||||||
|
}
|
||||||
|
|
||||||
|
for i = 1, #fac.tank_data_tbl do
|
||||||
|
table.insert(list, { label = "F-" .. i, color = core.cpair(colors.black, colors.lightGray), callback = tank_page_navs[i] })
|
||||||
|
end
|
||||||
|
|
||||||
|
app.set_sidebar(list)
|
||||||
|
|
||||||
|
-- done, show the app
|
||||||
|
load_pane.set_value(2)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- delete the elements and switch back to the loading screen
|
||||||
|
local function unload()
|
||||||
|
if page_div then
|
||||||
|
page_div.delete()
|
||||||
|
page_div = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
app.set_sidebar({ { label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = db.nav.go_home } })
|
||||||
|
app.delete_pages()
|
||||||
|
|
||||||
|
-- show loading screen
|
||||||
|
load_pane.set_value(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
app.set_load(load)
|
||||||
|
app.set_unload(unload)
|
||||||
|
|
||||||
|
return main
|
||||||
|
end
|
||||||
|
|
||||||
|
return new_view
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
--
|
--
|
||||||
-- Process Control Page
|
-- Process Control App
|
||||||
--
|
--
|
||||||
|
|
||||||
local types = require("scada-common.types")
|
local types = require("scada-common.types")
|
||||||
@@ -269,7 +269,7 @@ local function new_view(root)
|
|||||||
local auto_scram = IconIndicator{parent=a_div,y=3,label="Automatic SCRAM",states=red_ind_s}
|
local auto_scram = IconIndicator{parent=a_div,y=3,label="Automatic SCRAM",states=red_ind_s}
|
||||||
|
|
||||||
TextBox{parent=a_div,y=5,text="Induction Matrix",fg_bg=label_fg_bg}
|
TextBox{parent=a_div,y=5,text="Induction Matrix",fg_bg=label_fg_bg}
|
||||||
local matrix_dc = IconIndicator{parent=a_div,label="Disconnected",states=yel_ind_s}
|
local matrix_flt = IconIndicator{parent=a_div,label="Matrix Fault",states=yel_ind_s}
|
||||||
local matrix_fill = IconIndicator{parent=a_div,label="Charge High",states=red_ind_s}
|
local matrix_fill = IconIndicator{parent=a_div,label="Charge High",states=red_ind_s}
|
||||||
|
|
||||||
TextBox{parent=a_div,y=9,text="Assigned Units",fg_bg=label_fg_bg}
|
TextBox{parent=a_div,y=9,text="Assigned Units",fg_bg=label_fg_bg}
|
||||||
@@ -282,7 +282,7 @@ local function new_view(root)
|
|||||||
local gen_fault = IconIndicator{parent=a_div,label="Control Fault",states=yel_ind_s}
|
local gen_fault = IconIndicator{parent=a_div,label="Control Fault",states=yel_ind_s}
|
||||||
|
|
||||||
auto_scram.register(f_ps, "auto_scram", auto_scram.update)
|
auto_scram.register(f_ps, "auto_scram", auto_scram.update)
|
||||||
matrix_dc.register(f_ps, "as_matrix_dc", matrix_dc.update)
|
matrix_flt.register(f_ps, "as_matrix_fault", matrix_flt.update)
|
||||||
matrix_fill.register(f_ps, "as_matrix_fill", matrix_fill.update)
|
matrix_fill.register(f_ps, "as_matrix_fill", matrix_fill.update)
|
||||||
unit_crit.register(f_ps, "as_crit_alarm", unit_crit.update)
|
unit_crit.register(f_ps, "as_crit_alarm", unit_crit.update)
|
||||||
fac_rad_h.register(f_ps, "as_radiation", fac_rad_h.update)
|
fac_rad_h.register(f_ps, "as_radiation", fac_rad_h.update)
|
||||||
|
|||||||
219
pocket/ui/apps/radiation.lua
Normal file
219
pocket/ui/apps/radiation.lua
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
--
|
||||||
|
-- Radiation Monitor App
|
||||||
|
--
|
||||||
|
|
||||||
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
|
local iocontrol = require("pocket.iocontrol")
|
||||||
|
local pocket = require("pocket.pocket")
|
||||||
|
|
||||||
|
local style = require("pocket.ui.style")
|
||||||
|
|
||||||
|
local core = require("graphics.core")
|
||||||
|
|
||||||
|
local Div = require("graphics.elements.Div")
|
||||||
|
local ListBox = require("graphics.elements.ListBox")
|
||||||
|
local MultiPane = require("graphics.elements.MultiPane")
|
||||||
|
local Rectangle = require("graphics.elements.Rectangle")
|
||||||
|
local TextBox = require("graphics.elements.TextBox")
|
||||||
|
|
||||||
|
local WaitingAnim = require("graphics.elements.animations.Waiting")
|
||||||
|
|
||||||
|
local RadIndicator = require("graphics.elements.indicators.RadIndicator")
|
||||||
|
|
||||||
|
local ALIGN = core.ALIGN
|
||||||
|
local cpair = core.cpair
|
||||||
|
local border = core.border
|
||||||
|
|
||||||
|
local APP_ID = pocket.APP_ID
|
||||||
|
|
||||||
|
local label_fg_bg = style.label
|
||||||
|
local lu_col = style.label_unit_pair
|
||||||
|
|
||||||
|
-- new radiation monitor page view
|
||||||
|
---@param root Container parent
|
||||||
|
local function new_view(root)
|
||||||
|
local db = iocontrol.get_db()
|
||||||
|
|
||||||
|
local frame = Div{parent=root,x=1,y=1}
|
||||||
|
|
||||||
|
local app = db.nav.register_app(APP_ID.RADMON, frame, nil, false, true)
|
||||||
|
|
||||||
|
local load_div = Div{parent=frame,x=1,y=1}
|
||||||
|
local main = Div{parent=frame,x=1,y=1}
|
||||||
|
|
||||||
|
TextBox{parent=load_div,y=12,text="Loading...",alignment=ALIGN.CENTER}
|
||||||
|
WaitingAnim{parent=load_div,x=math.floor(main.get_width()/2)-1,y=8,fg_bg=cpair(colors.yellow,colors._INHERIT)}
|
||||||
|
|
||||||
|
local load_pane = MultiPane{parent=main,x=1,y=1,panes={load_div,main}}
|
||||||
|
|
||||||
|
app.set_sidebar({ { label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = db.nav.go_home } })
|
||||||
|
|
||||||
|
local page_div = nil ---@type Div|nil
|
||||||
|
|
||||||
|
-- load the app (create the elements)
|
||||||
|
local function load()
|
||||||
|
local f_ps = db.facility.ps
|
||||||
|
|
||||||
|
page_div = Div{parent=main,y=2,width=main.get_width()}
|
||||||
|
|
||||||
|
local panes = {} ---@type Div[]
|
||||||
|
|
||||||
|
-- create all page divs
|
||||||
|
for _ = 1, db.facility.num_units + 2 do
|
||||||
|
local div = Div{parent=page_div}
|
||||||
|
table.insert(panes, div)
|
||||||
|
end
|
||||||
|
|
||||||
|
local last_update = 0
|
||||||
|
-- refresh data callback, every 500ms it will re-send the query
|
||||||
|
local function update()
|
||||||
|
if util.time_ms() - last_update >= 500 then
|
||||||
|
db.api.get_rad()
|
||||||
|
last_update = util.time_ms()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- create a new radiation monitor list
|
||||||
|
---@param parent Container
|
||||||
|
---@param ps psil
|
||||||
|
local function new_mon_list(parent, ps)
|
||||||
|
local mon_list = ListBox{parent=parent,y=6,scroll_height=100,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)}
|
||||||
|
|
||||||
|
local elem_list = {} ---@type graphics_element[]
|
||||||
|
|
||||||
|
mon_list.register(ps, "radiation_monitors", function (data)
|
||||||
|
local ids = textutils.unserialize(data)
|
||||||
|
|
||||||
|
-- delete any disconnected monitors
|
||||||
|
for id, elem in pairs(elem_list) do
|
||||||
|
if not util.table_contains(ids, id) then
|
||||||
|
elem.delete()
|
||||||
|
elem_list[id] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- add newly connected monitors
|
||||||
|
for _, id in pairs(ids) do
|
||||||
|
if not elem_list[id] then
|
||||||
|
elem_list[id] = Div{parent=mon_list,height=5}
|
||||||
|
local mon_rect = Rectangle{parent=elem_list[id],height=4,x=2,width=20,border=border(1,colors.gray,true),thin=true,fg_bg=cpair(colors.black,colors.lightGray)}
|
||||||
|
|
||||||
|
TextBox{parent=mon_rect,text="Env. Detector "..id}
|
||||||
|
local mon_rad = RadIndicator{parent=mon_rect,x=2,label="",format="%13.3f",lu_colors=cpair(colors.gray,colors.gray),width=18}
|
||||||
|
mon_rad.register(ps, "radiation@" .. id, mon_rad.update)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
--#region unit radiation monitors
|
||||||
|
|
||||||
|
for i = 1, db.facility.num_units do
|
||||||
|
local u_pane = panes[i]
|
||||||
|
local u_div = Div{parent=u_pane}
|
||||||
|
local unit = db.units[i]
|
||||||
|
local u_ps = unit.unit_ps
|
||||||
|
|
||||||
|
local u_page = app.new_page(nil, i)
|
||||||
|
u_page.tasks = { update }
|
||||||
|
|
||||||
|
TextBox{parent=u_div,y=1,text="Unit #"..i.." Monitors",alignment=ALIGN.CENTER}
|
||||||
|
|
||||||
|
TextBox{parent=u_div,x=2,y=3,text="Max Radiation",fg_bg=label_fg_bg}
|
||||||
|
local radiation = RadIndicator{parent=u_div,x=2,label="",format="%17.3f",lu_colors=lu_col,width=21}
|
||||||
|
radiation.register(u_ps, "radiation", radiation.update)
|
||||||
|
|
||||||
|
new_mon_list(u_div, u_ps)
|
||||||
|
end
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
|
--#region overview page
|
||||||
|
|
||||||
|
local s_pane = panes[db.facility.num_units + 1]
|
||||||
|
local s_div = Div{parent=s_pane,x=2,width=main.get_width()-2}
|
||||||
|
|
||||||
|
local stat_page = app.new_page(nil, db.facility.num_units + 1)
|
||||||
|
stat_page.tasks = { update }
|
||||||
|
|
||||||
|
TextBox{parent=s_div,y=1,text=" Radiation Monitoring",alignment=ALIGN.CENTER}
|
||||||
|
|
||||||
|
TextBox{parent=s_div,y=3,text="Max Facility Rad.",fg_bg=label_fg_bg}
|
||||||
|
local s_f_rad = RadIndicator{parent=s_div,label="",format="%17.3f",lu_colors=lu_col,width=21}
|
||||||
|
s_f_rad.register(f_ps, "radiation", s_f_rad.update)
|
||||||
|
|
||||||
|
for i = 1, db.facility.num_units do
|
||||||
|
local unit = db.units[i]
|
||||||
|
local u_ps = unit.unit_ps
|
||||||
|
|
||||||
|
s_div.line_break()
|
||||||
|
TextBox{parent=s_div,text="Max Unit "..i.." Radiation",fg_bg=label_fg_bg}
|
||||||
|
local s_u_rad = RadIndicator{parent=s_div,label="",format="%17.3f",lu_colors=lu_col,width=21}
|
||||||
|
s_u_rad.register(u_ps, "radiation", s_u_rad.update)
|
||||||
|
end
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
|
--#region overview page
|
||||||
|
|
||||||
|
local f_pane = panes[db.facility.num_units + 2]
|
||||||
|
local f_div = Div{parent=f_pane,width=main.get_width()}
|
||||||
|
|
||||||
|
local fac_page = app.new_page(nil, db.facility.num_units + 2)
|
||||||
|
fac_page.tasks = { update }
|
||||||
|
|
||||||
|
TextBox{parent=f_div,y=1,text="Facility Monitors",alignment=ALIGN.CENTER}
|
||||||
|
|
||||||
|
TextBox{parent=f_div,x=2,y=3,text="Max Radiation",fg_bg=label_fg_bg}
|
||||||
|
local f_rad = RadIndicator{parent=f_div,x=2,label="",format="%17.3f",lu_colors=lu_col,width=21}
|
||||||
|
f_rad.register(f_ps, "radiation", f_rad.update)
|
||||||
|
|
||||||
|
new_mon_list(f_div, f_ps)
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
|
-- setup multipane
|
||||||
|
local u_pane = MultiPane{parent=page_div,x=1,y=1,panes=panes}
|
||||||
|
app.set_root_pane(u_pane)
|
||||||
|
|
||||||
|
-- setup sidebar
|
||||||
|
|
||||||
|
local list = {
|
||||||
|
{ label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = db.nav.go_home },
|
||||||
|
{ label = " \x1e ", color = core.cpair(colors.black, colors.blue), callback = stat_page.nav_to },
|
||||||
|
{ label = "FAC", color = core.cpair(colors.black, colors.yellow), callback = fac_page.nav_to }
|
||||||
|
}
|
||||||
|
|
||||||
|
for i = 1, db.facility.num_units do
|
||||||
|
table.insert(list, { label = "U-" .. i, color = core.cpair(colors.black, colors.lightGray), callback = function () app.switcher(i) end })
|
||||||
|
end
|
||||||
|
|
||||||
|
app.set_sidebar(list)
|
||||||
|
|
||||||
|
-- done, show the app
|
||||||
|
stat_page.nav_to()
|
||||||
|
load_pane.set_value(2)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- delete the elements and switch back to the loading screen
|
||||||
|
local function unload()
|
||||||
|
if page_div then
|
||||||
|
page_div.delete()
|
||||||
|
page_div = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
app.set_sidebar({ { label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = db.nav.go_home } })
|
||||||
|
app.delete_pages()
|
||||||
|
|
||||||
|
-- show loading screen
|
||||||
|
load_pane.set_value(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
app.set_load(load)
|
||||||
|
app.set_unload(unload)
|
||||||
|
|
||||||
|
return main
|
||||||
|
end
|
||||||
|
|
||||||
|
return new_view
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
--
|
--
|
||||||
-- Unit Overview Page
|
-- Unit Overview App
|
||||||
--
|
--
|
||||||
|
|
||||||
local util = require("scada-common.util")
|
local util = require("scada-common.util")
|
||||||
@@ -9,6 +9,7 @@ local pocket = require("pocket.pocket")
|
|||||||
|
|
||||||
local style = require("pocket.ui.style")
|
local style = require("pocket.ui.style")
|
||||||
|
|
||||||
|
local dyn_tank = require("pocket.ui.pages.dynamic_tank")
|
||||||
local boiler = require("pocket.ui.pages.unit_boiler")
|
local boiler = require("pocket.ui.pages.unit_boiler")
|
||||||
local reactor = require("pocket.ui.pages.unit_reactor")
|
local reactor = require("pocket.ui.pages.unit_reactor")
|
||||||
local turbine = require("pocket.ui.pages.unit_turbine")
|
local turbine = require("pocket.ui.pages.unit_turbine")
|
||||||
@@ -32,9 +33,8 @@ local cpair = core.cpair
|
|||||||
|
|
||||||
local APP_ID = pocket.APP_ID
|
local APP_ID = pocket.APP_ID
|
||||||
|
|
||||||
-- local label = style.label
|
|
||||||
local lu_col = style.label_unit_pair
|
|
||||||
local text_fg = style.text_fg
|
local text_fg = style.text_fg
|
||||||
|
local lu_col = style.label_unit_pair
|
||||||
local basic_states = style.icon_states.basic_states
|
local basic_states = style.icon_states.basic_states
|
||||||
local mode_states = style.icon_states.mode_states
|
local mode_states = style.icon_states.mode_states
|
||||||
local red_ind_s = style.icon_states.red_ind_s
|
local red_ind_s = style.icon_states.red_ind_s
|
||||||
@@ -92,6 +92,10 @@ local function new_view(root)
|
|||||||
table.insert(list, { label = "T-" .. i, color = core.cpair(colors.black, colors.lightGray), callback = nav_links[id].turbine[i] })
|
table.insert(list, { label = "T-" .. i, color = core.cpair(colors.black, colors.lightGray), callback = nav_links[id].turbine[i] })
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if #unit.tank_data_tbl > 0 then
|
||||||
|
table.insert(list, { label = "DYN", color = core.cpair(colors.black, colors.lightGray), callback = nav_links[id].d_tank })
|
||||||
|
end
|
||||||
|
|
||||||
app.set_sidebar(list)
|
app.set_sidebar(list)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -363,6 +367,15 @@ local function new_view(root)
|
|||||||
|
|
||||||
--#endregion
|
--#endregion
|
||||||
|
|
||||||
|
--#region Dynamic Tank Tab
|
||||||
|
|
||||||
|
if #unit.tank_data_tbl > 0 then
|
||||||
|
local tank_pane = Div{parent=page_div}
|
||||||
|
nav_links[i].d_tank = dyn_tank(app, u_page, panes, tank_pane, i, unit.tank_ps_tbl[1], update)
|
||||||
|
end
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
util.nop()
|
util.nop()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
308
pocket/ui/apps/waste.lua
Normal file
308
pocket/ui/apps/waste.lua
Normal file
@@ -0,0 +1,308 @@
|
|||||||
|
--
|
||||||
|
-- Waste Control App
|
||||||
|
--
|
||||||
|
|
||||||
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
|
local iocontrol = require("pocket.iocontrol")
|
||||||
|
local pocket = require("pocket.pocket")
|
||||||
|
local process = require("pocket.process")
|
||||||
|
|
||||||
|
local style = require("pocket.ui.style")
|
||||||
|
|
||||||
|
local core = require("graphics.core")
|
||||||
|
|
||||||
|
local Div = require("graphics.elements.Div")
|
||||||
|
local MultiPane = require("graphics.elements.MultiPane")
|
||||||
|
local TextBox = require("graphics.elements.TextBox")
|
||||||
|
|
||||||
|
local WaitingAnim = require("graphics.elements.animations.Waiting")
|
||||||
|
|
||||||
|
local Checkbox = require("graphics.elements.controls.Checkbox")
|
||||||
|
local PushButton = require("graphics.elements.controls.PushButton")
|
||||||
|
local RadioButton = require("graphics.elements.controls.RadioButton")
|
||||||
|
|
||||||
|
local DataIndicator = require("graphics.elements.indicators.DataIndicator")
|
||||||
|
local IconIndicator = require("graphics.elements.indicators.IconIndicator")
|
||||||
|
local StateIndicator = require("graphics.elements.indicators.StateIndicator")
|
||||||
|
|
||||||
|
local ALIGN = core.ALIGN
|
||||||
|
local cpair = core.cpair
|
||||||
|
|
||||||
|
local APP_ID = pocket.APP_ID
|
||||||
|
|
||||||
|
local label_fg_bg = style.label
|
||||||
|
local text_fg = style.text_fg
|
||||||
|
local lu_col = style.label_unit_pair
|
||||||
|
local yel_ind_s = style.icon_states.yel_ind_s
|
||||||
|
local wht_ind_s = style.icon_states.wht_ind_s
|
||||||
|
|
||||||
|
-- new waste control page view
|
||||||
|
---@param root Container parent
|
||||||
|
local function new_view(root)
|
||||||
|
local db = iocontrol.get_db()
|
||||||
|
|
||||||
|
local frame = Div{parent=root,x=1,y=1}
|
||||||
|
|
||||||
|
local app = db.nav.register_app(APP_ID.WASTE, frame, nil, false, true)
|
||||||
|
|
||||||
|
local load_div = Div{parent=frame,x=1,y=1}
|
||||||
|
local main = Div{parent=frame,x=1,y=1}
|
||||||
|
|
||||||
|
TextBox{parent=load_div,y=12,text="Loading...",alignment=ALIGN.CENTER}
|
||||||
|
WaitingAnim{parent=load_div,x=math.floor(main.get_width()/2)-1,y=8,fg_bg=cpair(colors.brown,colors._INHERIT)}
|
||||||
|
|
||||||
|
local load_pane = MultiPane{parent=main,x=1,y=1,panes={load_div,main}}
|
||||||
|
|
||||||
|
app.set_sidebar({ { label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = db.nav.go_home } })
|
||||||
|
|
||||||
|
local page_div = nil ---@type Div|nil
|
||||||
|
|
||||||
|
-- load the app (create the elements)
|
||||||
|
local function load()
|
||||||
|
local f_ps = db.facility.ps
|
||||||
|
|
||||||
|
page_div = Div{parent=main,y=2,width=main.get_width()}
|
||||||
|
|
||||||
|
local panes = {} ---@type Div[]
|
||||||
|
local u_pages = {} ---@type nav_tree_page[]
|
||||||
|
|
||||||
|
local last_update = 0
|
||||||
|
-- refresh data callback, every 500ms it will re-send the query
|
||||||
|
local function update()
|
||||||
|
if util.time_ms() - last_update >= 500 then
|
||||||
|
db.api.get_waste()
|
||||||
|
last_update = util.time_ms()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--#region unit waste options/statistics
|
||||||
|
|
||||||
|
for i = 1, db.facility.num_units do
|
||||||
|
local u_pane = Div{parent=page_div}
|
||||||
|
local u_div = Div{parent=u_pane,x=2,width=main.get_width()-2}
|
||||||
|
local unit = db.units[i]
|
||||||
|
local u_ps = unit.unit_ps
|
||||||
|
|
||||||
|
table.insert(panes, u_div)
|
||||||
|
|
||||||
|
local u_page = app.new_page(nil, #panes)
|
||||||
|
u_page.tasks = { update }
|
||||||
|
|
||||||
|
table.insert(u_pages, u_page)
|
||||||
|
|
||||||
|
TextBox{parent=u_div,y=1,text="Reactor Unit #"..i,alignment=ALIGN.CENTER}
|
||||||
|
|
||||||
|
local function set_waste(mode) process.set_unit_waste(i, mode) end
|
||||||
|
|
||||||
|
local waste_prod = StateIndicator{parent=u_div,x=16,y=3,states=style.get_waste().states_abbrv,value=1,min_width=6}
|
||||||
|
local waste_mode = RadioButton{parent=u_div,y=3,options=style.get_waste().unit_opts,callback=set_waste,radio_colors=cpair(colors.lightGray,colors.gray),select_color=colors.white}
|
||||||
|
|
||||||
|
waste_prod.register(u_ps, "U_WasteProduct", waste_prod.update)
|
||||||
|
waste_mode.register(u_ps, "U_WasteMode", waste_mode.set_value)
|
||||||
|
|
||||||
|
TextBox{parent=u_div,y=8,text="Plutonium (Pellets)",fg_bg=label_fg_bg}
|
||||||
|
local pu = DataIndicator{parent=u_div,label="",format="%16.3f",value=0,unit="mB/t",lu_colors=lu_col,width=21,fg_bg=text_fg}
|
||||||
|
TextBox{parent=u_div,y=11,text="Polonium",fg_bg=label_fg_bg}
|
||||||
|
local po = DataIndicator{parent=u_div,label="",format="%16.3f",value=0,unit="mB/t",lu_colors=lu_col,width=21,fg_bg=text_fg}
|
||||||
|
TextBox{parent=u_div,y=14,text="Polonium (Pellets)",fg_bg=label_fg_bg}
|
||||||
|
local popl = DataIndicator{parent=u_div,label="",format="%16.3f",value=0,unit="mB/t",lu_colors=lu_col,width=21,fg_bg=text_fg}
|
||||||
|
|
||||||
|
pu.register(u_ps, "pu_rate", pu.update)
|
||||||
|
po.register(u_ps, "po_rate", po.update)
|
||||||
|
popl.register(u_ps, "po_pl_rate", popl.update)
|
||||||
|
|
||||||
|
local sna_div = Div{parent=u_pane,x=2,width=page_div.get_width()-2}
|
||||||
|
table.insert(panes, sna_div)
|
||||||
|
|
||||||
|
local sps_page = app.new_page(u_page, #panes)
|
||||||
|
sps_page.tasks = { update }
|
||||||
|
|
||||||
|
PushButton{parent=u_div,x=6,y=18,text="SNA DATA",min_width=12,fg_bg=cpair(colors.lightGray,colors.gray),active_fg_bg=cpair(colors.gray,colors.lightGray),callback=sps_page.nav_to}
|
||||||
|
PushButton{parent=sna_div,x=9,y=18,text="BACK",min_width=6,fg_bg=cpair(colors.lightGray,colors.gray),active_fg_bg=cpair(colors.gray,colors.lightGray),callback=u_page.nav_to}
|
||||||
|
|
||||||
|
TextBox{parent=sna_div,y=1,text="Unit "..i.." SNAs",alignment=ALIGN.CENTER}
|
||||||
|
TextBox{parent=sna_div,y=3,text="Connected",fg_bg=label_fg_bg}
|
||||||
|
local count = DataIndicator{parent=sna_div,x=20,y=3,label="",format="%2d",value=0,unit="",lu_colors=lu_col,width=2,fg_bg=text_fg}
|
||||||
|
|
||||||
|
TextBox{parent=sna_div,y=5,text="Peak Possible Rate\n In\n Out",fg_bg=label_fg_bg}
|
||||||
|
local peak_i = DataIndicator{parent=sna_div,x=6,y=6,label="",format="%11.2f",value=0,unit="mB/t",lu_colors=lu_col,width=17,fg_bg=text_fg}
|
||||||
|
local peak_o = DataIndicator{parent=sna_div,x=6,label="",format="%11.2f",value=0,unit="mB/t",lu_colors=lu_col,width=17,fg_bg=text_fg}
|
||||||
|
|
||||||
|
TextBox{parent=sna_div,y=9,text="Current Maximum Rate\n In\n Out",fg_bg=label_fg_bg}
|
||||||
|
local max_i = DataIndicator{parent=sna_div,x=6,y=10,label="",format="%11.2f",value=0,unit="mB/t",lu_colors=lu_col,width=17,fg_bg=text_fg}
|
||||||
|
local max_o = DataIndicator{parent=sna_div,x=6,label="",format="%11.2f",value=0,unit="mB/t",lu_colors=lu_col,width=17,fg_bg=text_fg}
|
||||||
|
|
||||||
|
TextBox{parent=sna_div,y=13,text="Current Rate\n In\n Out",fg_bg=label_fg_bg}
|
||||||
|
local cur_i = DataIndicator{parent=sna_div,x=6,y=14,label="",format="%11.2f",value=0,unit="mB/t",lu_colors=lu_col,width=17,fg_bg=text_fg}
|
||||||
|
local cur_o = DataIndicator{parent=sna_div,x=6,label="",format="%11.2f",value=0,unit="mB/t",lu_colors=lu_col,width=17,fg_bg=text_fg}
|
||||||
|
|
||||||
|
count.register(u_ps, "sna_count", count.update)
|
||||||
|
peak_i.register(u_ps, "sna_peak_rate", function (x) peak_i.update(x * 10) end)
|
||||||
|
peak_o.register(u_ps, "sna_peak_rate", peak_o.update)
|
||||||
|
max_i.register(u_ps, "sna_max_rate", function (x) max_i.update(x * 10) end)
|
||||||
|
max_o.register(u_ps, "sna_max_rate", max_o.update)
|
||||||
|
cur_i.register(u_ps, "sna_out_rate", function (x) cur_i.update(x * 10) end)
|
||||||
|
cur_o.register(u_ps, "sna_out_rate", cur_o.update)
|
||||||
|
end
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
|
--#region waste control page
|
||||||
|
|
||||||
|
local c_pane = Div{parent=page_div}
|
||||||
|
local c_div = Div{parent=c_pane,x=2,width=main.get_width()-2}
|
||||||
|
table.insert(panes, c_div)
|
||||||
|
|
||||||
|
local wst_ctrl = app.new_page(nil, #panes)
|
||||||
|
wst_ctrl.tasks = { update }
|
||||||
|
|
||||||
|
TextBox{parent=c_div,y=1,text="Waste Control",alignment=ALIGN.CENTER}
|
||||||
|
|
||||||
|
local status = StateIndicator{parent=c_div,x=3,y=3,states=style.get_waste().states,value=1,min_width=17}
|
||||||
|
local waste_prod = RadioButton{parent=c_div,y=5,options=style.get_waste().options,callback=process.set_process_waste,radio_colors=cpair(colors.lightGray,colors.gray),select_color=colors.white}
|
||||||
|
|
||||||
|
status.register(f_ps, "current_waste_product", status.update)
|
||||||
|
waste_prod.register(f_ps, "process_waste_product", waste_prod.set_value)
|
||||||
|
|
||||||
|
local fb_active = IconIndicator{parent=c_div,y=9,label="Fallback Active",states=wht_ind_s}
|
||||||
|
local sps_disabled = IconIndicator{parent=c_div,y=10,label="SPS Disabled LC",states=yel_ind_s}
|
||||||
|
|
||||||
|
fb_active.register(f_ps, "pu_fallback_active", fb_active.update)
|
||||||
|
sps_disabled.register(f_ps, "sps_disabled_low_power", sps_disabled.update)
|
||||||
|
|
||||||
|
TextBox{parent=c_div,y=12,text="Nuclear Waste In",fg_bg=label_fg_bg}
|
||||||
|
local sum_raw_waste = DataIndicator{parent=c_div,label="",format="%16.3f",value=0,unit="mB/t",lu_colors=lu_col,width=21,fg_bg=text_fg}
|
||||||
|
|
||||||
|
sum_raw_waste.register(f_ps, "burn_sum", sum_raw_waste.update)
|
||||||
|
|
||||||
|
TextBox{parent=c_div,y=15,text="Spent Waste Out",fg_bg=label_fg_bg}
|
||||||
|
local sum_sp_waste = DataIndicator{parent=c_div,label="",format="%16.3f",value=0,unit="mB/t",lu_colors=lu_col,width=21,fg_bg=text_fg}
|
||||||
|
|
||||||
|
sum_sp_waste.register(f_ps, "spent_waste_rate", sum_sp_waste.update)
|
||||||
|
|
||||||
|
local stats_div = Div{parent=c_pane,x=2,width=page_div.get_width()-2}
|
||||||
|
table.insert(panes, stats_div)
|
||||||
|
|
||||||
|
local stats_page = app.new_page(wst_ctrl, #panes)
|
||||||
|
stats_page.tasks = { update }
|
||||||
|
|
||||||
|
PushButton{parent=c_div,x=6,y=18,text="PROD RATES",min_width=12,fg_bg=cpair(colors.lightGray,colors.gray),active_fg_bg=cpair(colors.gray,colors.lightGray),callback=stats_page.nav_to}
|
||||||
|
PushButton{parent=stats_div,x=9,y=18,text="BACK",min_width=6,fg_bg=cpair(colors.lightGray,colors.gray),active_fg_bg=cpair(colors.gray,colors.lightGray),callback=wst_ctrl.nav_to}
|
||||||
|
|
||||||
|
TextBox{parent=stats_div,y=1,text="Production Rates",alignment=ALIGN.CENTER}
|
||||||
|
|
||||||
|
TextBox{parent=stats_div,y=3,text="Plutonium (Pellets)",fg_bg=label_fg_bg}
|
||||||
|
local pu = DataIndicator{parent=stats_div,label="",format="%16.3f",value=0,unit="mB/t",lu_colors=lu_col,width=21,fg_bg=text_fg}
|
||||||
|
TextBox{parent=stats_div,y=6,text="Polonium",fg_bg=label_fg_bg}
|
||||||
|
local po = DataIndicator{parent=stats_div,label="",format="%16.3f",value=0,unit="mB/t",lu_colors=lu_col,width=21,fg_bg=text_fg}
|
||||||
|
TextBox{parent=stats_div,y=9,text="Polonium (Pellets)",fg_bg=label_fg_bg}
|
||||||
|
local popl = DataIndicator{parent=stats_div,label="",format="%16.3f",value=0,unit="mB/t",lu_colors=lu_col,width=21,fg_bg=text_fg}
|
||||||
|
|
||||||
|
pu.register(f_ps, "pu_rate", pu.update)
|
||||||
|
po.register(f_ps, "po_rate", po.update)
|
||||||
|
popl.register(f_ps, "po_pl_rate", popl.update)
|
||||||
|
|
||||||
|
TextBox{parent=stats_div,y=12,text="Antimatter",fg_bg=label_fg_bg}
|
||||||
|
local am = DataIndicator{parent=stats_div,label="",format="%16d",value=0,unit="\xb5B/t",lu_colors=lu_col,width=21,fg_bg=text_fg}
|
||||||
|
|
||||||
|
am.register(f_ps, "sps_process_rate", function (r) am.update(r * 1000) end)
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
|
--#region waste options page
|
||||||
|
|
||||||
|
local o_pane = Div{parent=page_div}
|
||||||
|
local o_div = Div{parent=o_pane,x=2,width=main.get_width()-2}
|
||||||
|
table.insert(panes, o_pane)
|
||||||
|
|
||||||
|
local opt_page = app.new_page(nil, #panes)
|
||||||
|
opt_page.tasks = { update }
|
||||||
|
|
||||||
|
TextBox{parent=o_div,y=1,text="Waste Options",alignment=ALIGN.CENTER}
|
||||||
|
|
||||||
|
local pu_fallback = Checkbox{parent=o_div,x=2,y=3,label="Pu Fallback",callback=process.set_pu_fallback,box_fg_bg=cpair(colors.white,colors.gray)}
|
||||||
|
|
||||||
|
TextBox{parent=o_div,x=2,y=5,height=3,text="Switch to Pu when SNAs cannot keep up with waste.",fg_bg=label_fg_bg}
|
||||||
|
|
||||||
|
local lc_sps = Checkbox{parent=o_div,x=2,y=9,label="Low Charge SPS",callback=process.set_sps_low_power,box_fg_bg=cpair(colors.white,colors.gray)}
|
||||||
|
|
||||||
|
TextBox{parent=o_div,x=2,y=11,height=3,text="Use SPS at low charge, otherwise switches to Po.",fg_bg=label_fg_bg}
|
||||||
|
|
||||||
|
pu_fallback.register(f_ps, "process_pu_fallback", pu_fallback.set_value)
|
||||||
|
lc_sps.register(f_ps, "process_sps_low_power", lc_sps.set_value)
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
|
--#region SPS page
|
||||||
|
|
||||||
|
local s_pane = Div{parent=page_div}
|
||||||
|
local s_div = Div{parent=s_pane,x=2,width=main.get_width()-2}
|
||||||
|
table.insert(panes, s_pane)
|
||||||
|
|
||||||
|
local sps_page = app.new_page(nil, #panes)
|
||||||
|
sps_page.tasks = { update }
|
||||||
|
|
||||||
|
TextBox{parent=s_div,y=1,text="Facility SPS",alignment=ALIGN.CENTER}
|
||||||
|
|
||||||
|
local sps_status = StateIndicator{parent=s_div,x=5,y=3,states=style.sps.states,value=1,min_width=12}
|
||||||
|
|
||||||
|
sps_status.register(db.facility.sps_ps_tbl[1], "SPSStateStatus", sps_status.update)
|
||||||
|
|
||||||
|
TextBox{parent=s_div,y=5,text="Input Rate",width=10,fg_bg=label_fg_bg}
|
||||||
|
local sps_in = DataIndicator{parent=s_div,label="",format="%16.2f",value=0,unit="mB/t",lu_colors=lu_col,width=21,fg_bg=text_fg}
|
||||||
|
|
||||||
|
sps_in.register(f_ps, "po_am_rate", sps_in.update)
|
||||||
|
|
||||||
|
TextBox{parent=s_div,y=8,text="Production Rate",width=15,fg_bg=label_fg_bg}
|
||||||
|
local sps_rate = DataIndicator{parent=s_div,label="",format="%16d",value=0,unit="\xb5B/t",lu_colors=lu_col,width=21,fg_bg=text_fg}
|
||||||
|
|
||||||
|
sps_rate.register(f_ps, "sps_process_rate", function (r) sps_rate.update(r * 1000) end)
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
|
-- setup multipane
|
||||||
|
local w_pane = MultiPane{parent=page_div,x=1,y=1,panes=panes}
|
||||||
|
app.set_root_pane(w_pane)
|
||||||
|
|
||||||
|
-- setup sidebar
|
||||||
|
|
||||||
|
local list = {
|
||||||
|
{ label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = db.nav.go_home },
|
||||||
|
{ label = "WST", color = core.cpair(colors.black, colors.brown), callback = wst_ctrl.nav_to },
|
||||||
|
{ label = "OPT", color = core.cpair(colors.black, colors.white), callback = opt_page.nav_to },
|
||||||
|
{ label = "SPS", color = core.cpair(colors.black, colors.purple), callback = sps_page.nav_to }
|
||||||
|
}
|
||||||
|
|
||||||
|
for i = 1, db.facility.num_units do
|
||||||
|
table.insert(list, { label = "U-" .. i, color = core.cpair(colors.black, colors.lightGray), callback = u_pages[i].nav_to })
|
||||||
|
end
|
||||||
|
|
||||||
|
app.set_sidebar(list)
|
||||||
|
|
||||||
|
-- done, show the app
|
||||||
|
wst_ctrl.nav_to()
|
||||||
|
load_pane.set_value(2)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- delete the elements and switch back to the loading screen
|
||||||
|
local function unload()
|
||||||
|
if page_div then
|
||||||
|
page_div.delete()
|
||||||
|
page_div = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
app.set_sidebar({ { label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = db.nav.go_home } })
|
||||||
|
app.delete_pages()
|
||||||
|
|
||||||
|
-- show loading screen
|
||||||
|
load_pane.set_value(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
app.set_load(load)
|
||||||
|
app.set_unload(unload)
|
||||||
|
|
||||||
|
return main
|
||||||
|
end
|
||||||
|
|
||||||
|
return new_view
|
||||||
@@ -148,7 +148,7 @@ doc("auto_ramping", "Process Ramping", "Automatic process control is performing
|
|||||||
doc("auto_saturated", "Min/Max Burn Rate", "Auto control has either commanded 0 mB/t or the maximum total burn rate available (from assigned units).")
|
doc("auto_saturated", "Min/Max Burn Rate", "Auto control has either commanded 0 mB/t or the maximum total burn rate available (from assigned units).")
|
||||||
sect("Automatic SCRAM")
|
sect("Automatic SCRAM")
|
||||||
doc("auto_scram", "Automatic SCRAM", "Automatic control system SCRAM'ed the assigned reactors due to a safety hazard, shown by the below indicators.")
|
doc("auto_scram", "Automatic SCRAM", "Automatic control system SCRAM'ed the assigned reactors due to a safety hazard, shown by the below indicators.")
|
||||||
doc("as_matrix_dc", "Matrix Disconnected", "Automatic SCRAM occurred due to loss of induction matrix connection.")
|
doc("as_matrix_fault", "Matrix Fault", "Automatic SCRAM occurred due to the loss of the induction matrix connection, or the matrix being unformed or faulted.")
|
||||||
doc("as_matrix_fill", "Matrix Charge High", "Automatic SCRAM occurred due to induction matrix charge exceeding acceptable limit.")
|
doc("as_matrix_fill", "Matrix Charge High", "Automatic SCRAM occurred due to induction matrix charge exceeding acceptable limit.")
|
||||||
doc("as_crit_alarm", "Unit Critical Alarm", "Automatic SCRAM occurred due to critical level unit alarm(s).")
|
doc("as_crit_alarm", "Unit Critical Alarm", "Automatic SCRAM occurred due to critical level unit alarm(s).")
|
||||||
doc("as_radiation", "Facility Radiation High", "Automatic SCRAM occurred due to high facility radiation levels.")
|
doc("as_radiation", "Facility Radiation High", "Automatic SCRAM occurred due to high facility radiation levels.")
|
||||||
|
|||||||
@@ -10,11 +10,14 @@ local pocket = require("pocket.pocket")
|
|||||||
local control_app = require("pocket.ui.apps.control")
|
local control_app = require("pocket.ui.apps.control")
|
||||||
local diag_apps = require("pocket.ui.apps.diag_apps")
|
local diag_apps = require("pocket.ui.apps.diag_apps")
|
||||||
local dummy_app = require("pocket.ui.apps.dummy_app")
|
local dummy_app = require("pocket.ui.apps.dummy_app")
|
||||||
|
local facil_app = require("pocket.ui.apps.facility")
|
||||||
local guide_app = require("pocket.ui.apps.guide")
|
local guide_app = require("pocket.ui.apps.guide")
|
||||||
local loader_app = require("pocket.ui.apps.loader")
|
local loader_app = require("pocket.ui.apps.loader")
|
||||||
local process_app = require("pocket.ui.apps.process")
|
local process_app = require("pocket.ui.apps.process")
|
||||||
|
local rad_app = require("pocket.ui.apps.radiation")
|
||||||
local sys_apps = require("pocket.ui.apps.sys_apps")
|
local sys_apps = require("pocket.ui.apps.sys_apps")
|
||||||
local unit_app = require("pocket.ui.apps.unit")
|
local unit_app = require("pocket.ui.apps.unit")
|
||||||
|
local waste_app = require("pocket.ui.apps.waste")
|
||||||
|
|
||||||
local home_page = require("pocket.ui.pages.home_page")
|
local home_page = require("pocket.ui.pages.home_page")
|
||||||
|
|
||||||
@@ -44,7 +47,7 @@ local function init(main)
|
|||||||
local db = iocontrol.get_db()
|
local db = iocontrol.get_db()
|
||||||
|
|
||||||
-- window header message and connection status
|
-- window header message and connection status
|
||||||
TextBox{parent=main,y=1,text="EARLY ACCESS ALPHA S C ",fg_bg=style.header}
|
TextBox{parent=main,y=1,text=" S C ",fg_bg=style.header}
|
||||||
local svr_conn = SignalBar{parent=main,y=1,x=22,compact=true,colors_low_med=cpair(colors.red,colors.yellow),disconnect_color=colors.lightGray,fg_bg=cpair(colors.green,colors.gray)}
|
local svr_conn = SignalBar{parent=main,y=1,x=22,compact=true,colors_low_med=cpair(colors.red,colors.yellow),disconnect_color=colors.lightGray,fg_bg=cpair(colors.green,colors.gray)}
|
||||||
local crd_conn = SignalBar{parent=main,y=1,x=26,compact=true,colors_low_med=cpair(colors.red,colors.yellow),disconnect_color=colors.lightGray,fg_bg=cpair(colors.green,colors.gray)}
|
local crd_conn = SignalBar{parent=main,y=1,x=26,compact=true,colors_low_med=cpair(colors.red,colors.yellow),disconnect_color=colors.lightGray,fg_bg=cpair(colors.green,colors.gray)}
|
||||||
|
|
||||||
@@ -64,9 +67,12 @@ local function init(main)
|
|||||||
-- create all the apps & pages
|
-- create all the apps & pages
|
||||||
home_page(page_div)
|
home_page(page_div)
|
||||||
unit_app(page_div)
|
unit_app(page_div)
|
||||||
|
facil_app(page_div)
|
||||||
control_app(page_div)
|
control_app(page_div)
|
||||||
process_app(page_div)
|
process_app(page_div)
|
||||||
|
waste_app(page_div)
|
||||||
guide_app(page_div)
|
guide_app(page_div)
|
||||||
|
rad_app(page_div)
|
||||||
loader_app(page_div)
|
loader_app(page_div)
|
||||||
sys_apps(page_div)
|
sys_apps(page_div)
|
||||||
diag_apps(page_div)
|
diag_apps(page_div)
|
||||||
|
|||||||
97
pocket/ui/pages/dynamic_tank.lua
Normal file
97
pocket/ui/pages/dynamic_tank.lua
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
local types = require("scada-common.types")
|
||||||
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
|
local iocontrol = require("pocket.iocontrol")
|
||||||
|
|
||||||
|
local style = require("pocket.ui.style")
|
||||||
|
|
||||||
|
local core = require("graphics.core")
|
||||||
|
|
||||||
|
local Div = require("graphics.elements.Div")
|
||||||
|
local TextBox = require("graphics.elements.TextBox")
|
||||||
|
|
||||||
|
local DataIndicator = require("graphics.elements.indicators.DataIndicator")
|
||||||
|
local HorizontalBar = require("graphics.elements.indicators.HorizontalBar")
|
||||||
|
local IconIndicator = require("graphics.elements.indicators.IconIndicator")
|
||||||
|
local StateIndicator = require("graphics.elements.indicators.StateIndicator")
|
||||||
|
|
||||||
|
local CONTAINER_MODE = types.CONTAINER_MODE
|
||||||
|
local COOLANT_TYPE = types.COOLANT_TYPE
|
||||||
|
|
||||||
|
local ALIGN = core.ALIGN
|
||||||
|
local cpair = core.cpair
|
||||||
|
|
||||||
|
local label = style.label
|
||||||
|
local lu_col = style.label_unit_pair
|
||||||
|
local text_fg = style.text_fg
|
||||||
|
|
||||||
|
local mode_ind_s = {
|
||||||
|
{ color = cpair(colors.black, colors.lightGray), symbol = "-" },
|
||||||
|
{ color = cpair(colors.black, colors.white), symbol = "+" }
|
||||||
|
}
|
||||||
|
|
||||||
|
-- create a dynamic tank view for the unit or facility app
|
||||||
|
---@param app pocket_app
|
||||||
|
---@param page nav_tree_page|nil parent page, if applicable
|
||||||
|
---@param panes Div[]
|
||||||
|
---@param tank_pane Div
|
||||||
|
---@param tank_id integer global facility tank ID (as used for tank list, etc)
|
||||||
|
---@param ps psil
|
||||||
|
---@param update function
|
||||||
|
return function (app, page, panes, tank_pane, tank_id, ps, update)
|
||||||
|
local fac = iocontrol.get_db().facility
|
||||||
|
|
||||||
|
local tank_div = Div{parent=tank_pane,x=2,width=tank_pane.get_width()-2}
|
||||||
|
table.insert(panes, tank_div)
|
||||||
|
|
||||||
|
local tank_page = app.new_page(page, #panes)
|
||||||
|
tank_page.tasks = { update }
|
||||||
|
|
||||||
|
local tank_assign = ""
|
||||||
|
local f_tank_count = 0
|
||||||
|
|
||||||
|
for i = 1, #fac.tank_list do
|
||||||
|
local is_fac = fac.tank_list[i] == 2
|
||||||
|
if is_fac then f_tank_count = f_tank_count + 1 end
|
||||||
|
|
||||||
|
if i == tank_id then
|
||||||
|
tank_assign = util.trinary(is_fac, "F-" .. f_tank_count, "U-" .. i)
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
TextBox{parent=tank_div,y=1,text="Dynamic Tank "..tank_assign,alignment=ALIGN.CENTER}
|
||||||
|
local status = StateIndicator{parent=tank_div,x=5,y=3,states=style.dtank.states,value=1,min_width=12}
|
||||||
|
status.register(ps, "DynamicTankStateStatus", status.update)
|
||||||
|
|
||||||
|
TextBox{parent=tank_div,y=5,text="Fill",width=10,fg_bg=label}
|
||||||
|
local tank_pcnt = DataIndicator{parent=tank_div,x=14,y=5,label="",format="%5.2f",value=100,unit="%",lu_colors=lu_col,width=8,fg_bg=text_fg}
|
||||||
|
local tank_amnt = DataIndicator{parent=tank_div,label="",format="%18d",value=0,commas=true,unit="mB",lu_colors=lu_col,width=21,fg_bg=text_fg}
|
||||||
|
|
||||||
|
local is_water = fac.tank_fluid_types[tank_id] == COOLANT_TYPE.WATER
|
||||||
|
|
||||||
|
TextBox{parent=tank_div,y=8,text=util.trinary(is_water,"Water","Sodium").." Level",width=12,fg_bg=label}
|
||||||
|
local level = HorizontalBar{parent=tank_div,y=9,bar_fg_bg=cpair(util.trinary(is_water,colors.blue,colors.lightBlue),colors.gray),height=1,width=21}
|
||||||
|
|
||||||
|
TextBox{parent=tank_div,y=11,text="Tank Fill Mode",width=14,fg_bg=label}
|
||||||
|
local can_fill = IconIndicator{parent=tank_div,y=12,label="Fill",states=mode_ind_s}
|
||||||
|
local can_empty = IconIndicator{parent=tank_div,y=13,label="Empty",states=mode_ind_s}
|
||||||
|
|
||||||
|
local function _can_fill(mode)
|
||||||
|
can_fill.update((mode == CONTAINER_MODE.BOTH) or (mode == CONTAINER_MODE.FILL))
|
||||||
|
end
|
||||||
|
|
||||||
|
local function _can_empty(mode)
|
||||||
|
can_empty.update((mode == CONTAINER_MODE.BOTH) or (mode == CONTAINER_MODE.EMPTY))
|
||||||
|
end
|
||||||
|
|
||||||
|
tank_pcnt.register(ps, "fill", function (f) tank_pcnt.update(f * 100) end)
|
||||||
|
tank_amnt.register(ps, "stored", function (sto) tank_amnt.update(sto.amount) end)
|
||||||
|
|
||||||
|
level.register(ps, "fill", level.update)
|
||||||
|
|
||||||
|
can_fill.register(ps, "container_mode", _can_fill)
|
||||||
|
can_empty.register(ps, "container_mode", _can_empty)
|
||||||
|
|
||||||
|
return tank_page.nav_to
|
||||||
|
end
|
||||||
121
pocket/ui/pages/facility_matrix.lua
Normal file
121
pocket/ui/pages/facility_matrix.lua
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
local iocontrol = require("pocket.iocontrol")
|
||||||
|
|
||||||
|
local style = require("pocket.ui.style")
|
||||||
|
|
||||||
|
local core = require("graphics.core")
|
||||||
|
|
||||||
|
local Div = require("graphics.elements.Div")
|
||||||
|
local TextBox = require("graphics.elements.TextBox")
|
||||||
|
|
||||||
|
local PushButton = require("graphics.elements.controls.PushButton")
|
||||||
|
|
||||||
|
local DataIndicator = require("graphics.elements.indicators.DataIndicator")
|
||||||
|
local HorizontalBar = require("graphics.elements.indicators.HorizontalBar")
|
||||||
|
local IconIndicator = require("graphics.elements.indicators.IconIndicator")
|
||||||
|
local PowerIndicator = require("graphics.elements.indicators.PowerIndicator")
|
||||||
|
local StateIndicator = require("graphics.elements.indicators.StateIndicator")
|
||||||
|
|
||||||
|
local ALIGN = core.ALIGN
|
||||||
|
local cpair = core.cpair
|
||||||
|
|
||||||
|
local label = style.label
|
||||||
|
local lu_col = style.label_unit_pair
|
||||||
|
local text_fg = style.text_fg
|
||||||
|
|
||||||
|
local yel_ind_s = style.icon_states.yel_ind_s
|
||||||
|
local wht_ind_s = style.icon_states.wht_ind_s
|
||||||
|
|
||||||
|
-- create an induction matrix view for the facility app
|
||||||
|
---@param app pocket_app
|
||||||
|
---@param panes Div[]
|
||||||
|
---@param matrix_pane Div
|
||||||
|
---@param ps psil
|
||||||
|
---@param update function
|
||||||
|
return function (app, panes, matrix_pane, ps, update)
|
||||||
|
local db = iocontrol.get_db()
|
||||||
|
local fac = db.facility
|
||||||
|
|
||||||
|
local mtx_div = Div{parent=matrix_pane,x=2,width=matrix_pane.get_width()-2}
|
||||||
|
table.insert(panes, mtx_div)
|
||||||
|
|
||||||
|
local matrix_page = app.new_page(nil, #panes)
|
||||||
|
matrix_page.tasks = { update }
|
||||||
|
|
||||||
|
TextBox{parent=mtx_div,y=1,text="Induction Matrix",alignment=ALIGN.CENTER}
|
||||||
|
local status = StateIndicator{parent=mtx_div,x=5,y=3,states=style.imatrix.states,value=1,min_width=12}
|
||||||
|
status.register(ps, "InductionMatrixStateStatus", status.update)
|
||||||
|
|
||||||
|
TextBox{parent=mtx_div,text="Chg",y=5,fg_bg=label}
|
||||||
|
local chg_bar = HorizontalBar{parent=mtx_div,x=5,y=5,height=1,fg_bg=cpair(colors.green,colors.gray)}
|
||||||
|
TextBox{parent=mtx_div,text="In",y=7,fg_bg=label}
|
||||||
|
local in_bar = HorizontalBar{parent=mtx_div,x=5,y=7,height=1,fg_bg=cpair(colors.blue,colors.gray)}
|
||||||
|
TextBox{parent=mtx_div,text="Out",y=9,fg_bg=label}
|
||||||
|
local out_bar = HorizontalBar{parent=mtx_div,x=5,y=9,height=1,fg_bg=cpair(colors.red,colors.gray)}
|
||||||
|
|
||||||
|
local function calc_saturation(val)
|
||||||
|
local data = fac.induction_data_tbl[1]
|
||||||
|
if (type(data.build) == "table") and (type(data.build.transfer_cap) == "number") and (data.build.transfer_cap > 0) then
|
||||||
|
return val / data.build.transfer_cap
|
||||||
|
else return 0 end
|
||||||
|
end
|
||||||
|
|
||||||
|
chg_bar.register(ps, "energy_fill", chg_bar.update)
|
||||||
|
in_bar.register(ps, "last_input", function (val) in_bar.update(calc_saturation(val)) end)
|
||||||
|
out_bar.register(ps, "last_output", function (val) out_bar.update(calc_saturation(val)) end)
|
||||||
|
|
||||||
|
local energy = PowerIndicator{parent=mtx_div,y=11,lu_colors=lu_col,label="Chg: ",unit=db.energy_label,format="%8.2f",value=0,width=21,fg_bg=text_fg}
|
||||||
|
local avg_chg = PowerIndicator{parent=mtx_div,lu_colors=lu_col,label="\xb7Avg: ",unit=db.energy_label,format="%8.2f",value=0,width=21,fg_bg=text_fg}
|
||||||
|
local input = PowerIndicator{parent=mtx_div,lu_colors=lu_col,label="In: ",unit=db.energy_label,format="%8.2f",rate=true,value=0,width=21,fg_bg=text_fg}
|
||||||
|
local avg_in = PowerIndicator{parent=mtx_div,lu_colors=lu_col,label="\xb7Avg: ",unit=db.energy_label,format="%8.2f",rate=true,value=0,width=21,fg_bg=text_fg}
|
||||||
|
local output = PowerIndicator{parent=mtx_div,lu_colors=lu_col,label="Out: ",unit=db.energy_label,format="%8.2f",rate=true,value=0,width=21,fg_bg=text_fg}
|
||||||
|
local avg_out = PowerIndicator{parent=mtx_div,lu_colors=lu_col,label="\xb7Avg: ",unit=db.energy_label,format="%8.2f",rate=true,value=0,width=21,fg_bg=text_fg}
|
||||||
|
|
||||||
|
energy.register(ps, "energy", function (val) energy.update(db.energy_convert(val)) end)
|
||||||
|
avg_chg.register(ps, "avg_charge", avg_chg.update)
|
||||||
|
input.register(ps, "last_input", function (val) input.update(db.energy_convert(val)) end)
|
||||||
|
avg_in.register(ps, "avg_inflow", avg_in.update)
|
||||||
|
output.register(ps, "last_output", function (val) output.update(db.energy_convert(val)) end)
|
||||||
|
avg_out.register(ps, "avg_outflow", avg_out.update)
|
||||||
|
|
||||||
|
local mtx_ext_div = Div{parent=matrix_pane,x=2,width=matrix_pane.get_width()-2}
|
||||||
|
table.insert(panes, mtx_ext_div)
|
||||||
|
|
||||||
|
local mtx_ext_page = app.new_page(matrix_page, #panes)
|
||||||
|
mtx_ext_page.tasks = { update }
|
||||||
|
|
||||||
|
PushButton{parent=mtx_div,x=9,y=18,text="MORE",min_width=6,fg_bg=cpair(colors.lightGray,colors.gray),active_fg_bg=cpair(colors.gray,colors.lightGray),callback=mtx_ext_page.nav_to}
|
||||||
|
PushButton{parent=mtx_ext_div,x=9,y=18,text="BACK",min_width=6,fg_bg=cpair(colors.lightGray,colors.gray),active_fg_bg=cpair(colors.gray,colors.lightGray),callback=matrix_page.nav_to}
|
||||||
|
|
||||||
|
TextBox{parent=mtx_ext_div,y=1,text="More Matrix Info",alignment=ALIGN.CENTER}
|
||||||
|
|
||||||
|
local chging = IconIndicator{parent=mtx_ext_div,y=3,label="Charging",states=wht_ind_s}
|
||||||
|
local dischg = IconIndicator{parent=mtx_ext_div,y=4,label="Discharging",states=wht_ind_s}
|
||||||
|
|
||||||
|
TextBox{parent=mtx_ext_div,text="Energy Fill",x=1,y=6,width=13,fg_bg=label}
|
||||||
|
local fill = DataIndicator{parent=mtx_ext_div,x=14,y=6,lu_colors=lu_col,label="",unit="%",format="%6.2f",value=0,width=8,fg_bg=text_fg}
|
||||||
|
|
||||||
|
chging.register(ps, "is_charging", chging.update)
|
||||||
|
dischg.register(ps, "is_discharging", dischg.update)
|
||||||
|
fill.register(ps, "energy_fill", function (x) fill.update(x * 100) end)
|
||||||
|
|
||||||
|
local max_io = IconIndicator{parent=mtx_ext_div,y=8,label="Max I/O Rate",states=yel_ind_s}
|
||||||
|
|
||||||
|
TextBox{parent=mtx_ext_div,text="Input Util.",x=1,y=10,width=13,fg_bg=label}
|
||||||
|
local in_util = DataIndicator{parent=mtx_ext_div,x=14,y=10,lu_colors=lu_col,label="",unit="%",format="%6.2f",value=0,width=8,fg_bg=text_fg}
|
||||||
|
TextBox{parent=mtx_ext_div,text="Output Util.",x=1,y=11,width=13,fg_bg=label}
|
||||||
|
local out_util = DataIndicator{parent=mtx_ext_div,x=14,y=11,lu_colors=lu_col,label="",unit="%",format="%6.2f",value=0,width=8,fg_bg=text_fg}
|
||||||
|
|
||||||
|
max_io.register(ps, "at_max_io", max_io.update)
|
||||||
|
in_util.register(ps, "last_input", function (x) in_util.update(calc_saturation(x) * 100) end)
|
||||||
|
out_util.register(ps, "last_output", function (x) out_util.update(calc_saturation(x) * 100) end)
|
||||||
|
|
||||||
|
TextBox{parent=mtx_ext_div,text="Capacity ("..db.energy_label..")",x=1,y=13,fg_bg=label}
|
||||||
|
local capacity = DataIndicator{parent=mtx_ext_div,y=14,lu_colors=lu_col,label="",unit="",format="%21d",value=0,width=21,fg_bg=text_fg}
|
||||||
|
TextBox{parent=mtx_ext_div,text="Max In/Out ("..db.energy_label.."/t)",x=1,y=15,fg_bg=label}
|
||||||
|
local trans_cap = DataIndicator{parent=mtx_ext_div,y=16,lu_colors=lu_col,label="",unit="",format="%21d",rate=true,value=0,width=21,fg_bg=text_fg}
|
||||||
|
|
||||||
|
capacity.register(ps, "max_energy", function (val) capacity.update(db.energy_convert(val)) end)
|
||||||
|
trans_cap.register(ps, "transfer_cap", function (val) trans_cap.update(db.energy_convert(val)) end)
|
||||||
|
|
||||||
|
return matrix_page.nav_to
|
||||||
|
end
|
||||||
84
pocket/ui/pages/facility_sps.lua
Normal file
84
pocket/ui/pages/facility_sps.lua
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
local iocontrol = require("pocket.iocontrol")
|
||||||
|
|
||||||
|
local style = require("pocket.ui.style")
|
||||||
|
|
||||||
|
local core = require("graphics.core")
|
||||||
|
|
||||||
|
local Div = require("graphics.elements.Div")
|
||||||
|
local TextBox = require("graphics.elements.TextBox")
|
||||||
|
|
||||||
|
local PushButton = require("graphics.elements.controls.PushButton")
|
||||||
|
|
||||||
|
local DataIndicator = require("graphics.elements.indicators.DataIndicator")
|
||||||
|
local HorizontalBar = require("graphics.elements.indicators.HorizontalBar")
|
||||||
|
local StateIndicator = require("graphics.elements.indicators.StateIndicator")
|
||||||
|
|
||||||
|
local ALIGN = core.ALIGN
|
||||||
|
local cpair = core.cpair
|
||||||
|
|
||||||
|
local label = style.label
|
||||||
|
local lu_col = style.label_unit_pair
|
||||||
|
local text_fg = style.text_fg
|
||||||
|
|
||||||
|
-- create an SPS view in the facility app
|
||||||
|
---@param app pocket_app
|
||||||
|
---@param panes Div[]
|
||||||
|
---@param sps_pane Div
|
||||||
|
---@param ps psil
|
||||||
|
---@param update function
|
||||||
|
return function (app, panes, sps_pane, ps, update)
|
||||||
|
local db = iocontrol.get_db()
|
||||||
|
|
||||||
|
local sps_div = Div{parent=sps_pane,x=2,width=sps_pane.get_width()-2}
|
||||||
|
table.insert(panes, sps_div)
|
||||||
|
|
||||||
|
local sps_page = app.new_page(nil, #panes)
|
||||||
|
sps_page.tasks = { update }
|
||||||
|
|
||||||
|
TextBox{parent=sps_div,y=1,text="Facility SPS",alignment=ALIGN.CENTER}
|
||||||
|
local status = StateIndicator{parent=sps_div,x=5,y=3,states=style.sps.states,value=1,min_width=12}
|
||||||
|
status.register(ps, "SPSStateStatus", status.update)
|
||||||
|
|
||||||
|
TextBox{parent=sps_div,text="Po",y=5,fg_bg=label}
|
||||||
|
local po_bar = HorizontalBar{parent=sps_div,x=4,y=5,fg_bg=cpair(colors.cyan,colors.gray),height=1}
|
||||||
|
TextBox{parent=sps_div,text="AM",y=7,fg_bg=label}
|
||||||
|
local am_bar = HorizontalBar{parent=sps_div,x=4,y=7,fg_bg=cpair(colors.purple,colors.gray),height=1}
|
||||||
|
|
||||||
|
po_bar.register(ps, "input_fill", po_bar.update)
|
||||||
|
am_bar.register(ps, "output_fill", am_bar.update)
|
||||||
|
|
||||||
|
TextBox{parent=sps_div,y=9,text="Input Rate",width=10,fg_bg=label}
|
||||||
|
local input_rate = DataIndicator{parent=sps_div,label="",format="%16.2f",value=0,unit="mB/t",lu_colors=lu_col,width=21,fg_bg=text_fg}
|
||||||
|
TextBox{parent=sps_div,y=12,text="Production Rate",width=15,fg_bg=label}
|
||||||
|
local proc_rate = DataIndicator{parent=sps_div,label="",format="%16d",value=0,unit="\xb5B/t",lu_colors=lu_col,width=21,fg_bg=text_fg}
|
||||||
|
|
||||||
|
proc_rate.register(ps, "process_rate", function (r) proc_rate.update(r * 1000) end)
|
||||||
|
input_rate.register(db.facility.ps, "po_am_rate", input_rate.update)
|
||||||
|
|
||||||
|
local sps_ext_div = Div{parent=sps_pane,x=2,width=sps_pane.get_width()-2}
|
||||||
|
table.insert(panes, sps_ext_div)
|
||||||
|
|
||||||
|
local sps_ext_page = app.new_page(sps_page, #panes)
|
||||||
|
sps_ext_page.tasks = { update }
|
||||||
|
|
||||||
|
PushButton{parent=sps_div,x=9,y=18,text="MORE",min_width=6,fg_bg=cpair(colors.lightGray,colors.gray),active_fg_bg=cpair(colors.gray,colors.lightGray),callback=sps_ext_page.nav_to}
|
||||||
|
PushButton{parent=sps_ext_div,x=9,y=18,text="BACK",min_width=6,fg_bg=cpair(colors.lightGray,colors.gray),active_fg_bg=cpair(colors.gray,colors.lightGray),callback=sps_page.nav_to}
|
||||||
|
|
||||||
|
TextBox{parent=sps_ext_div,y=1,text="More SPS Info",alignment=ALIGN.CENTER}
|
||||||
|
|
||||||
|
TextBox{parent=sps_ext_div,text="Polonium",x=1,y=3,width=13,fg_bg=label}
|
||||||
|
local input_p = DataIndicator{parent=sps_ext_div,x=14,y=3,lu_colors=lu_col,label="",unit="%",format="%6.2f",value=0,width=8,fg_bg=text_fg}
|
||||||
|
local input_amnt = DataIndicator{parent=sps_ext_div,x=1,y=4,lu_colors=lu_col,label="",unit="mB",format="%18.0f",value=0,commas=true,width=21,fg_bg=text_fg}
|
||||||
|
|
||||||
|
input_p.register(ps, "input_fill", function (x) input_p.update(x * 100) end)
|
||||||
|
input_amnt.register(ps, "input", function (x) input_amnt.update(x.amount) end)
|
||||||
|
|
||||||
|
TextBox{parent=sps_ext_div,text="Antimatter",x=1,y=6,width=15,fg_bg=label}
|
||||||
|
local output_p = DataIndicator{parent=sps_ext_div,x=14,y=6,lu_colors=lu_col,label="",unit="%",format="%6.2f",value=0,width=8,fg_bg=text_fg}
|
||||||
|
local output_amnt = DataIndicator{parent=sps_ext_div,x=1,y=7,lu_colors=lu_col,label="",unit="\xb5B",format="%18.3f",value=0,commas=true,width=21,fg_bg=text_fg}
|
||||||
|
|
||||||
|
output_p.register(ps, "output_fill", function (x) output_p.update(x * 100) end)
|
||||||
|
output_amnt.register(ps, "output", function (x) output_amnt.update(x.amount) end)
|
||||||
|
|
||||||
|
return sps_page.nav_to
|
||||||
|
end
|
||||||
@@ -29,8 +29,9 @@ local function new_view(root)
|
|||||||
|
|
||||||
local apps_1 = Div{parent=main,x=1,y=1,height=15}
|
local apps_1 = Div{parent=main,x=1,y=1,height=15}
|
||||||
local apps_2 = Div{parent=main,x=1,y=1,height=15}
|
local apps_2 = Div{parent=main,x=1,y=1,height=15}
|
||||||
|
local apps_3 = Div{parent=main,x=1,y=1,height=15}
|
||||||
|
|
||||||
local panes = { apps_1, apps_2 }
|
local panes = { apps_1, apps_2, apps_3 }
|
||||||
|
|
||||||
local app_pane = AppMultiPane{parent=main,x=1,y=1,height=18,panes=panes,active_color=colors.lightGray,nav_colors=cpair(colors.lightGray,colors.gray),scroll_nav=true,drag_nav=true,callback=app.switcher}
|
local app_pane = AppMultiPane{parent=main,x=1,y=1,height=18,panes=panes,active_color=colors.lightGray,nav_colors=cpair(colors.lightGray,colors.gray),scroll_nav=true,drag_nav=true,callback=app.switcher}
|
||||||
|
|
||||||
@@ -46,19 +47,21 @@ local function new_view(root)
|
|||||||
local active_fg_bg = cpair(colors.white,colors.gray)
|
local active_fg_bg = cpair(colors.white,colors.gray)
|
||||||
|
|
||||||
App{parent=apps_1,x=2,y=2,text="U",title="Units",callback=function()open(APP_ID.UNITS)end,app_fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=active_fg_bg}
|
App{parent=apps_1,x=2,y=2,text="U",title="Units",callback=function()open(APP_ID.UNITS)end,app_fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=active_fg_bg}
|
||||||
App{parent=apps_1,x=9,y=2,text="F",title="Facil",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.orange),active_fg_bg=active_fg_bg}
|
App{parent=apps_1,x=9,y=2,text="F",title="Facil",callback=function()open(APP_ID.FACILITY)end,app_fg_bg=cpair(colors.black,colors.orange),active_fg_bg=active_fg_bg}
|
||||||
App{parent=apps_1,x=16,y=2,text="\x15",title="Control",callback=function()open(APP_ID.CONTROL)end,app_fg_bg=cpair(colors.black,colors.green),active_fg_bg=active_fg_bg}
|
App{parent=apps_1,x=16,y=2,text="\x15",title="Control",callback=function()open(APP_ID.CONTROL)end,app_fg_bg=cpair(colors.black,colors.green),active_fg_bg=active_fg_bg}
|
||||||
App{parent=apps_1,x=2,y=7,text="\x17",title="Process",callback=function()open(APP_ID.PROCESS)end,app_fg_bg=cpair(colors.black,colors.purple),active_fg_bg=active_fg_bg}
|
App{parent=apps_1,x=2,y=7,text="\x17",title="Process",callback=function()open(APP_ID.PROCESS)end,app_fg_bg=cpair(colors.black,colors.purple),active_fg_bg=active_fg_bg}
|
||||||
App{parent=apps_1,x=9,y=7,text="\x7f",title="Waste",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.brown),active_fg_bg=active_fg_bg}
|
App{parent=apps_1,x=9,y=7,text="\x7f",title="Waste",callback=function()open(APP_ID.WASTE)end,app_fg_bg=cpair(colors.black,colors.brown),active_fg_bg=active_fg_bg}
|
||||||
App{parent=apps_1,x=16,y=7,text="\x08",title="Devices",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.lightGray),active_fg_bg=active_fg_bg}
|
App{parent=apps_1,x=16,y=7,text="\xb6",title="Guide",callback=function()open(APP_ID.GUIDE)end,app_fg_bg=cpair(colors.black,colors.cyan),active_fg_bg=active_fg_bg}
|
||||||
App{parent=apps_1,x=2,y=12,text="\xb6",title="Guide",callback=function()open(APP_ID.GUIDE)end,app_fg_bg=cpair(colors.black,colors.cyan),active_fg_bg=active_fg_bg}
|
App{parent=apps_1,x=2,y=12,text="?",title="About",callback=function()open(APP_ID.ABOUT)end,app_fg_bg=cpair(colors.black,colors.white),active_fg_bg=active_fg_bg}
|
||||||
App{parent=apps_1,x=9,y=12,text="?",title="About",callback=function()open(APP_ID.ABOUT)end,app_fg_bg=cpair(colors.black,colors.white),active_fg_bg=active_fg_bg}
|
|
||||||
|
|
||||||
TextBox{parent=apps_2,text="Diagnostic Apps",x=1,y=2,alignment=ALIGN.CENTER}
|
App{parent=apps_2,x=2,y=2,text="\x08",title="Devices",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.lightGray),active_fg_bg=active_fg_bg}
|
||||||
|
App{parent=apps_2,x=9,y=2,text="\x1e",title="Rad",callback=function()open(APP_ID.RADMON)end,app_fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=active_fg_bg}
|
||||||
|
|
||||||
App{parent=apps_2,x=2,y=4,text="\x0f",title="Alarm",callback=function()open(APP_ID.ALARMS)end,app_fg_bg=cpair(colors.black,colors.red),active_fg_bg=active_fg_bg}
|
TextBox{parent=apps_3,text="Diagnostic Apps",x=1,y=2,alignment=ALIGN.CENTER}
|
||||||
App{parent=apps_2,x=9,y=4,text="\x1e",title="LoopT",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.cyan),active_fg_bg=active_fg_bg}
|
|
||||||
App{parent=apps_2,x=16,y=4,text="@",title="Comps",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.orange),active_fg_bg=active_fg_bg}
|
App{parent=apps_3,x=2,y=4,text="\x0f",title="Alarm",callback=function()open(APP_ID.ALARMS)end,app_fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=active_fg_bg}
|
||||||
|
App{parent=apps_3,x=9,y=4,text="@",title="Comps",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.orange),active_fg_bg=active_fg_bg}
|
||||||
|
App{parent=apps_3,x=16,y=4,text="R",title="RS Test",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.red),active_fg_bg=active_fg_bg}
|
||||||
|
|
||||||
return main
|
return main
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -2,12 +2,18 @@
|
|||||||
-- Graphics Style Options
|
-- Graphics Style Options
|
||||||
--
|
--
|
||||||
|
|
||||||
local core = require("graphics.core")
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
|
local core = require("graphics.core")
|
||||||
|
|
||||||
|
local pocket = require("pocket.pocket")
|
||||||
|
|
||||||
local style = {}
|
local style = {}
|
||||||
|
|
||||||
local cpair = core.cpair
|
local cpair = core.cpair
|
||||||
|
|
||||||
|
local config = pocket.config
|
||||||
|
|
||||||
-- GLOBAL --
|
-- GLOBAL --
|
||||||
|
|
||||||
style.root = cpair(colors.white, colors.black)
|
style.root = cpair(colors.white, colors.black)
|
||||||
@@ -95,123 +101,105 @@ style.icon_states = states
|
|||||||
-- MAIN LAYOUT --
|
-- MAIN LAYOUT --
|
||||||
|
|
||||||
style.reactor = {
|
style.reactor = {
|
||||||
-- reactor states
|
-- reactor states<br>
|
||||||
|
---@see REACTOR_STATE
|
||||||
states = {
|
states = {
|
||||||
{
|
{ color = cpair(colors.black, colors.yellow), text = "OFF-LINE" },
|
||||||
color = cpair(colors.black, colors.yellow),
|
{ color = cpair(colors.black, colors.orange), text = "NOT FORMED" },
|
||||||
text = "OFF-LINE"
|
{ color = cpair(colors.black, colors.orange), text = "PLC FAULT" },
|
||||||
},
|
{ color = cpair(colors.white, colors.gray), text = "DISABLED" },
|
||||||
{
|
{ color = cpair(colors.black, colors.green), text = "ACTIVE" },
|
||||||
color = cpair(colors.black, colors.orange),
|
{ color = cpair(colors.black, colors.red), text = "SCRAMMED" },
|
||||||
text = "NOT FORMED"
|
{ color = cpair(colors.black, colors.red), text = "FORCE DSBL" }
|
||||||
},
|
|
||||||
{
|
|
||||||
color = cpair(colors.black, colors.orange),
|
|
||||||
text = "PLC FAULT"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color = cpair(colors.white, colors.gray),
|
|
||||||
text = "DISABLED"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color = cpair(colors.black, colors.green),
|
|
||||||
text = "ACTIVE"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color = cpair(colors.black, colors.red),
|
|
||||||
text = "SCRAMMED"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color = cpair(colors.black, colors.red),
|
|
||||||
text = "FORCE DSBL"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
style.boiler = {
|
style.boiler = {
|
||||||
-- boiler states
|
-- boiler states<br>
|
||||||
|
---@see BOILER_STATE
|
||||||
states = {
|
states = {
|
||||||
{
|
{ color = cpair(colors.black, colors.yellow), text = "OFF-LINE" },
|
||||||
color = cpair(colors.black, colors.yellow),
|
{ color = cpair(colors.black, colors.orange), text = "NOT FORMED" },
|
||||||
text = "OFF-LINE"
|
{ color = cpair(colors.black, colors.orange), text = "RTU FAULT" },
|
||||||
},
|
{ color = cpair(colors.white, colors.gray), text = "IDLE" },
|
||||||
{
|
{ color = cpair(colors.black, colors.green), text = "ACTIVE" }
|
||||||
color = cpair(colors.black, colors.orange),
|
|
||||||
text = "NOT FORMED"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color = cpair(colors.black, colors.orange),
|
|
||||||
text = "RTU FAULT"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color = cpair(colors.white, colors.gray),
|
|
||||||
text = "IDLE"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color = cpair(colors.black, colors.green),
|
|
||||||
text = "ACTIVE"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
style.turbine = {
|
style.turbine = {
|
||||||
-- turbine states
|
-- turbine states<br>
|
||||||
|
---@see TURBINE_STATE
|
||||||
states = {
|
states = {
|
||||||
{
|
{ color = cpair(colors.black, colors.yellow), text = "OFF-LINE" },
|
||||||
color = cpair(colors.black, colors.yellow),
|
{ color = cpair(colors.black, colors.orange), text = "NOT FORMED" },
|
||||||
text = "OFF-LINE"
|
{ color = cpair(colors.black, colors.orange), text = "RTU FAULT" },
|
||||||
},
|
{ color = cpair(colors.white, colors.gray), text = "IDLE" },
|
||||||
{
|
{ color = cpair(colors.black, colors.green), text = "ACTIVE" },
|
||||||
color = cpair(colors.black, colors.orange),
|
{ color = cpair(colors.black, colors.red), text = "TRIP" }
|
||||||
text = "NOT FORMED"
|
}
|
||||||
},
|
}
|
||||||
{
|
|
||||||
color = cpair(colors.black, colors.orange),
|
style.dtank = {
|
||||||
text = "RTU FAULT"
|
-- dynamic tank states<br>
|
||||||
},
|
---@see TANK_STATE
|
||||||
{
|
states = {
|
||||||
color = cpair(colors.white, colors.gray),
|
{ color = cpair(colors.black, colors.yellow), text = "OFF-LINE" },
|
||||||
text = "IDLE"
|
{ color = cpair(colors.black, colors.orange), text = "NOT FORMED" },
|
||||||
},
|
{ color = cpair(colors.black, colors.orange), text = "RTU FAULT" },
|
||||||
{
|
{ color = cpair(colors.black, colors.green), text = "ONLINE" },
|
||||||
color = cpair(colors.black, colors.green),
|
{ color = cpair(colors.black, colors.yellow), text = "LOW FILL" },
|
||||||
text = "ACTIVE"
|
{ color = cpair(colors.black, colors.green), text = "FILLED" }
|
||||||
},
|
|
||||||
{
|
|
||||||
color = cpair(colors.black, colors.red),
|
|
||||||
text = "TRIP"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
style.imatrix = {
|
style.imatrix = {
|
||||||
-- induction matrix states
|
-- induction matrix states<br>
|
||||||
|
---@see IMATRIX_STATE
|
||||||
states = {
|
states = {
|
||||||
{
|
{ color = cpair(colors.black, colors.yellow), text = "OFF-LINE" },
|
||||||
color = cpair(colors.black, colors.yellow),
|
{ color = cpair(colors.black, colors.orange), text = "NOT FORMED" },
|
||||||
text = "OFF-LINE"
|
{ color = cpair(colors.black, colors.orange), text = "RTU FAULT" },
|
||||||
},
|
{ color = cpair(colors.black, colors.green), text = "ONLINE" },
|
||||||
{
|
{ color = cpair(colors.black, colors.yellow), text = "LOW CHARGE" },
|
||||||
color = cpair(colors.black, colors.orange),
|
{ color = cpair(colors.black, colors.yellow), text = "HIGH CHARGE" }
|
||||||
text = "NOT FORMED"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color = cpair(colors.black, colors.orange),
|
|
||||||
text = "RTU FAULT"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color = cpair(colors.black, colors.green),
|
|
||||||
text = "ONLINE"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color = cpair(colors.black, colors.yellow),
|
|
||||||
text = "LOW CHARGE"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color = cpair(colors.black, colors.yellow),
|
|
||||||
text = "HIGH CHARGE"
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
style.sps = {
|
||||||
|
-- SPS states<br>
|
||||||
|
---@see SPS_STATE
|
||||||
|
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.white, colors.gray), text = "IDLE" },
|
||||||
|
{ color = cpair(colors.black, colors.green), text = "ACTIVE" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
-- get waste styling, which depends on the configuration
|
||||||
|
---@return { states: { color: color, text: string }, states_abbrv: { color: color, text: string }, options: string[], unit_opts: string[] }
|
||||||
|
function style.get_waste()
|
||||||
|
local pu_color = util.trinary(config.GreenPuPellet, colors.green, colors.cyan)
|
||||||
|
local po_color = util.trinary(config.GreenPuPellet, colors.cyan, colors.green)
|
||||||
|
|
||||||
|
return {
|
||||||
|
-- auto waste processing states
|
||||||
|
states = {
|
||||||
|
{ color = cpair(colors.black, pu_color), text = "PLUTONIUM" },
|
||||||
|
{ color = cpair(colors.black, po_color), text = "POLONIUM" },
|
||||||
|
{ color = cpair(colors.black, colors.purple), text = "ANTI MATTER" }
|
||||||
|
},
|
||||||
|
states_abbrv = {
|
||||||
|
{ color = cpair(colors.black, pu_color), text = "Pu" },
|
||||||
|
{ color = cpair(colors.black, po_color), text = "Po" },
|
||||||
|
{ color = cpair(colors.black, colors.purple), text = "AM" }
|
||||||
|
},
|
||||||
|
-- process radio button options
|
||||||
|
options = { "Plutonium", "Polonium", "Antimatter" },
|
||||||
|
-- unit waste selection
|
||||||
|
unit_opts = { "Auto", "Plutonium", "Polonium", "Antimatter" }
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
return style
|
return style
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ local function handle_packet(packet)
|
|||||||
elseif est_ack == ESTABLISH_ACK.COLLISION then
|
elseif est_ack == ESTABLISH_ACK.COLLISION then
|
||||||
error_msg = "another reactor PLC is connected with this reactor unit ID"
|
error_msg = "another reactor PLC is connected with this reactor unit ID"
|
||||||
elseif est_ack == ESTABLISH_ACK.BAD_VERSION then
|
elseif est_ack == ESTABLISH_ACK.BAD_VERSION then
|
||||||
error_msg = "reactor PLC comms version does not match supervisor comms version, make sure both devices are up-to-date (ccmsi update ...)"
|
error_msg = "reactor PLC comms version does not match supervisor comms version, make sure both devices are up-to-date (ccmsi update)"
|
||||||
else
|
else
|
||||||
error_msg = "error: invalid reply from supervisor"
|
error_msg = "error: invalid reply from supervisor"
|
||||||
end
|
end
|
||||||
@@ -120,11 +120,15 @@ local function self_check()
|
|||||||
|
|
||||||
self.self_check_pass = true
|
self.self_check_pass = true
|
||||||
|
|
||||||
|
local cfg = self.settings
|
||||||
local modem = ppm.get_wireless_modem()
|
local modem = ppm.get_wireless_modem()
|
||||||
local reactor = ppm.get_fission_reactor()
|
local reactor = ppm.get_fission_reactor()
|
||||||
local valid_cfg = plc.validate_config(self.settings)
|
local valid_cfg = plc.validate_config(cfg)
|
||||||
|
|
||||||
|
if cfg.Networked then
|
||||||
|
self.self_check_msg("> check wireless/ender modem connected...", modem ~= nil, "you must connect an ender or wireless modem to the reactor PLC")
|
||||||
|
end
|
||||||
|
|
||||||
self.self_check_msg("> check wireless/ender modem connected...", modem ~= nil, "you must connect an ender or wireless modem to the reactor PLC")
|
|
||||||
self.self_check_msg("> check fission reactor connected...", reactor ~= nil, "please connect the reactor PLC to the reactor's fission reactor logic adapter")
|
self.self_check_msg("> check fission reactor connected...", reactor ~= nil, "please connect the reactor PLC to the reactor's fission reactor logic adapter")
|
||||||
self.self_check_msg("> check fission reactor formed...")
|
self.self_check_msg("> check fission reactor formed...")
|
||||||
-- this consumes events, but that is fine here
|
-- this consumes events, but that is fine here
|
||||||
@@ -132,12 +136,12 @@ local function self_check()
|
|||||||
|
|
||||||
self.self_check_msg("> check configuration...", valid_cfg, "go through Configure System and apply settings to set any missing settings and repair any corrupted ones")
|
self.self_check_msg("> check configuration...", valid_cfg, "go through Configure System and apply settings to set any missing settings and repair any corrupted ones")
|
||||||
|
|
||||||
if valid_cfg and modem then
|
if cfg.Networked and valid_cfg and modem then
|
||||||
self.self_check_msg("> check supervisor connection...")
|
self.self_check_msg("> check supervisor connection...")
|
||||||
|
|
||||||
-- init mac as needed
|
-- init mac as needed
|
||||||
if self.settings.AuthKey and string.len(self.settings.AuthKey) >= 8 then
|
if cfg.AuthKey and string.len(cfg.AuthKey) >= 8 then
|
||||||
network.init_mac(self.settings.AuthKey)
|
network.init_mac(cfg.AuthKey)
|
||||||
else
|
else
|
||||||
network.deinit_mac()
|
network.deinit_mac()
|
||||||
end
|
end
|
||||||
@@ -145,12 +149,12 @@ local function self_check()
|
|||||||
self.nic = network.nic(modem)
|
self.nic = network.nic(modem)
|
||||||
|
|
||||||
self.nic.closeAll()
|
self.nic.closeAll()
|
||||||
self.nic.open(self.settings.PLC_Channel)
|
self.nic.open(cfg.PLC_Channel)
|
||||||
|
|
||||||
self.sv_addr = comms.BROADCAST
|
self.sv_addr = comms.BROADCAST
|
||||||
self.net_listen = true
|
self.net_listen = true
|
||||||
|
|
||||||
send_sv(MGMT_TYPE.ESTABLISH, { comms.version, "0.0.0", DEVICE_TYPE.PLC, self.settings.UnitID })
|
send_sv(MGMT_TYPE.ESTABLISH, { comms.version, "0.0.0", DEVICE_TYPE.PLC, cfg.UnitID })
|
||||||
|
|
||||||
tcd.dispatch_unique(8, handle_timeout)
|
tcd.dispatch_unique(8, handle_timeout)
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -82,8 +82,9 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit)
|
|||||||
local plc_c_2 = Div{parent=plc_cfg,x=2,y=4,width=49}
|
local plc_c_2 = Div{parent=plc_cfg,x=2,y=4,width=49}
|
||||||
local plc_c_3 = Div{parent=plc_cfg,x=2,y=4,width=49}
|
local plc_c_3 = Div{parent=plc_cfg,x=2,y=4,width=49}
|
||||||
local plc_c_4 = Div{parent=plc_cfg,x=2,y=4,width=49}
|
local plc_c_4 = Div{parent=plc_cfg,x=2,y=4,width=49}
|
||||||
|
local plc_c_5 = Div{parent=plc_cfg,x=2,y=4,width=49}
|
||||||
|
|
||||||
local plc_pane = MultiPane{parent=plc_cfg,x=1,y=4,panes={plc_c_1,plc_c_2,plc_c_3,plc_c_4}}
|
local plc_pane = MultiPane{parent=plc_cfg,x=1,y=4,panes={plc_c_1,plc_c_2,plc_c_3,plc_c_4,plc_c_5}}
|
||||||
|
|
||||||
TextBox{parent=plc_cfg,x=1,y=2,text=" PLC Configuration",fg_bg=cpair(colors.black,colors.orange)}
|
TextBox{parent=plc_cfg,x=1,y=2,text=" PLC Configuration",fg_bg=cpair(colors.black,colors.orange)}
|
||||||
|
|
||||||
@@ -152,13 +153,21 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit)
|
|||||||
|
|
||||||
function self.bundled_emcool(en) if en then color.enable() else color.disable() end end
|
function self.bundled_emcool(en) if en then color.enable() else color.disable() end end
|
||||||
|
|
||||||
|
TextBox{parent=plc_c_5,x=1,y=1,height=5,text="Advanced Options"}
|
||||||
|
local invert = Checkbox{parent=plc_c_5,x=1,y=3,label="Invert",default=ini_cfg.EmerCoolInvert,box_fg_bg=cpair(colors.orange,colors.black),callback=function()end}
|
||||||
|
TextBox{parent=plc_c_5,x=10,y=3,text="new!",fg_bg=cpair(colors.red,colors._INHERIT)} ---@todo remove NEW tag on next revision
|
||||||
|
TextBox{parent=plc_c_5,x=3,y=4,height=4,text="Digital I/O is already inverted (or not) based on intended use. If you have a non-standard setup, you can use this option to avoid needing a redstone inverter.",fg_bg=cpair(colors.gray,colors.lightGray)}
|
||||||
|
PushButton{parent=plc_c_5,x=1,y=14,text="\x1b Back",callback=function()plc_pane.set_value(4)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
local function submit_emcool()
|
local function submit_emcool()
|
||||||
tmp_cfg.EmerCoolSide = side_options_map[side.get_value()]
|
tmp_cfg.EmerCoolSide = side_options_map[side.get_value()]
|
||||||
tmp_cfg.EmerCoolColor = util.trinary(bundled.get_value(), color_options_map[color.get_value()], nil)
|
tmp_cfg.EmerCoolColor = util.trinary(bundled.get_value(), color_options_map[color.get_value()], nil)
|
||||||
|
tmp_cfg.EmerCoolInvert = invert.get_value()
|
||||||
next_from_plc()
|
next_from_plc()
|
||||||
end
|
end
|
||||||
|
|
||||||
PushButton{parent=plc_c_4,x=1,y=14,text="\x1b Back",callback=function()plc_pane.set_value(3)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=plc_c_4,x=1,y=14,text="\x1b Back",callback=function()plc_pane.set_value(3)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
PushButton{parent=plc_c_4,x=33,y=14,min_width=10,text="Advanced",callback=function()plc_pane.set_value(5)end,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
||||||
PushButton{parent=plc_c_4,x=44,y=14,text="Next \x1a",callback=submit_emcool,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=plc_c_4,x=44,y=14,text="Next \x1a",callback=submit_emcool,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
--#endregion
|
--#endregion
|
||||||
@@ -461,6 +470,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit)
|
|||||||
try_set(side, side_to_idx(ini_cfg.EmerCoolSide))
|
try_set(side, side_to_idx(ini_cfg.EmerCoolSide))
|
||||||
try_set(bundled, ini_cfg.EmerCoolColor ~= nil)
|
try_set(bundled, ini_cfg.EmerCoolColor ~= nil)
|
||||||
if ini_cfg.EmerCoolColor ~= nil then try_set(color, color_to_idx(ini_cfg.EmerCoolColor)) end
|
if ini_cfg.EmerCoolColor ~= nil then try_set(color, color_to_idx(ini_cfg.EmerCoolColor)) end
|
||||||
|
try_set(invert, ini_cfg.EmerCoolInvert)
|
||||||
try_set(svr_chan, ini_cfg.SVR_Channel)
|
try_set(svr_chan, ini_cfg.SVR_Channel)
|
||||||
try_set(plc_chan, ini_cfg.PLC_Channel)
|
try_set(plc_chan, ini_cfg.PLC_Channel)
|
||||||
try_set(timeout, ini_cfg.ConnTimeout)
|
try_set(timeout, ini_cfg.ConnTimeout)
|
||||||
@@ -533,9 +543,11 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit)
|
|||||||
if tmp_cfg.EmerCoolEnable then
|
if tmp_cfg.EmerCoolEnable then
|
||||||
tmp_cfg.EmerCoolSide = config.EMERGENCY_COOL.side
|
tmp_cfg.EmerCoolSide = config.EMERGENCY_COOL.side
|
||||||
tmp_cfg.EmerCoolColor = config.EMERGENCY_COOL.color
|
tmp_cfg.EmerCoolColor = config.EMERGENCY_COOL.color
|
||||||
|
tmp_cfg.EmerCoolInvert = false
|
||||||
else
|
else
|
||||||
tmp_cfg.EmerCoolSide = nil
|
tmp_cfg.EmerCoolSide = nil
|
||||||
tmp_cfg.EmerCoolColor = nil
|
tmp_cfg.EmerCoolColor = nil
|
||||||
|
tmp_cfg.EmerCoolInvert = false
|
||||||
end
|
end
|
||||||
|
|
||||||
tmp_cfg.SVR_Channel = config.SVR_CHANNEL
|
tmp_cfg.SVR_Channel = config.SVR_CHANNEL
|
||||||
@@ -592,7 +604,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit)
|
|||||||
local c = util.trinary(alternate, g_lg_fg_bg, cpair(colors.gray,colors.white))
|
local c = util.trinary(alternate, g_lg_fg_bg, cpair(colors.gray,colors.white))
|
||||||
alternate = not alternate
|
alternate = not alternate
|
||||||
|
|
||||||
if string.len(val) > val_max_w then
|
if (string.len(val) > val_max_w) or string.find(val, "\n") then
|
||||||
local lines = util.strwrap(val, inner_width)
|
local lines = util.strwrap(val, inner_width)
|
||||||
height = #lines + 1
|
height = #lines + 1
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -32,7 +32,8 @@ local changes = {
|
|||||||
{ "v1.6.2", { "AuthKey minimum length is now 8 (if set)" } },
|
{ "v1.6.2", { "AuthKey minimum length is now 8 (if set)" } },
|
||||||
{ "v1.6.8", { "ConnTimeout can now have a fractional part" } },
|
{ "v1.6.8", { "ConnTimeout can now have a fractional part" } },
|
||||||
{ "v1.6.15", { "Added front panel UI theme", "Added color accessibility modes" } },
|
{ "v1.6.15", { "Added front panel UI theme", "Added color accessibility modes" } },
|
||||||
{ "v1.7.3", { "Added standard with black off state color mode", "Added blue indicator color modes" } }
|
{ "v1.7.3", { "Added standard with black off state color mode", "Added blue indicator color modes" } },
|
||||||
|
{ "v1.8.21", { "Added option to invert emergency coolant redstone control" } }
|
||||||
}
|
}
|
||||||
|
|
||||||
---@class plc_configurator
|
---@class plc_configurator
|
||||||
@@ -53,6 +54,7 @@ style.btn_dis_fg_bg = cpair(colors.lightGray, colors.white)
|
|||||||
|
|
||||||
---@class _plc_cfg_tool_ctl
|
---@class _plc_cfg_tool_ctl
|
||||||
local tool_ctl = {
|
local tool_ctl = {
|
||||||
|
launch_startup = false,
|
||||||
ask_config = false,
|
ask_config = false,
|
||||||
has_config = false,
|
has_config = false,
|
||||||
viewing_config = false,
|
viewing_config = false,
|
||||||
@@ -75,6 +77,7 @@ local tmp_cfg = {
|
|||||||
EmerCoolEnable = false,
|
EmerCoolEnable = false,
|
||||||
EmerCoolSide = nil, ---@type string|nil
|
EmerCoolSide = nil, ---@type string|nil
|
||||||
EmerCoolColor = nil, ---@type color|nil
|
EmerCoolColor = nil, ---@type color|nil
|
||||||
|
EmerCoolInvert = false, ---@type boolean
|
||||||
SVR_Channel = nil, ---@type integer
|
SVR_Channel = nil, ---@type integer
|
||||||
PLC_Channel = nil, ---@type integer
|
PLC_Channel = nil, ---@type integer
|
||||||
ConnTimeout = nil, ---@type number
|
ConnTimeout = nil, ---@type number
|
||||||
@@ -99,6 +102,7 @@ local fields = {
|
|||||||
{ "EmerCoolEnable", "Emergency Coolant", false },
|
{ "EmerCoolEnable", "Emergency Coolant", false },
|
||||||
{ "EmerCoolSide", "Emergency Coolant Side", nil },
|
{ "EmerCoolSide", "Emergency Coolant Side", nil },
|
||||||
{ "EmerCoolColor", "Emergency Coolant Color", nil },
|
{ "EmerCoolColor", "Emergency Coolant Color", nil },
|
||||||
|
{ "EmerCoolInvert", "Emergency Coolant Invert", false },
|
||||||
{ "SVR_Channel", "SVR Channel", 16240 },
|
{ "SVR_Channel", "SVR Channel", 16240 },
|
||||||
{ "PLC_Channel", "PLC Channel", 16241 },
|
{ "PLC_Channel", "PLC Channel", 16241 },
|
||||||
{ "ConnTimeout", "Connection Timeout", 5 },
|
{ "ConnTimeout", "Connection Timeout", 5 },
|
||||||
@@ -158,7 +162,7 @@ local function config_view(display)
|
|||||||
TextBox{parent=main_page,x=2,y=2,height=2,text="Welcome to the Reactor PLC configurator! Please select one of the following options."}
|
TextBox{parent=main_page,x=2,y=2,height=2,text="Welcome to the Reactor PLC configurator! Please select one of the following options."}
|
||||||
|
|
||||||
if tool_ctl.ask_config then
|
if tool_ctl.ask_config then
|
||||||
TextBox{parent=main_page,x=2,y=y_start,height=4,width=49,text="Notice: This device had no valid config so the configurator has been automatically started. If you previously had a valid config, you may want to check the Change Log to see what changed.",fg_bg=cpair(colors.red,colors.lightGray)}
|
TextBox{parent=main_page,x=2,y=y_start,height=4,width=49,text="Notice: This device is not configured for this version of the reactor PLC. If you previously had a valid config, it's not lost. You may want to check the Change Log to see what changed.",fg_bg=cpair(colors.red,colors.lightGray)}
|
||||||
y_start = y_start + 5
|
y_start = y_start + 5
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -184,10 +188,18 @@ local function config_view(display)
|
|||||||
main_pane.set_value(5)
|
main_pane.set_value(5)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function startup()
|
||||||
|
tool_ctl.launch_startup = true
|
||||||
|
exit()
|
||||||
|
end
|
||||||
|
|
||||||
PushButton{parent=main_page,x=2,y=17,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=main_page,x=2,y=17,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=btn_act_fg_bg}
|
||||||
PushButton{parent=main_page,x=10,y=17,min_width=12,text="Self-Check",callback=function()main_pane.set_value(8)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
local start_btn = PushButton{parent=main_page,x=42,y=17,min_width=9,text="Startup",callback=startup,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
||||||
tool_ctl.color_cfg = PushButton{parent=main_page,x=23,y=17,min_width=15,text="Color Options",callback=jump_color,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
PushButton{parent=main_page,x=39,y=y_start,min_width=12,text="Self-Check",callback=function()main_pane.set_value(8)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
||||||
PushButton{parent=main_page,x=39,y=17,min_width=12,text="Change Log",callback=function()main_pane.set_value(7)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
tool_ctl.color_cfg = PushButton{parent=main_page,x=36,y=y_start+2,min_width=15,text="Color Options",callback=jump_color,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
||||||
|
PushButton{parent=main_page,x=39,y=y_start+4,min_width=12,text="Change Log",callback=function()main_pane.set_value(7)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
|
if tool_ctl.ask_config then start_btn.disable() end
|
||||||
|
|
||||||
if not tool_ctl.has_config then
|
if not tool_ctl.has_config then
|
||||||
tool_ctl.view_cfg.disable()
|
tool_ctl.view_cfg.disable()
|
||||||
@@ -293,7 +305,7 @@ function configurator.configure(ask_config)
|
|||||||
println("configurator error: " .. error)
|
println("configurator error: " .. error)
|
||||||
end
|
end
|
||||||
|
|
||||||
return status, error
|
return status, error, tool_ctl.launch_startup
|
||||||
end
|
end
|
||||||
|
|
||||||
return configurator
|
return configurator
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ local function init(panel)
|
|||||||
|
|
||||||
local disabled_fg = style.fp.disabled_fg
|
local disabled_fg = style.fp.disabled_fg
|
||||||
|
|
||||||
|
local term_w, term_h = term.getSize()
|
||||||
|
|
||||||
local header = TextBox{parent=panel,y=1,text="FISSION REACTOR PLC - UNIT ?",alignment=ALIGN.CENTER,fg_bg=style.theme.header}
|
local header = TextBox{parent=panel,y=1,text="FISSION REACTOR PLC - UNIT ?",alignment=ALIGN.CENTER,fg_bg=style.theme.header}
|
||||||
header.register(databus.ps, "unit_id", function (id) header.set_value(util.c("FISSION REACTOR PLC - UNIT ", id)) end)
|
header.register(databus.ps, "unit_id", function (id) header.set_value(util.c("FISSION REACTOR PLC - UNIT ", id)) end)
|
||||||
|
|
||||||
@@ -60,7 +62,7 @@ local function init(panel)
|
|||||||
local modem = LED{parent=system,label="MODEM",colors=ind_grn}
|
local modem = LED{parent=system,label="MODEM",colors=ind_grn}
|
||||||
|
|
||||||
if not style.colorblind then
|
if not style.colorblind then
|
||||||
local network = RGBLED{parent=system,label="NETWORK",colors={colors.green,colors.red,colors.orange,colors.yellow,style.ind_bkg}}
|
local network = RGBLED{parent=system,label="NETWORK",colors={colors.green,colors.red,colors.yellow,colors.orange,style.ind_bkg}}
|
||||||
network.update(types.PANEL_LINK_STATE.DISCONNECTED)
|
network.update(types.PANEL_LINK_STATE.DISCONNECTED)
|
||||||
network.register(databus.ps, "link_state", network.update)
|
network.register(databus.ps, "link_state", network.update)
|
||||||
else
|
else
|
||||||
@@ -121,7 +123,7 @@ local function init(panel)
|
|||||||
-- status & controls
|
-- status & controls
|
||||||
--
|
--
|
||||||
|
|
||||||
local status = Div{parent=panel,width=19,height=18,x=17,y=3}
|
local status = Div{parent=panel,width=term_w-32,height=18,x=17,y=3}
|
||||||
|
|
||||||
local active = LED{parent=status,x=2,width=12,label="RCT ACTIVE",colors=ind_grn}
|
local active = LED{parent=status,x=2,width=12,label="RCT ACTIVE",colors=ind_grn}
|
||||||
|
|
||||||
@@ -131,14 +133,15 @@ local function init(panel)
|
|||||||
emer_cool.register(databus.ps, "emer_cool", emer_cool.update)
|
emer_cool.register(databus.ps, "emer_cool", emer_cool.update)
|
||||||
end
|
end
|
||||||
|
|
||||||
local status_trip_rct = Rectangle{parent=status,width=20,height=3,x=1,border=border(1,s_hi_box.bkg,true),even_inner=true}
|
local status_trip_rct = Rectangle{parent=status,height=3,x=1,border=border(1,s_hi_box.bkg,true),even_inner=true}
|
||||||
local status_trip = Div{parent=status_trip_rct,width=18,height=1,fg_bg=s_hi_box}
|
local status_trip = Div{parent=status_trip_rct,height=1,fg_bg=s_hi_box}
|
||||||
local scram = LED{parent=status_trip,width=10,label="RPS TRIP",colors=ind_red,flash=true,period=flasher.PERIOD.BLINK_250_MS}
|
local scram = LED{parent=status_trip,width=10,label="RPS TRIP",colors=ind_red,flash=true,period=flasher.PERIOD.BLINK_250_MS}
|
||||||
|
|
||||||
local controls_rct = Rectangle{parent=status,width=17,height=3,x=1,border=border(1,s_hi_box.bkg,true),even_inner=true}
|
local controls_rct = Rectangle{parent=status,width=status.get_width()-2,height=3,x=1,border=border(1,s_hi_box.bkg,true),even_inner=true}
|
||||||
local controls = Div{parent=controls_rct,width=15,height=1,fg_bg=s_hi_box}
|
local controls = Div{parent=controls_rct,width=controls_rct.get_width()-2,height=1,fg_bg=s_hi_box}
|
||||||
PushButton{parent=controls,x=1,y=1,min_width=7,text="SCRAM",callback=databus.rps_scram,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.black,colors.red_off)}
|
local button_padding = math.floor((controls.get_width() - 14) / 3)
|
||||||
PushButton{parent=controls,x=9,y=1,min_width=7,text="RESET",callback=databus.rps_reset,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.black,colors.yellow_off)}
|
PushButton{parent=controls,x=button_padding+1,y=1,min_width=7,text="SCRAM",callback=databus.rps_scram,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.black,colors.red_off)}
|
||||||
|
PushButton{parent=controls,x=(2*button_padding)+9,y=1,min_width=7,text="RESET",callback=databus.rps_reset,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.black,colors.yellow_off)}
|
||||||
|
|
||||||
active.register(databus.ps, "reactor_active", active.update)
|
active.register(databus.ps, "reactor_active", active.update)
|
||||||
scram.register(databus.ps, "rps_scram", scram.update)
|
scram.register(databus.ps, "rps_scram", scram.update)
|
||||||
@@ -147,9 +150,9 @@ local function init(panel)
|
|||||||
-- about footer
|
-- about footer
|
||||||
--
|
--
|
||||||
|
|
||||||
local about = Div{parent=panel,width=15,height=3,x=1,y=18,fg_bg=disabled_fg}
|
local about = Div{parent=panel,width=15,height=2,y=term_h-1,fg_bg=disabled_fg}
|
||||||
local fw_v = TextBox{parent=about,x=1,y=1,text="FW: v00.00.00"}
|
local fw_v = TextBox{parent=about,text="FW: v00.00.00"}
|
||||||
local comms_v = TextBox{parent=about,x=1,y=2,text="NT: v00.00.00"}
|
local comms_v = TextBox{parent=about,text="NT: v00.00.00"}
|
||||||
|
|
||||||
fw_v.register(databus.ps, "version", function (version) fw_v.set_value(util.c("FW: ", version)) end)
|
fw_v.register(databus.ps, "version", function (version) fw_v.set_value(util.c("FW: ", version)) end)
|
||||||
comms_v.register(databus.ps, "comms_version", function (version) comms_v.set_value(util.c("NT: v", version)) end)
|
comms_v.register(databus.ps, "comms_version", function (version) comms_v.set_value(util.c("NT: v", version)) end)
|
||||||
@@ -158,7 +161,7 @@ local function init(panel)
|
|||||||
-- rps list
|
-- rps list
|
||||||
--
|
--
|
||||||
|
|
||||||
local rps = Rectangle{parent=panel,width=16,height=16,x=36,y=3,border=border(1,s_hi_box.bkg),thin=true,fg_bg=s_hi_box}
|
local rps = Rectangle{parent=panel,width=16,height=16,x=term_w-15,y=3,border=border(1,s_hi_box.bkg),thin=true,fg_bg=s_hi_box}
|
||||||
local rps_man = LED{parent=rps,label="MANUAL",colors=ind_red}
|
local rps_man = LED{parent=rps,label="MANUAL",colors=ind_red}
|
||||||
local rps_auto = LED{parent=rps,label="AUTOMATIC",colors=ind_red}
|
local rps_auto = LED{parent=rps,label="AUTOMATIC",colors=ind_red}
|
||||||
local rps_tmo = LED{parent=rps,label="TIMEOUT",colors=ind_red}
|
local rps_tmo = LED{parent=rps,label="TIMEOUT",colors=ind_red}
|
||||||
|
|||||||
@@ -23,12 +23,12 @@ local AUTO_ACK = comms.PLC_AUTO_ACK
|
|||||||
|
|
||||||
local RPS_LIMITS = const.RPS_LIMITS
|
local RPS_LIMITS = const.RPS_LIMITS
|
||||||
|
|
||||||
-- I sure hope the devs don't change this error message, not that it would have safety implications
|
-- specific errors thrown when scram/start is used that still count as success
|
||||||
-- I wish they didn't change it to be like this
|
|
||||||
local PCALL_SCRAM_MSG = "Scram requires the reactor to be active."
|
local PCALL_SCRAM_MSG = "Scram requires the reactor to be active."
|
||||||
local PCALL_START_MSG = "Reactor is already active."
|
local PCALL_START_MSG = "Reactor is already active."
|
||||||
|
|
||||||
---@type plc_config
|
---@type plc_config
|
||||||
|
---@diagnostic disable-next-line: missing-fields
|
||||||
local config = {}
|
local config = {}
|
||||||
|
|
||||||
plc.config = config
|
plc.config = config
|
||||||
@@ -43,6 +43,7 @@ function plc.load_config()
|
|||||||
config.EmerCoolEnable = settings.get("EmerCoolEnable")
|
config.EmerCoolEnable = settings.get("EmerCoolEnable")
|
||||||
config.EmerCoolSide = settings.get("EmerCoolSide")
|
config.EmerCoolSide = settings.get("EmerCoolSide")
|
||||||
config.EmerCoolColor = settings.get("EmerCoolColor")
|
config.EmerCoolColor = settings.get("EmerCoolColor")
|
||||||
|
config.EmerCoolInvert = settings.get("EmerCoolInvert")
|
||||||
|
|
||||||
config.SVR_Channel = settings.get("SVR_Channel")
|
config.SVR_Channel = settings.get("SVR_Channel")
|
||||||
config.PLC_Channel = settings.get("PLC_Channel")
|
config.PLC_Channel = settings.get("PLC_Channel")
|
||||||
@@ -98,6 +99,7 @@ function plc.validate_config(cfg)
|
|||||||
if cfg.EmerCoolEnable then
|
if cfg.EmerCoolEnable then
|
||||||
cfv.assert_eq(rsio.is_valid_side(cfg.EmerCoolSide), true)
|
cfv.assert_eq(rsio.is_valid_side(cfg.EmerCoolSide), true)
|
||||||
cfv.assert_eq(cfg.EmerCoolColor == nil or rsio.is_color(cfg.EmerCoolColor), true)
|
cfv.assert_eq(cfg.EmerCoolColor == nil or rsio.is_color(cfg.EmerCoolColor), true)
|
||||||
|
cfv.assert_type_bool(cfg.EmerCoolInvert)
|
||||||
end
|
end
|
||||||
|
|
||||||
return cfv.valid()
|
return cfv.valid()
|
||||||
@@ -166,7 +168,8 @@ function plc.rps_init(reactor, is_formed)
|
|||||||
local function _set_emer_cool(state)
|
local function _set_emer_cool(state)
|
||||||
-- check if this was configured: if it's a table, fields have already been validated.
|
-- check if this was configured: if it's a table, fields have already been validated.
|
||||||
if config.EmerCoolEnable then
|
if config.EmerCoolEnable then
|
||||||
local level = rsio.digital_write_active(rsio.IO.U_EMER_COOL, state)
|
-- use ~= as XOR for simple inversion
|
||||||
|
local level = rsio.digital_write_active(rsio.IO.U_EMER_COOL, config.EmerCoolInvert ~= state)
|
||||||
|
|
||||||
if level ~= false then
|
if level ~= false then
|
||||||
if rsio.is_color(config.EmerCoolColor) then
|
if rsio.is_color(config.EmerCoolColor) then
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ local plc = require("reactor-plc.plc")
|
|||||||
local renderer = require("reactor-plc.renderer")
|
local renderer = require("reactor-plc.renderer")
|
||||||
local threads = require("reactor-plc.threads")
|
local threads = require("reactor-plc.threads")
|
||||||
|
|
||||||
local R_PLC_VERSION = "v1.8.12"
|
local R_PLC_VERSION = "v1.8.22"
|
||||||
|
|
||||||
local println = util.println
|
local println = util.println
|
||||||
local println_ts = util.println_ts
|
local println_ts = util.println_ts
|
||||||
@@ -169,12 +169,12 @@ local function main()
|
|||||||
-- PLC init<br>
|
-- PLC init<br>
|
||||||
--- EVENT_CONSUMER: this function consumes events
|
--- EVENT_CONSUMER: this function consumes events
|
||||||
local function init()
|
local function init()
|
||||||
-- just booting up, no fission allowed (neutrons stay put thanks)
|
-- scram on boot if networked, otherwise leave the reactor be
|
||||||
if (not plc_state.no_reactor) and plc_state.reactor_formed and smem_dev.reactor.getStatus() then
|
if __shared_memory.networked and (not plc_state.no_reactor) and plc_state.reactor_formed and smem_dev.reactor.getStatus() then
|
||||||
smem_dev.reactor.scram()
|
smem_dev.reactor.scram()
|
||||||
end
|
end
|
||||||
|
|
||||||
-- front panel time!
|
-- setup front panel
|
||||||
if not renderer.ui_ready() then
|
if not renderer.ui_ready() then
|
||||||
local message
|
local message
|
||||||
plc_state.fp_ok, message = renderer.try_start_ui(config.FrontPanelTheme, config.ColorMode)
|
plc_state.fp_ok, message = renderer.try_start_ui(config.FrontPanelTheme, config.ColorMode)
|
||||||
|
|||||||
318
rtu/config/check.lua
Normal file
318
rtu/config/check.lua
Normal file
@@ -0,0 +1,318 @@
|
|||||||
|
local comms = require("scada-common.comms")
|
||||||
|
local network = require("scada-common.network")
|
||||||
|
local ppm = require("scada-common.ppm")
|
||||||
|
local rsio = require("scada-common.rsio")
|
||||||
|
local tcd = require("scada-common.tcd")
|
||||||
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
|
local rtu = require("rtu.rtu")
|
||||||
|
|
||||||
|
local redstone = require("rtu.config.redstone")
|
||||||
|
|
||||||
|
local core = require("graphics.core")
|
||||||
|
|
||||||
|
local Div = require("graphics.elements.Div")
|
||||||
|
local ListBox = require("graphics.elements.ListBox")
|
||||||
|
local TextBox = require("graphics.elements.TextBox")
|
||||||
|
|
||||||
|
local PushButton = require("graphics.elements.controls.PushButton")
|
||||||
|
|
||||||
|
local tri = util.trinary
|
||||||
|
|
||||||
|
local cpair = core.cpair
|
||||||
|
|
||||||
|
local PROTOCOL = comms.PROTOCOL
|
||||||
|
local DEVICE_TYPE = comms.DEVICE_TYPE
|
||||||
|
local ESTABLISH_ACK = comms.ESTABLISH_ACK
|
||||||
|
local MGMT_TYPE = comms.MGMT_TYPE
|
||||||
|
|
||||||
|
local self = {
|
||||||
|
nic = nil, ---@type nic
|
||||||
|
net_listen = false,
|
||||||
|
sv_addr = comms.BROADCAST,
|
||||||
|
sv_seq_num = util.time_ms() * 10,
|
||||||
|
|
||||||
|
self_check_pass = true,
|
||||||
|
|
||||||
|
settings = nil, ---@type rtu_config
|
||||||
|
|
||||||
|
run_test_btn = nil, ---@type PushButton
|
||||||
|
sc_log = nil, ---@type ListBox
|
||||||
|
self_check_msg = nil ---@type function
|
||||||
|
}
|
||||||
|
|
||||||
|
-- report successful completion of the check
|
||||||
|
local function check_complete()
|
||||||
|
TextBox{parent=self.sc_log,text="> all tests passed!",fg_bg=cpair(colors.blue,colors._INHERIT)}
|
||||||
|
TextBox{parent=self.sc_log,text=""}
|
||||||
|
local more = Div{parent=self.sc_log,height=3,fg_bg=cpair(colors.gray,colors._INHERIT)}
|
||||||
|
TextBox{parent=more,text="if you still have a problem:"}
|
||||||
|
TextBox{parent=more,text="- check the wiki on GitHub"}
|
||||||
|
TextBox{parent=more,text="- ask for help on GitHub discussions or Discord"}
|
||||||
|
end
|
||||||
|
|
||||||
|
-- send a management packet to the supervisor
|
||||||
|
---@param msg_type MGMT_TYPE
|
||||||
|
---@param msg table
|
||||||
|
local function send_sv(msg_type, msg)
|
||||||
|
local s_pkt = comms.scada_packet()
|
||||||
|
local pkt = comms.mgmt_packet()
|
||||||
|
|
||||||
|
pkt.make(msg_type, msg)
|
||||||
|
s_pkt.make(self.sv_addr, self.sv_seq_num, PROTOCOL.SCADA_MGMT, pkt.raw_sendable())
|
||||||
|
|
||||||
|
self.nic.transmit(self.settings.SVR_Channel, self.settings.RTU_Channel, s_pkt)
|
||||||
|
self.sv_seq_num = self.sv_seq_num + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
-- handle an establish message from the supervisor
|
||||||
|
---@param packet mgmt_frame
|
||||||
|
local function handle_packet(packet)
|
||||||
|
local error_msg = nil
|
||||||
|
|
||||||
|
if packet.scada_frame.local_channel() ~= self.settings.RTU_Channel then
|
||||||
|
error_msg = "error: unknown receive channel"
|
||||||
|
elseif packet.scada_frame.remote_channel() == self.settings.SVR_Channel and packet.scada_frame.protocol() == PROTOCOL.SCADA_MGMT then
|
||||||
|
if packet.type == MGMT_TYPE.ESTABLISH then
|
||||||
|
if packet.length == 1 then
|
||||||
|
local est_ack = packet.data[1]
|
||||||
|
|
||||||
|
if est_ack== ESTABLISH_ACK.ALLOW then
|
||||||
|
self.self_check_msg(nil, true, "")
|
||||||
|
self.sv_addr = packet.scada_frame.src_addr()
|
||||||
|
send_sv(MGMT_TYPE.CLOSE, {})
|
||||||
|
if self.self_check_pass then check_complete() end
|
||||||
|
elseif est_ack == ESTABLISH_ACK.DENY then
|
||||||
|
error_msg = "error: supervisor connection denied"
|
||||||
|
elseif est_ack == ESTABLISH_ACK.BAD_VERSION then
|
||||||
|
error_msg = "RTU gateway comms version does not match supervisor comms version, make sure both devices are up-to-date (ccmsi update)"
|
||||||
|
else
|
||||||
|
error_msg = "error: invalid reply from supervisor"
|
||||||
|
end
|
||||||
|
else
|
||||||
|
error_msg = "error: invalid reply length from supervisor"
|
||||||
|
end
|
||||||
|
else
|
||||||
|
error_msg = "error: didn't get an establish reply from supervisor"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
self.net_listen = false
|
||||||
|
self.run_test_btn.enable()
|
||||||
|
|
||||||
|
if error_msg then
|
||||||
|
self.self_check_msg(nil, false, error_msg)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- handle supervisor connection failure
|
||||||
|
local function handle_timeout()
|
||||||
|
self.net_listen = false
|
||||||
|
self.run_test_btn.enable()
|
||||||
|
self.self_check_msg(nil, false, "make sure your supervisor is running, your channels are correct, trusted ranges are set properly (if enabled), facility keys match (if set), and if you are using wireless modems rather than ender modems, that your devices are close together in the same dimension")
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- check if a value is an integer within a range (inclusive)
|
||||||
|
---@param x any
|
||||||
|
---@param min integer
|
||||||
|
---@param max integer
|
||||||
|
local function is_int_min_max(x, min, max) return util.is_int(x) and x >= min and x <= max end
|
||||||
|
|
||||||
|
-- execute the self-check
|
||||||
|
local function self_check()
|
||||||
|
self.run_test_btn.disable()
|
||||||
|
|
||||||
|
self.sc_log.remove_all()
|
||||||
|
ppm.mount_all()
|
||||||
|
|
||||||
|
self.self_check_pass = true
|
||||||
|
|
||||||
|
local cfg = self.settings
|
||||||
|
local modem = ppm.get_wireless_modem()
|
||||||
|
local valid_cfg = rtu.validate_config(cfg)
|
||||||
|
|
||||||
|
self.self_check_msg("> check wireless/ender modem connected...", modem ~= nil, "you must connect an ender or wireless modem to the RTU gateway")
|
||||||
|
self.self_check_msg("> check gateway configuration...", valid_cfg, "go through Configure Gateway and apply settings to set any missing settings and repair any corrupted ones")
|
||||||
|
|
||||||
|
-- check redstone configurations
|
||||||
|
|
||||||
|
local phys = {} ---@type rtu_rs_definition[][]
|
||||||
|
local inputs = { [0] = {}, {}, {}, {}, {} }
|
||||||
|
|
||||||
|
for i = 1, #cfg.Redstone do
|
||||||
|
local entry = cfg.Redstone[i]
|
||||||
|
local name = entry.relay or "local"
|
||||||
|
|
||||||
|
if phys[name] == nil then phys[name] = {} end
|
||||||
|
table.insert(phys[entry.relay or "local"], entry)
|
||||||
|
end
|
||||||
|
|
||||||
|
for name, entries in pairs(phys) do
|
||||||
|
TextBox{parent=self.sc_log,text="> checking redstone @ "..name.."...",fg_bg=cpair(colors.blue,colors.white)}
|
||||||
|
|
||||||
|
local ifaces = {}
|
||||||
|
local bundled_sides = {}
|
||||||
|
|
||||||
|
for i = 1, #entries do
|
||||||
|
local entry = entries[i]
|
||||||
|
local ident = entry.side .. tri(entry.color, ":" .. rsio.color_name(entry.color), "")
|
||||||
|
|
||||||
|
local sc_dupe = util.table_contains(ifaces, ident)
|
||||||
|
local mixed = (bundled_sides[entry.side] and (entry.color == nil)) or (bundled_sides[entry.side] == false and (entry.color ~= nil))
|
||||||
|
|
||||||
|
local mixed_msg = util.trinary(bundled_sides[entry.side], "bundled entry(s) but this entry is not", "non-bundled entry(s) but this entry is")
|
||||||
|
|
||||||
|
self.self_check_msg("> check redstone " .. ident .. " unique...", not sc_dupe, "only one port should be set to a side/color combination")
|
||||||
|
self.self_check_msg("> check redstone " .. ident .. " bundle...", not mixed, "this side has " .. mixed_msg .. " bundled, which will not work")
|
||||||
|
self.self_check_msg("> check redstone " .. ident .. " valid...", redstone.validate(entry), "configuration invalid, please re-configure redstone entry")
|
||||||
|
|
||||||
|
if rsio.get_io_dir(entry.port) == rsio.IO_DIR.IN then
|
||||||
|
local in_dupe = util.table_contains(inputs[entry.unit or 0], entry.port)
|
||||||
|
self.self_check_msg("> check redstone " .. ident .. " input...", not in_dupe, "you cannot have multiple of the same input for a given unit or the facility ("..rsio.to_string(entry.port)..")")
|
||||||
|
end
|
||||||
|
|
||||||
|
bundled_sides[entry.side] = bundled_sides[entry.side] or entry.color ~= nil
|
||||||
|
table.insert(ifaces, ident)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- check peripheral configurations
|
||||||
|
for i = 1, #cfg.Peripherals do
|
||||||
|
local entry = cfg.Peripherals[i]
|
||||||
|
local valid = false
|
||||||
|
|
||||||
|
if type(entry.name) == "string" then
|
||||||
|
self.self_check_msg("> check " .. entry.name .. " connected...", ppm.get_periph(entry.name), "please connect this device via a wired modem or direct contact and ensure the configuration matches what it connects as")
|
||||||
|
|
||||||
|
local p_type = ppm.get_type(entry.name)
|
||||||
|
|
||||||
|
if p_type == "boilerValve" then
|
||||||
|
valid = is_int_min_max(entry.index, 1, 2) and is_int_min_max(entry.unit, 1, 4)
|
||||||
|
elseif p_type == "turbineValve" then
|
||||||
|
valid = is_int_min_max(entry.index, 1, 3) and is_int_min_max(entry.unit, 1, 4)
|
||||||
|
elseif p_type == "solarNeutronActivator" then
|
||||||
|
valid = is_int_min_max(entry.unit, 1, 4)
|
||||||
|
elseif p_type == "dynamicValve" then
|
||||||
|
valid = (entry.unit == nil and is_int_min_max(entry.index, 1, 4)) or is_int_min_max(entry.unit, 1, 4)
|
||||||
|
elseif p_type == "environmentDetector" or p_type == "environment_detector" then
|
||||||
|
valid = (entry.unit == nil or is_int_min_max(entry.unit, 1, 4)) and util.is_int(entry.index)
|
||||||
|
else
|
||||||
|
valid = true
|
||||||
|
|
||||||
|
if p_type ~= nil and not (p_type == "inductionPort" or p_type == "spsPort") then
|
||||||
|
self.self_check_msg("> check " .. entry.name .. " valid...", false, "unrecognized device type")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not valid then
|
||||||
|
self.self_check_msg("> check " .. entry.name .. " valid...", false, "configuration invalid, please re-configure peripheral entry")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if valid_cfg and modem then
|
||||||
|
self.self_check_msg("> check supervisor connection...")
|
||||||
|
|
||||||
|
-- init mac as needed
|
||||||
|
if cfg.AuthKey and string.len(cfg.AuthKey) >= 8 then
|
||||||
|
network.init_mac(cfg.AuthKey)
|
||||||
|
else
|
||||||
|
network.deinit_mac()
|
||||||
|
end
|
||||||
|
|
||||||
|
self.nic = network.nic(modem)
|
||||||
|
|
||||||
|
self.nic.closeAll()
|
||||||
|
self.nic.open(cfg.RTU_Channel)
|
||||||
|
|
||||||
|
self.sv_addr = comms.BROADCAST
|
||||||
|
self.net_listen = true
|
||||||
|
|
||||||
|
send_sv(MGMT_TYPE.ESTABLISH, { comms.version, "0.0.0", DEVICE_TYPE.RTU, {} })
|
||||||
|
|
||||||
|
tcd.dispatch_unique(8, handle_timeout)
|
||||||
|
else
|
||||||
|
if self.self_check_pass then check_complete() end
|
||||||
|
self.run_test_btn.enable()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- exit self check back home
|
||||||
|
---@param main_pane MultiPane
|
||||||
|
local function exit_self_check(main_pane)
|
||||||
|
tcd.abort(handle_timeout)
|
||||||
|
self.net_listen = false
|
||||||
|
self.run_test_btn.enable()
|
||||||
|
self.sc_log.remove_all()
|
||||||
|
main_pane.set_value(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
local check = {}
|
||||||
|
|
||||||
|
-- create the self-check view
|
||||||
|
---@param main_pane MultiPane
|
||||||
|
---@param settings_cfg rtu_config
|
||||||
|
---@param check_sys Div
|
||||||
|
---@param style { [string]: cpair }
|
||||||
|
function check.create(main_pane, settings_cfg, check_sys, style)
|
||||||
|
local bw_fg_bg = style.bw_fg_bg
|
||||||
|
local g_lg_fg_bg = style.g_lg_fg_bg
|
||||||
|
local nav_fg_bg = style.nav_fg_bg
|
||||||
|
local btn_act_fg_bg = style.btn_act_fg_bg
|
||||||
|
local btn_dis_fg_bg = style.btn_dis_fg_bg
|
||||||
|
|
||||||
|
self.settings = settings_cfg
|
||||||
|
|
||||||
|
local sc = Div{parent=check_sys,x=2,y=4,width=49}
|
||||||
|
|
||||||
|
TextBox{parent=check_sys,x=1,y=2,text=" RTU Gateway Self-Check",fg_bg=bw_fg_bg}
|
||||||
|
|
||||||
|
self.sc_log = ListBox{parent=sc,x=1,y=1,height=12,width=49,scroll_height=1000,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
|
||||||
|
|
||||||
|
local last_check = { nil, nil }
|
||||||
|
|
||||||
|
function self.self_check_msg(msg, success, fail_msg)
|
||||||
|
if type(msg) == "string" then
|
||||||
|
last_check[1] = Div{parent=self.sc_log,height=1}
|
||||||
|
local e = TextBox{parent=last_check[1],text=msg,fg_bg=bw_fg_bg}
|
||||||
|
last_check[2] = e.get_x()+string.len(msg)
|
||||||
|
end
|
||||||
|
|
||||||
|
if type(fail_msg) == "string" then
|
||||||
|
TextBox{parent=last_check[1],x=last_check[2],y=1,text=tri(success,"PASS","FAIL"),fg_bg=tri(success,cpair(colors.green,colors._INHERIT),cpair(colors.red,colors._INHERIT))}
|
||||||
|
|
||||||
|
if not success then
|
||||||
|
local fail = Div{parent=self.sc_log,height=#util.strwrap(fail_msg, 46)}
|
||||||
|
TextBox{parent=fail,x=3,text=fail_msg,fg_bg=cpair(colors.gray,colors.white)}
|
||||||
|
end
|
||||||
|
|
||||||
|
self.self_check_pass = self.self_check_pass and success
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
PushButton{parent=sc,x=1,y=14,text="\x1b Back",callback=function()exit_self_check(main_pane)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
self.run_test_btn = PushButton{parent=sc,x=40,y=14,min_width=10,text="Run Test",callback=function()self_check()end,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
||||||
|
end
|
||||||
|
|
||||||
|
-- handle incoming modem messages
|
||||||
|
---@param side string
|
||||||
|
---@param sender integer
|
||||||
|
---@param reply_to integer
|
||||||
|
---@param message any
|
||||||
|
---@param distance integer
|
||||||
|
function check.receive_sv(side, sender, reply_to, message, distance)
|
||||||
|
if self.nic ~= nil and self.net_listen then
|
||||||
|
local s_pkt = self.nic.receive(side, sender, reply_to, message, distance)
|
||||||
|
|
||||||
|
if s_pkt and s_pkt.protocol() == PROTOCOL.SCADA_MGMT then
|
||||||
|
local mgmt_pkt = comms.mgmt_packet()
|
||||||
|
if mgmt_pkt.decode(s_pkt) then
|
||||||
|
tcd.abort(handle_timeout)
|
||||||
|
handle_packet(mgmt_pkt.get())
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return check
|
||||||
@@ -43,8 +43,8 @@ local self = {
|
|||||||
|
|
||||||
local peripherals = {}
|
local peripherals = {}
|
||||||
|
|
||||||
local RTU_DEV_TYPES = { "boilerValve", "turbineValve", "dynamicValve", "inductionPort", "spsPort", "solarNeutronActivator", "environmentDetector" }
|
local RTU_DEV_TYPES = { "boilerValve", "turbineValve", "dynamicValve", "inductionPort", "spsPort", "solarNeutronActivator", "environmentDetector", "environment_detector" }
|
||||||
local NEEDS_UNIT = { "boilerValve", "turbineValve", "dynamicValve", "solarNeutronActivator", "environmentDetector" }
|
local NEEDS_UNIT = { "boilerValve", "turbineValve", "dynamicValve", "solarNeutronActivator", "environmentDetector", "environment_detector" }
|
||||||
|
|
||||||
-- create the peripherals configuration view
|
-- create the peripherals configuration view
|
||||||
---@param tool_ctl _rtu_cfg_tool_ctl
|
---@param tool_ctl _rtu_cfg_tool_ctl
|
||||||
@@ -90,6 +90,10 @@ function peripherals.create(tool_ctl, main_pane, cfg_sys, peri_cfg, style)
|
|||||||
load_settings(settings_cfg, true)
|
load_settings(settings_cfg, true)
|
||||||
load_settings(ini_cfg)
|
load_settings(ini_cfg)
|
||||||
peri_pane.set_value(5)
|
peri_pane.set_value(5)
|
||||||
|
|
||||||
|
-- for return to list from saved screen
|
||||||
|
tmp_cfg.Peripherals = tool_ctl.deep_copy_peri(ini_cfg.Peripherals)
|
||||||
|
tool_ctl.gen_peri_summary()
|
||||||
else
|
else
|
||||||
peri_pane.set_value(6)
|
peri_pane.set_value(6)
|
||||||
end
|
end
|
||||||
@@ -145,7 +149,7 @@ function peripherals.create(tool_ctl, main_pane, cfg_sys, peri_cfg, style)
|
|||||||
reposition("This SNA is for reactor unit # .", 46, 1, 31, 4, 7)
|
reposition("This SNA is for reactor unit # .", 46, 1, 31, 4, 7)
|
||||||
self.p_idx.hide()
|
self.p_idx.hide()
|
||||||
self.p_assign_btn.hide(true)
|
self.p_assign_btn.hide(true)
|
||||||
self.p_desc_ext.set_value("Before adding lots of SNAs: multiply the \"PEAK\" rate on the flow monitor (after connecting at least 1 SNA) by 10 to get the mB/t of waste that they can process. Enough SNAs to provide 2x to 3x of your max burn rate should be a good margin to catch up after night or cloudy weather. Too many devices (such as SNAs) on one RTU can cause lag.")
|
self.p_desc_ext.set_value("Warning: too many devices on one RTU Gateway can cause lag. Note that 10x the \"PEAK\x1a\" rate on the flow monitor gives you the mB/t of waste that the SNA(s) can process. Enough SNAs to provide 2x to 3x of that unit's max burn rate should be a good margin to catch up after night or cloudy weather.")
|
||||||
elseif type == "dynamicValve" then
|
elseif type == "dynamicValve" then
|
||||||
reposition("This is the below system's # dynamic tank.", 29, 4, 17, 6, 8)
|
reposition("This is the below system's # dynamic tank.", 29, 4, 17, 6, 8)
|
||||||
self.p_assign_btn.show()
|
self.p_assign_btn.show()
|
||||||
@@ -161,7 +165,7 @@ function peripherals.create(tool_ctl, main_pane, cfg_sys, peri_cfg, style)
|
|||||||
end
|
end
|
||||||
|
|
||||||
self.p_desc.set_value("Each reactor unit can have at most 1 tank and the facility can have at most 4. Each facility tank must have a unique # 1 through 4, regardless of where it is connected. Only a total of 4 tanks can be displayed on the flow monitor.")
|
self.p_desc.set_value("Each reactor unit can have at most 1 tank and the facility can have at most 4. Each facility tank must have a unique # 1 through 4, regardless of where it is connected. Only a total of 4 tanks can be displayed on the flow monitor.")
|
||||||
elseif type == "environmentDetector" then
|
elseif type == "environmentDetector" or type == "environment_detector" then
|
||||||
reposition("This is the below system's # env. detector.", 29, 99, 17, 6, 8)
|
reposition("This is the below system's # env. detector.", 29, 99, 17, 6, 8)
|
||||||
self.p_assign_btn.show()
|
self.p_assign_btn.show()
|
||||||
self.p_assign_btn.redraw()
|
self.p_assign_btn.redraw()
|
||||||
@@ -277,7 +281,7 @@ function peripherals.create(tool_ctl, main_pane, cfg_sys, peri_cfg, style)
|
|||||||
local idx = tonumber(self.p_idx.get_value())
|
local idx = tonumber(self.p_idx.get_value())
|
||||||
|
|
||||||
if util.table_contains(NEEDS_UNIT, peri_type) then
|
if util.table_contains(NEEDS_UNIT, peri_type) then
|
||||||
if (peri_type == "dynamicValve" or peri_type == "environmentDetector") and for_facility then
|
if (peri_type == "dynamicValve" or peri_type == "environmentDetector" or peri_type == "environment_detector") and for_facility then
|
||||||
-- skip
|
-- skip
|
||||||
elseif not (util.is_int(u) and u > 0 and u < 5) then
|
elseif not (util.is_int(u) and u > 0 and u < 5) then
|
||||||
self.p_err.set_value("Unit ID must be within 1 to 4.")
|
self.p_err.set_value("Unit ID must be within 1 to 4.")
|
||||||
@@ -306,7 +310,7 @@ function peripherals.create(tool_ctl, main_pane, cfg_sys, peri_cfg, style)
|
|||||||
else index = idx end
|
else index = idx end
|
||||||
elseif peri_type == "dynamicValve" then
|
elseif peri_type == "dynamicValve" then
|
||||||
index = 1
|
index = 1
|
||||||
elseif peri_type == "environmentDetector" then
|
elseif peri_type == "environmentDetector" or peri_type == "environment_detector" then
|
||||||
if not (util.is_int(idx) and idx > 0) then
|
if not (util.is_int(idx) and idx > 0) then
|
||||||
self.p_err.set_value("Index must be greater than 0.")
|
self.p_err.set_value("Index must be greater than 0.")
|
||||||
self.p_err.show()
|
self.p_err.show()
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
local constants = require("scada-common.constants")
|
local constants = require("scada-common.constants")
|
||||||
|
local ppm = require("scada-common.ppm")
|
||||||
local rsio = require("scada-common.rsio")
|
local rsio = require("scada-common.rsio")
|
||||||
local util = require("scada-common.util")
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
@@ -18,8 +19,10 @@ local NumberField = require("graphics.elements.form.NumberField")
|
|||||||
---@class rtu_rs_definition
|
---@class rtu_rs_definition
|
||||||
---@field unit integer|nil
|
---@field unit integer|nil
|
||||||
---@field port IO_PORT
|
---@field port IO_PORT
|
||||||
|
---@field relay string|nil
|
||||||
---@field side side
|
---@field side side
|
||||||
---@field color color|nil
|
---@field color color|nil
|
||||||
|
---@field invert true|nil
|
||||||
|
|
||||||
local tri = util.trinary
|
local tri = util.trinary
|
||||||
|
|
||||||
@@ -32,15 +35,19 @@ local IO_MODE = rsio.IO_MODE
|
|||||||
local LEFT = core.ALIGN.LEFT
|
local LEFT = core.ALIGN.LEFT
|
||||||
|
|
||||||
local self = {
|
local self = {
|
||||||
rs_cfg_port = 1, ---@type IO_PORT
|
rs_cfg_phy = false, ---@type string|nil|false
|
||||||
rs_cfg_editing = false, ---@type integer|false
|
rs_cfg_port = 1, ---@type IO_PORT
|
||||||
|
rs_cfg_editing = false, ---@type integer|false
|
||||||
|
|
||||||
rs_cfg_selection = nil, ---@type TextBox
|
rs_cfg_selection = nil, ---@type TextBox
|
||||||
rs_cfg_unit_l = nil, ---@type TextBox
|
rs_cfg_unit_l = nil, ---@type TextBox
|
||||||
rs_cfg_unit = nil, ---@type NumberField
|
rs_cfg_unit = nil, ---@type NumberField
|
||||||
rs_cfg_side_l = nil, ---@type TextBox
|
rs_cfg_side_l = nil, ---@type TextBox
|
||||||
rs_cfg_color = nil, ---@type Radio2D
|
rs_cfg_bundled = nil, ---@type Checkbox
|
||||||
rs_cfg_shortcut = nil ---@type TextBox
|
rs_cfg_color = nil, ---@type Radio2D
|
||||||
|
rs_cfg_inverted = nil, ---@type Checkbox
|
||||||
|
rs_cfg_shortcut = nil, ---@type TextBox
|
||||||
|
rs_cfg_advanced = nil ---@type PushButton
|
||||||
}
|
}
|
||||||
|
|
||||||
-- rsio port descriptions
|
-- rsio port descriptions
|
||||||
@@ -73,11 +80,12 @@ local PORT_DESC_MAP = {
|
|||||||
{ IO.R_PLC_FAULT, "RPS PLC Fault" },
|
{ IO.R_PLC_FAULT, "RPS PLC Fault" },
|
||||||
{ IO.R_PLC_TIMEOUT, "RPS Supervisor Timeout" },
|
{ IO.R_PLC_TIMEOUT, "RPS Supervisor Timeout" },
|
||||||
{ IO.U_ALARM, "Unit Alarm" },
|
{ IO.U_ALARM, "Unit Alarm" },
|
||||||
{ IO.U_EMER_COOL, "Unit Emergency Cool. Valve" }
|
{ IO.U_EMER_COOL, "Unit Emergency Cool. Valve" },
|
||||||
|
{ IO.U_AUX_COOL, "Unit Auxiliary Cool. Valve" }
|
||||||
}
|
}
|
||||||
|
|
||||||
-- designation (0 = facility, 1 = unit)
|
-- designation (0 = facility, 1 = unit)
|
||||||
local PORT_DSGN = { [-1] = 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0 }
|
local PORT_DSGN = { [-1] = 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1 }
|
||||||
|
|
||||||
assert(#PORT_DESC_MAP == rsio.NUM_PORTS)
|
assert(#PORT_DESC_MAP == rsio.NUM_PORTS)
|
||||||
assert(#PORT_DSGN == rsio.NUM_PORTS)
|
assert(#PORT_DSGN == rsio.NUM_PORTS)
|
||||||
@@ -103,8 +111,34 @@ local function color_to_idx(color)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- select the subset of redstone entries assigned to the given phy
|
||||||
|
---@param cfg rtu_rs_definition[] the full redstone entry list
|
||||||
|
---@param phy string|nil which phy to get redstone entries for
|
||||||
|
---@param invert boolean? true to get all except this phy
|
||||||
|
---@return rtu_rs_definition[]
|
||||||
|
local function redstone_subset(cfg, phy, invert)
|
||||||
|
local subset = {}
|
||||||
|
|
||||||
|
for i = 1, #cfg do
|
||||||
|
if ((not invert) and cfg[i].relay == phy) or (invert and cfg[i].relay ~= phy) then
|
||||||
|
table.insert(subset, cfg[i])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return subset
|
||||||
|
end
|
||||||
|
|
||||||
local redstone = {}
|
local redstone = {}
|
||||||
|
|
||||||
|
-- validate a redstone entry
|
||||||
|
---@param def rtu_rs_definition
|
||||||
|
function redstone.validate(def)
|
||||||
|
return tri(PORT_DSGN[def.port] == 1, util.is_int(def.unit) and def.unit > 0 and def.unit <= 4, def.unit == nil) and
|
||||||
|
rsio.is_valid_port(def.port) and
|
||||||
|
rsio.is_valid_side(def.side) and
|
||||||
|
(def.color == nil or (rsio.is_digital(def.port) and rsio.is_color(def.color)))
|
||||||
|
end
|
||||||
|
|
||||||
-- create the redstone configuration view
|
-- create the redstone configuration view
|
||||||
---@param tool_ctl _rtu_cfg_tool_ctl
|
---@param tool_ctl _rtu_cfg_tool_ctl
|
||||||
---@param main_pane MultiPane
|
---@param main_pane MultiPane
|
||||||
@@ -123,20 +157,89 @@ function redstone.create(tool_ctl, main_pane, cfg_sys, rs_cfg, style)
|
|||||||
|
|
||||||
--#region Redstone
|
--#region Redstone
|
||||||
|
|
||||||
local rs_c_1 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
local rs_c_1 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
||||||
local rs_c_2 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
local rs_c_2 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
||||||
local rs_c_3 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
local rs_c_3 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
||||||
local rs_c_4 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
local rs_c_4 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
||||||
local rs_c_5 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
local rs_c_5 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
||||||
local rs_c_6 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
local rs_c_6 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
||||||
local rs_c_7 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
local rs_c_7 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
||||||
|
local rs_c_8 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
||||||
|
local rs_c_9 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
||||||
|
local rs_c_10 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
||||||
|
|
||||||
local rs_pane = MultiPane{parent=rs_cfg,x=1,y=4,panes={rs_c_1,rs_c_2,rs_c_3,rs_c_4,rs_c_5,rs_c_6,rs_c_7}}
|
local rs_pane = MultiPane{parent=rs_cfg,x=1,y=4,panes={rs_c_1,rs_c_2,rs_c_3,rs_c_4,rs_c_5,rs_c_6,rs_c_7,rs_c_8,rs_c_9,rs_c_10}}
|
||||||
|
|
||||||
TextBox{parent=rs_cfg,x=1,y=2,text=" Redstone Connections",fg_bg=cpair(colors.black,colors.red)}
|
local header = TextBox{parent=rs_cfg,x=1,y=2,text=" Redstone Connections",fg_bg=cpair(colors.black,colors.red)}
|
||||||
|
|
||||||
TextBox{parent=rs_c_1,x=1,y=1,text=" port side/color unit/facility",fg_bg=g_lg_fg_bg}
|
--#region Interface Selection
|
||||||
local rs_list = ListBox{parent=rs_c_1,x=1,y=2,height=11,width=49,scroll_height=200,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
|
|
||||||
|
TextBox{parent=rs_c_1,x=1,y=1,text="Configure this computer or a redstone relay."}
|
||||||
|
local iface_list = ListBox{parent=rs_c_1,x=1,y=3,height=10,width=49,scroll_height=1000,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
|
||||||
|
|
||||||
|
-- update relay interface list
|
||||||
|
function tool_ctl.update_relay_list()
|
||||||
|
local mounts = ppm.list_mounts()
|
||||||
|
|
||||||
|
iface_list.remove_all()
|
||||||
|
|
||||||
|
-- assemble list of configured relays
|
||||||
|
local relays = {}
|
||||||
|
for i = 1, #tmp_cfg.Redstone do
|
||||||
|
local def = tmp_cfg.Redstone[i]
|
||||||
|
if def.relay and not util.table_contains(relays, def.relay) then
|
||||||
|
table.insert(relays, def.relay)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- add unconfigured connected relays
|
||||||
|
for name, entry in pairs(mounts) do
|
||||||
|
if entry.type == "redstone_relay" and not util.table_contains(relays, name) then
|
||||||
|
table.insert(relays, name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function config_rs(name)
|
||||||
|
header.set_value(" Redstone Connections (" .. name .. ")")
|
||||||
|
|
||||||
|
self.rs_cfg_phy = tri(name == "local", nil, name)
|
||||||
|
|
||||||
|
tool_ctl.gen_rs_summary()
|
||||||
|
rs_pane.set_value(2)
|
||||||
|
end
|
||||||
|
|
||||||
|
local line = Div{parent=iface_list,height=2,fg_bg=cpair(colors.black,colors.white)}
|
||||||
|
TextBox{parent=line,x=1,y=1,text="@ local",fg_bg=cpair(colors.black,colors.white)}
|
||||||
|
TextBox{parent=line,x=3,y=2,text="This Computer",fg_bg=cpair(colors.gray,colors.white)}
|
||||||
|
local count = #redstone_subset(ini_cfg.Redstone, nil)
|
||||||
|
TextBox{parent=line,x=33,y=2,width=16,alignment=core.ALIGN.RIGHT,text=count.." connections",fg_bg=cpair(colors.gray,colors.white)}
|
||||||
|
|
||||||
|
PushButton{parent=line,x=41,y=1,min_width=8,height=1,text="CONFIG",callback=function()config_rs("local")end,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
||||||
|
|
||||||
|
for i = 1, #relays do
|
||||||
|
local name = relays[i]
|
||||||
|
|
||||||
|
line = Div{parent=iface_list,height=2,fg_bg=cpair(colors.black,colors.white)}
|
||||||
|
TextBox{parent=line,x=1,y=1,text="@ "..name,fg_bg=cpair(colors.black,colors.white)}
|
||||||
|
TextBox{parent=line,x=3,y=2,text="Redstone Relay",fg_bg=cpair(colors.gray,colors.white)}
|
||||||
|
TextBox{parent=line,x=18,y=2,text=tri(mounts[name],"ONLINE","OFFLINE"),fg_bg=cpair(tri(mounts[name],colors.green,colors.red),colors.white)}
|
||||||
|
count = #redstone_subset(ini_cfg.Redstone, name)
|
||||||
|
TextBox{parent=line,x=33,y=2,width=16,alignment=core.ALIGN.RIGHT,text=count.." connections",fg_bg=cpair(colors.gray,colors.white)}
|
||||||
|
|
||||||
|
PushButton{parent=line,x=41,y=1,min_width=8,height=1,text="CONFIG",callback=function()config_rs(name)end,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
tool_ctl.update_relay_list()
|
||||||
|
|
||||||
|
PushButton{parent=rs_c_1,x=1,y=14,text="\x1b Back",callback=function()main_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
PushButton{parent=rs_c_1,x=27,y=14,min_width=23,text="I don't see my relay!",callback=function()rs_pane.set_value(10)end,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
--#region Configuration List
|
||||||
|
|
||||||
|
TextBox{parent=rs_c_2,x=1,y=1,text=" port side/color unit/facility",fg_bg=g_lg_fg_bg}
|
||||||
|
local rs_list = ListBox{parent=rs_c_2,x=1,y=2,height=11,width=49,scroll_height=200,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
|
||||||
|
|
||||||
local function rs_revert()
|
local function rs_revert()
|
||||||
tmp_cfg.Redstone = tool_ctl.deep_copy_rs(ini_cfg.Redstone)
|
tmp_cfg.Redstone = tool_ctl.deep_copy_rs(ini_cfg.Redstone)
|
||||||
@@ -144,39 +247,47 @@ function redstone.create(tool_ctl, main_pane, cfg_sys, rs_cfg, style)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function rs_apply()
|
local function rs_apply()
|
||||||
settings.set("Redstone", tmp_cfg.Redstone)
|
-- add the changed data to the existing saved data
|
||||||
|
local new_data = redstone_subset(tmp_cfg.Redstone, self.rs_cfg_phy)
|
||||||
|
local new_save = redstone_subset(ini_cfg.Redstone, self.rs_cfg_phy, true)
|
||||||
|
for i = 1, #new_data do table.insert(new_save, new_data[i]) end
|
||||||
|
|
||||||
|
settings.set("Redstone", new_save)
|
||||||
|
|
||||||
if settings.save("/rtu.settings") then
|
if settings.save("/rtu.settings") then
|
||||||
load_settings(settings_cfg, true)
|
load_settings(settings_cfg, true)
|
||||||
load_settings(ini_cfg)
|
load_settings(ini_cfg)
|
||||||
rs_pane.set_value(4)
|
|
||||||
else
|
|
||||||
rs_pane.set_value(5)
|
rs_pane.set_value(5)
|
||||||
|
|
||||||
|
-- for return to list from saved screen
|
||||||
|
-- this will delete unsaved changes for other phy's, which is acceptable
|
||||||
|
tmp_cfg.Redstone = tool_ctl.deep_copy_rs(ini_cfg.Redstone)
|
||||||
|
tool_ctl.gen_rs_summary()
|
||||||
|
tool_ctl.update_relay_list()
|
||||||
|
else
|
||||||
|
rs_pane.set_value(6)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
PushButton{parent=rs_c_1,x=1,y=14,text="\x1b Back",callback=function()main_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
local function rs_back()
|
||||||
local rs_revert_btn = PushButton{parent=rs_c_1,x=8,y=14,min_width=16,text="Revert Changes",callback=rs_revert,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
self.rs_cfg_phy = false
|
||||||
PushButton{parent=rs_c_1,x=35,y=14,min_width=7,text="New +",callback=function()rs_pane.set_value(2)end,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg}
|
rs_pane.set_value(1)
|
||||||
local rs_apply_btn = PushButton{parent=rs_c_1,x=43,y=14,min_width=7,text="Apply",callback=rs_apply,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
header.set_value(" Redstone Connections")
|
||||||
|
end
|
||||||
|
|
||||||
TextBox{parent=rs_c_6,x=1,y=1,height=5,text="You already configured this input. There can only be one entry for each input.\n\nPlease select a different port."}
|
PushButton{parent=rs_c_2,x=1,y=14,text="\x1b Back",callback=rs_back,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
PushButton{parent=rs_c_6,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
local rs_revert_btn = PushButton{parent=rs_c_2,x=8,y=14,min_width=16,text="Revert Changes",callback=rs_revert,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
||||||
|
PushButton{parent=rs_c_2,x=35,y=14,min_width=7,text="New +",callback=function()rs_pane.set_value(3)end,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg}
|
||||||
|
local rs_apply_btn = PushButton{parent=rs_c_2,x=43,y=14,min_width=7,text="Apply",callback=rs_apply,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=rs_c_2,x=1,y=1,text="Select one of the below ports to use."}
|
--#endregion
|
||||||
|
--#region Port Selection
|
||||||
|
|
||||||
local rs_ports = ListBox{parent=rs_c_2,x=1,y=3,height=10,width=49,scroll_height=200,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
|
TextBox{parent=rs_c_3,x=1,y=1,text="Select one of the below ports to use."}
|
||||||
|
|
||||||
|
local rs_ports = ListBox{parent=rs_c_3,x=1,y=3,height=10,width=49,scroll_height=200,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
|
||||||
|
|
||||||
local function new_rs(port)
|
local function new_rs(port)
|
||||||
if (rsio.get_io_dir(port) == rsio.IO_DIR.IN) then
|
|
||||||
for i = 1, #tmp_cfg.Redstone do
|
|
||||||
if tmp_cfg.Redstone[i].port == port then
|
|
||||||
rs_pane.set_value(6)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
self.rs_cfg_editing = false
|
self.rs_cfg_editing = false
|
||||||
|
|
||||||
local text
|
local text
|
||||||
@@ -185,6 +296,8 @@ function redstone.create(tool_ctl, main_pane, cfg_sys, rs_cfg, style)
|
|||||||
self.rs_cfg_color.hide(true)
|
self.rs_cfg_color.hide(true)
|
||||||
self.rs_cfg_shortcut.show()
|
self.rs_cfg_shortcut.show()
|
||||||
self.rs_cfg_side_l.set_value("Output Side")
|
self.rs_cfg_side_l.set_value("Output Side")
|
||||||
|
self.rs_cfg_bundled.enable()
|
||||||
|
self.rs_cfg_advanced.disable()
|
||||||
text = "You selected the ALL_WASTE shortcut."
|
text = "You selected the ALL_WASTE shortcut."
|
||||||
else
|
else
|
||||||
self.rs_cfg_shortcut.hide(true)
|
self.rs_cfg_shortcut.hide(true)
|
||||||
@@ -195,6 +308,19 @@ function redstone.create(tool_ctl, main_pane, cfg_sys, rs_cfg, style)
|
|||||||
local io_mode = rsio.get_io_mode(port)
|
local io_mode = rsio.get_io_mode(port)
|
||||||
local inv = tri(rsio.digital_is_active(port, IO_LVL.LOW) == true, "inverted ", "")
|
local inv = tri(rsio.digital_is_active(port, IO_LVL.LOW) == true, "inverted ", "")
|
||||||
|
|
||||||
|
if rsio.is_analog(port) then
|
||||||
|
self.rs_cfg_bundled.set_value(false)
|
||||||
|
self.rs_cfg_bundled.disable()
|
||||||
|
self.rs_cfg_color.disable()
|
||||||
|
self.rs_cfg_inverted.set_value(false)
|
||||||
|
self.rs_cfg_advanced.disable()
|
||||||
|
else
|
||||||
|
self.rs_cfg_bundled.enable()
|
||||||
|
if self.rs_cfg_bundled.get_value() then self.rs_cfg_color.enable() else self.rs_cfg_color.disable() end
|
||||||
|
self.rs_cfg_inverted.set_value(false)
|
||||||
|
self.rs_cfg_advanced.enable()
|
||||||
|
end
|
||||||
|
|
||||||
if io_mode == IO_MODE.DIGITAL_IN then
|
if io_mode == IO_MODE.DIGITAL_IN then
|
||||||
io_type = inv .. "digital input "
|
io_type = inv .. "digital input "
|
||||||
elseif io_mode == IO_MODE.DIGITAL_OUT then
|
elseif io_mode == IO_MODE.DIGITAL_OUT then
|
||||||
@@ -218,7 +344,7 @@ function redstone.create(tool_ctl, main_pane, cfg_sys, rs_cfg, style)
|
|||||||
|
|
||||||
self.rs_cfg_selection.set_value(text)
|
self.rs_cfg_selection.set_value(text)
|
||||||
self.rs_cfg_port = port
|
self.rs_cfg_port = port
|
||||||
rs_pane.set_value(3)
|
rs_pane.set_value(4)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- add entries to redstone option list
|
-- add entries to redstone option list
|
||||||
@@ -239,43 +365,43 @@ function redstone.create(tool_ctl, main_pane, cfg_sys, rs_cfg, style)
|
|||||||
TextBox{parent=entry,x=22,y=1,text=PORT_DESC_MAP[i][2],fg_bg=cpair(colors.gray,colors.white)}
|
TextBox{parent=entry,x=22,y=1,text=PORT_DESC_MAP[i][2],fg_bg=cpair(colors.gray,colors.white)}
|
||||||
end
|
end
|
||||||
|
|
||||||
PushButton{parent=rs_c_2,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=rs_c_3,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
self.rs_cfg_selection = TextBox{parent=rs_c_3,x=1,y=1,height=2,text=""}
|
--#endregion
|
||||||
|
--#region Port Configuration
|
||||||
|
|
||||||
PushButton{parent=rs_c_3,x=36,y=3,text="What's that?",min_width=14,callback=function()rs_pane.set_value(7)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
self.rs_cfg_selection = TextBox{parent=rs_c_4,x=1,y=1,height=2,text=""}
|
||||||
|
|
||||||
TextBox{parent=rs_c_7,x=1,y=1,height=4,text="(Normal) Digital Input: On if there is a redstone signal, off otherwise\nInverted Digital Input: On without a redstone signal, off otherwise"}
|
PushButton{parent=rs_c_4,x=36,y=3,text="What's that?",min_width=14,callback=function()rs_pane.set_value(8)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
TextBox{parent=rs_c_7,x=1,y=6,height=4,text="(Normal) Digital Output: Redstone signal to 'turn it on', none to 'turn it off'\nInverted Digital Output: No redstone signal to 'turn it on', redstone signal to 'turn it off'"}
|
|
||||||
TextBox{parent=rs_c_7,x=1,y=11,height=2,text="Analog Input: 0-15 redstone power level input\nAnalog Output: 0-15 scaled redstone power level output"}
|
|
||||||
PushButton{parent=rs_c_7,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(3)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
|
||||||
|
|
||||||
self.rs_cfg_side_l = TextBox{parent=rs_c_3,x=1,y=4,width=11,text="Output Side"}
|
self.rs_cfg_side_l = TextBox{parent=rs_c_4,x=1,y=4,width=11,text="Output Side"}
|
||||||
local side = Radio2D{parent=rs_c_3,x=1,y=5,rows=1,columns=6,default=1,options=side_options,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.red}
|
local side = Radio2D{parent=rs_c_4,x=1,y=5,rows=1,columns=6,default=1,options=side_options,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.red}
|
||||||
|
|
||||||
self.rs_cfg_unit_l = TextBox{parent=rs_c_3,x=25,y=7,width=7,text="Unit ID"}
|
self.rs_cfg_unit_l = TextBox{parent=rs_c_4,x=25,y=7,width=7,text="Unit ID"}
|
||||||
self.rs_cfg_unit = NumberField{parent=rs_c_3,x=33,y=7,width=10,max_chars=2,min=1,max=4,fg_bg=bw_fg_bg}
|
self.rs_cfg_unit = NumberField{parent=rs_c_4,x=33,y=7,width=10,max_chars=2,min=1,max=4,fg_bg=bw_fg_bg}
|
||||||
|
|
||||||
local function set_bundled(bundled)
|
local function set_bundled(bundled)
|
||||||
if bundled then self.rs_cfg_color.enable() else self.rs_cfg_color.disable() end
|
if bundled then self.rs_cfg_color.enable() else self.rs_cfg_color.disable() end
|
||||||
end
|
end
|
||||||
|
|
||||||
self.rs_cfg_shortcut = TextBox{parent=rs_c_3,x=1,y=9,height=4,text="This shortcut will add entries for each of the 4 waste outputs. If you select bundled, 4 colors will be assigned to the selected side. Otherwise, 4 default sides will be used."}
|
self.rs_cfg_shortcut = TextBox{parent=rs_c_4,x=1,y=9,height=4,text="This shortcut will add entries for each of the 4 waste outputs. If you select bundled, 4 colors will be assigned to the selected side. Otherwise, 4 default sides will be used."}
|
||||||
self.rs_cfg_shortcut.hide(true)
|
self.rs_cfg_shortcut.hide(true)
|
||||||
|
|
||||||
local bundled = Checkbox{parent=rs_c_3,x=1,y=7,label="Is Bundled?",default=false,box_fg_bg=cpair(colors.red,colors.black),callback=set_bundled}
|
self.rs_cfg_bundled = Checkbox{parent=rs_c_4,x=1,y=7,label="Is Bundled?",default=false,box_fg_bg=cpair(colors.red,colors.black),callback=set_bundled,disable_fg_bg=g_lg_fg_bg}
|
||||||
self.rs_cfg_color = Radio2D{parent=rs_c_3,x=1,y=9,rows=4,columns=4,default=1,options=color_options,radio_colors=cpair(colors.lightGray,colors.black),color_map=color_options_map,disable_color=colors.gray,disable_fg_bg=g_lg_fg_bg}
|
self.rs_cfg_color = Radio2D{parent=rs_c_4,x=1,y=9,rows=4,columns=4,default=1,options=color_options,radio_colors=cpair(colors.lightGray,colors.black),color_map=color_options_map,disable_color=colors.gray,disable_fg_bg=g_lg_fg_bg}
|
||||||
self.rs_cfg_color.disable()
|
self.rs_cfg_color.disable()
|
||||||
|
|
||||||
local rs_err = TextBox{parent=rs_c_3,x=8,y=14,width=30,text="Unit ID must be within 1 to 4.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
local rs_err = TextBox{parent=rs_c_4,x=8,y=14,width=30,text="Unit ID invalid.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||||
rs_err.hide(true)
|
rs_err.hide(true)
|
||||||
|
|
||||||
local function back_from_rs_opts()
|
local function back_from_rs_opts()
|
||||||
rs_err.hide(true)
|
rs_err.hide(true)
|
||||||
if self.rs_cfg_editing ~= false then rs_pane.set_value(1) else rs_pane.set_value(2) end
|
if self.rs_cfg_editing ~= false then rs_pane.set_value(2) else rs_pane.set_value(3) end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function save_rs_entry()
|
local function save_rs_entry()
|
||||||
|
assert(self.rs_cfg_phy ~= false, "tried to save a redstone entry without a phy")
|
||||||
|
|
||||||
local port = self.rs_cfg_port
|
local port = self.rs_cfg_port
|
||||||
local u = tonumber(self.rs_cfg_unit.get_value())
|
local u = tonumber(self.rs_cfg_unit.get_value())
|
||||||
|
|
||||||
@@ -287,11 +413,23 @@ function redstone.create(tool_ctl, main_pane, cfg_sys, rs_cfg, style)
|
|||||||
local def = {
|
local def = {
|
||||||
unit = tri(PORT_DSGN[port] == 1, u, nil),
|
unit = tri(PORT_DSGN[port] == 1, u, nil),
|
||||||
port = port,
|
port = port,
|
||||||
|
relay = self.rs_cfg_phy,
|
||||||
side = side_options_map[side.get_value()],
|
side = side_options_map[side.get_value()],
|
||||||
color = tri(bundled.get_value(), color_options_map[self.rs_cfg_color.get_value()], nil)
|
color = tri(self.rs_cfg_bundled.get_value() and rsio.is_digital(port), color_options_map[self.rs_cfg_color.get_value()], nil),
|
||||||
|
invert = self.rs_cfg_inverted.get_value() or nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.rs_cfg_editing == false then
|
if self.rs_cfg_editing == false then
|
||||||
|
-- check for duplicate inputs for this unit/facility
|
||||||
|
if (rsio.get_io_dir(port) == rsio.IO_DIR.IN) then
|
||||||
|
for i = 1, #tmp_cfg.Redstone do
|
||||||
|
if tmp_cfg.Redstone[i].port == port and tmp_cfg.Redstone[i].unit == def.unit then
|
||||||
|
rs_pane.set_value(7)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
table.insert(tmp_cfg.Redstone, def)
|
table.insert(tmp_cfg.Redstone, def)
|
||||||
else
|
else
|
||||||
def.port = tmp_cfg.Redstone[self.rs_cfg_editing].port
|
def.port = tmp_cfg.Redstone[self.rs_cfg_editing].port
|
||||||
@@ -304,33 +442,55 @@ function redstone.create(tool_ctl, main_pane, cfg_sys, rs_cfg, style)
|
|||||||
table.insert(tmp_cfg.Redstone, {
|
table.insert(tmp_cfg.Redstone, {
|
||||||
unit = tri(PORT_DSGN[IO.WASTE_PU + i] == 1, u, nil),
|
unit = tri(PORT_DSGN[IO.WASTE_PU + i] == 1, u, nil),
|
||||||
port = IO.WASTE_PU + i,
|
port = IO.WASTE_PU + i,
|
||||||
side = tri(bundled.get_value(), side_options_map[side.get_value()], default_sides[i + 1]),
|
relay = self.rs_cfg_phy,
|
||||||
color = tri(bundled.get_value(), default_colors[i + 1], nil)
|
side = tri(self.rs_cfg_bundled.get_value(), side_options_map[side.get_value()], default_sides[i + 1]),
|
||||||
|
color = tri(self.rs_cfg_bundled.get_value(), default_colors[i + 1], nil)
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
rs_pane.set_value(1)
|
rs_pane.set_value(2)
|
||||||
tool_ctl.gen_rs_summary()
|
tool_ctl.gen_rs_summary()
|
||||||
|
|
||||||
side.set_value(1)
|
side.set_value(1)
|
||||||
bundled.set_value(false)
|
self.rs_cfg_bundled.set_value(false)
|
||||||
self.rs_cfg_color.set_value(1)
|
self.rs_cfg_color.set_value(1)
|
||||||
self.rs_cfg_color.disable()
|
self.rs_cfg_color.disable()
|
||||||
|
self.rs_cfg_inverted.set_value(false)
|
||||||
|
self.rs_cfg_advanced.disable()
|
||||||
else rs_err.show() end
|
else rs_err.show() end
|
||||||
end
|
end
|
||||||
|
|
||||||
PushButton{parent=rs_c_3,x=1,y=14,text="\x1b Back",callback=back_from_rs_opts,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=rs_c_4,x=1,y=14,text="\x1b Back",callback=back_from_rs_opts,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
PushButton{parent=rs_c_3,x=41,y=14,min_width=9,text="Confirm",callback=save_rs_entry,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg}
|
self.rs_cfg_advanced = PushButton{parent=rs_c_4,x=30,y=14,min_width=10,text="Advanced",callback=function()rs_pane.set_value(9)end,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
||||||
|
PushButton{parent=rs_c_4,x=41,y=14,min_width=9,text="Confirm",callback=save_rs_entry,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=rs_c_4,x=1,y=1,text="Settings saved!"}
|
--#endregion
|
||||||
PushButton{parent=rs_c_4,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
|
||||||
PushButton{parent=rs_c_4,x=44,y=14,min_width=6,text="Home",callback=function()tool_ctl.go_home()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
|
||||||
|
|
||||||
TextBox{parent=rs_c_5,x=1,y=1,height=5,text="Failed to save the settings file.\n\nThere may not be enough space for the modification or server file permissions may be denying writes."}
|
TextBox{parent=rs_c_5,x=1,y=1,text="Settings saved!"}
|
||||||
PushButton{parent=rs_c_5,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=rs_c_5,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
PushButton{parent=rs_c_5,x=44,y=14,min_width=6,text="Home",callback=function()tool_ctl.go_home()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=rs_c_5,x=44,y=14,min_width=6,text="Home",callback=function()tool_ctl.go_home()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
|
TextBox{parent=rs_c_6,x=1,y=1,height=5,text="Failed to save the settings file.\n\nThere may not be enough space for the modification or server file permissions may be denying writes."}
|
||||||
|
PushButton{parent=rs_c_6,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
PushButton{parent=rs_c_6,x=44,y=14,min_width=6,text="Home",callback=function()tool_ctl.go_home()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
|
TextBox{parent=rs_c_7,x=1,y=1,height=6,text="You already configured this input for this facility/unit assignment. There can only be one entry for each input per each unit or the facility (for facility inputs).\n\nPlease select a different port."}
|
||||||
|
PushButton{parent=rs_c_7,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(3)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
|
TextBox{parent=rs_c_8,x=1,y=1,height=4,text="(Normal) Digital Input: On if there is a redstone signal, off otherwise\nInverted Digital Input: On without a redstone signal, off otherwise"}
|
||||||
|
TextBox{parent=rs_c_8,x=1,y=6,height=4,text="(Normal) Digital Output: Redstone signal to 'turn it on', none to 'turn it off'\nInverted Digital Output: No redstone signal to 'turn it on', redstone signal to 'turn it off'"}
|
||||||
|
TextBox{parent=rs_c_8,x=1,y=11,height=2,text="Analog Input: 0-15 redstone power level input\nAnalog Output: 0-15 scaled redstone power level output"}
|
||||||
|
PushButton{parent=rs_c_8,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(4)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
|
TextBox{parent=rs_c_9,x=1,y=1,height=5,text="Advanced Options"}
|
||||||
|
self.rs_cfg_inverted = Checkbox{parent=rs_c_9,x=1,y=3,label="Invert",default=false,box_fg_bg=cpair(colors.red,colors.black),callback=function()end,disable_fg_bg=g_lg_fg_bg}
|
||||||
|
TextBox{parent=rs_c_9,x=3,y=4,height=4,text="Digital I/O is already inverted (or not) based on intended use. If you have a non-standard setup, you can use this option to avoid needing a redstone inverter.",fg_bg=cpair(colors.gray,colors.lightGray)}
|
||||||
|
PushButton{parent=rs_c_9,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(4)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
|
TextBox{parent=rs_c_10,x=1,y=1,height=10,text="Make sure your relay is either touching the RTU gateway or connected via wired modems. There should be a wired modem on a side of the RTU gateway then one on the device, connected by a cable. The modem on the device needs to be right clicked to connect it (which will turn its border red), at which point the peripheral name will be shown in the chat."}
|
||||||
|
PushButton{parent=rs_c_10,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
--#endregion
|
--#endregion
|
||||||
|
|
||||||
--#region Tool Functions
|
--#region Tool Functions
|
||||||
@@ -356,6 +516,16 @@ function redstone.create(tool_ctl, main_pane, cfg_sys, rs_cfg, style)
|
|||||||
text = text .. "the facility)."
|
text = text .. "the facility)."
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if rsio.is_analog(def.port) then
|
||||||
|
self.rs_cfg_bundled.set_value(false)
|
||||||
|
self.rs_cfg_bundled.disable()
|
||||||
|
self.rs_cfg_advanced.disable()
|
||||||
|
else
|
||||||
|
self.rs_cfg_bundled.enable()
|
||||||
|
self.rs_cfg_bundled.set_value(def.color ~= nil)
|
||||||
|
self.rs_cfg_advanced.enable()
|
||||||
|
end
|
||||||
|
|
||||||
local value = 1
|
local value = 1
|
||||||
if def.color ~= nil then
|
if def.color ~= nil then
|
||||||
value = color_to_idx(def.color)
|
value = color_to_idx(def.color)
|
||||||
@@ -367,9 +537,9 @@ function redstone.create(tool_ctl, main_pane, cfg_sys, rs_cfg, style)
|
|||||||
self.rs_cfg_selection.set_value(text)
|
self.rs_cfg_selection.set_value(text)
|
||||||
self.rs_cfg_side_l.set_value(tri(rsio.get_io_dir(def.port) == rsio.IO_DIR.IN, "Input Side", "Output Side"))
|
self.rs_cfg_side_l.set_value(tri(rsio.get_io_dir(def.port) == rsio.IO_DIR.IN, "Input Side", "Output Side"))
|
||||||
side.set_value(side_to_idx(def.side))
|
side.set_value(side_to_idx(def.side))
|
||||||
bundled.set_value(def.color ~= nil)
|
|
||||||
self.rs_cfg_color.set_value(value)
|
self.rs_cfg_color.set_value(value)
|
||||||
rs_pane.set_value(3)
|
self.rs_cfg_inverted.set_value(def.invert or false)
|
||||||
|
rs_pane.set_value(4)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function delete_rs_entry(idx)
|
local function delete_rs_entry(idx)
|
||||||
@@ -379,33 +549,41 @@ function redstone.create(tool_ctl, main_pane, cfg_sys, rs_cfg, style)
|
|||||||
|
|
||||||
-- generate the redstone summary list
|
-- generate the redstone summary list
|
||||||
function tool_ctl.gen_rs_summary()
|
function tool_ctl.gen_rs_summary()
|
||||||
|
assert(self.rs_cfg_phy ~= false, "tried to generate a summary without a phy set")
|
||||||
|
|
||||||
rs_list.remove_all()
|
rs_list.remove_all()
|
||||||
|
|
||||||
local modified = #ini_cfg.Redstone ~= #tmp_cfg.Redstone
|
local ini = redstone_subset(ini_cfg.Redstone, self.rs_cfg_phy)
|
||||||
|
local tmp = redstone_subset(tmp_cfg.Redstone, self.rs_cfg_phy)
|
||||||
|
|
||||||
|
local modified = #ini ~= #tmp
|
||||||
|
|
||||||
for i = 1, #tmp_cfg.Redstone do
|
for i = 1, #tmp_cfg.Redstone do
|
||||||
local def = tmp_cfg.Redstone[i]
|
local def = tmp_cfg.Redstone[i]
|
||||||
|
|
||||||
local name = rsio.to_string(def.port)
|
if def.relay == self.rs_cfg_phy then
|
||||||
local io_dir = tri(rsio.get_io_mode(def.port) == rsio.IO_DIR.IN, "\x1a", "\x1b")
|
local name = rsio.to_string(def.port)
|
||||||
local conn = def.side
|
local io_dir = tri(rsio.get_io_dir(def.port) == rsio.IO_DIR.IN, "\x1a", "\x1b")
|
||||||
local unit = util.strval(def.unit or "F")
|
local io_c = tri(rsio.is_digital(def.port), colors.blue, colors.purple)
|
||||||
|
local conn = def.side
|
||||||
|
local unit = util.strval(def.unit or "F")
|
||||||
|
|
||||||
if def.color ~= nil then conn = def.side .. "/" .. rsio.color_name(def.color) end
|
if def.color ~= nil then conn = def.side .. "/" .. rsio.color_name(def.color) end
|
||||||
|
|
||||||
local entry = Div{parent=rs_list,height=1}
|
local entry = Div{parent=rs_list,height=1}
|
||||||
TextBox{parent=entry,x=1,y=1,width=1,text=io_dir,fg_bg=cpair(colors.lightGray,colors.white)}
|
TextBox{parent=entry,x=1,y=1,width=1,text=io_dir,fg_bg=cpair(tri(def.invert,colors.orange,io_c),colors.white)}
|
||||||
TextBox{parent=entry,x=2,y=1,width=14,text=name}
|
TextBox{parent=entry,x=2,y=1,width=14,text=name}
|
||||||
TextBox{parent=entry,x=16,y=1,width=string.len(conn),text=conn,fg_bg=cpair(colors.gray,colors.white)}
|
TextBox{parent=entry,x=16,y=1,width=string.len(conn),text=conn,fg_bg=cpair(colors.gray,colors.white)}
|
||||||
TextBox{parent=entry,x=33,y=1,width=1,text=unit,fg_bg=cpair(colors.gray,colors.white)}
|
TextBox{parent=entry,x=33,y=1,width=1,text=unit,fg_bg=cpair(colors.gray,colors.white)}
|
||||||
PushButton{parent=entry,x=35,y=1,min_width=6,height=1,text="EDIT",callback=function()edit_rs_entry(i)end,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=entry,x=35,y=1,min_width=6,height=1,text="EDIT",callback=function()edit_rs_entry(i)end,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg}
|
||||||
PushButton{parent=entry,x=41,y=1,min_width=8,height=1,text="DELETE",callback=function()delete_rs_entry(i)end,fg_bg=cpair(colors.black,colors.red),active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=entry,x=41,y=1,min_width=8,height=1,text="DELETE",callback=function()delete_rs_entry(i)end,fg_bg=cpair(colors.black,colors.red),active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
if not modified then
|
if not modified then
|
||||||
local a = ini_cfg.Redstone[i]
|
local a = ini_cfg.Redstone[i]
|
||||||
local b = tmp_cfg.Redstone[i]
|
local b = tmp_cfg.Redstone[i]
|
||||||
|
|
||||||
modified = (a.unit ~= b.unit) or (a.port ~= b.port) or (a.side ~= b.side) or (a.color ~= b.color)
|
modified = (a.unit ~= b.unit) or (a.port ~= b.port) or (a.relay ~= b.relay) or (a.side ~= b.side) or (a.color ~= b.color) or (a.invert ~= b.invert)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -506,7 +506,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style)
|
|||||||
local u, idx = def.unit, def.index
|
local u, idx = def.unit, def.index
|
||||||
|
|
||||||
if util.table_contains(NEEDS_UNIT, mount.type) then
|
if util.table_contains(NEEDS_UNIT, mount.type) then
|
||||||
if (mount.type == "dynamicValve" or mount.type == "environmentDetector") and for_facility then
|
if (mount.type == "dynamicValve" or mount.type == "environmentDetector" or mount.type == "environment_detector") and for_facility then
|
||||||
-- skip
|
-- skip
|
||||||
elseif not (util.is_int(u) and u > 0 and u < 5) then
|
elseif not (util.is_int(u) and u > 0 and u < 5) then
|
||||||
err = true
|
err = true
|
||||||
@@ -527,7 +527,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style)
|
|||||||
else index = idx end
|
else index = idx end
|
||||||
elseif mount.type == "dynamicValve" then
|
elseif mount.type == "dynamicValve" then
|
||||||
index = 1
|
index = 1
|
||||||
elseif mount.type == "environmentDetector" then
|
elseif mount.type == "environmentDetector" or mount.type == "environment_detector" then
|
||||||
if not (util.is_int(idx) and idx > 0) then
|
if not (util.is_int(idx) and idx > 0) then
|
||||||
err = true
|
err = true
|
||||||
else index = idx end
|
else index = idx end
|
||||||
@@ -646,7 +646,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style)
|
|||||||
local c = tri(alternate, g_lg_fg_bg, cpair(colors.gray,colors.white))
|
local c = tri(alternate, g_lg_fg_bg, cpair(colors.gray,colors.white))
|
||||||
alternate = not alternate
|
alternate = not alternate
|
||||||
|
|
||||||
if string.len(val) > val_max_w then
|
if (string.len(val) > val_max_w) or string.find(val, "\n") then
|
||||||
local lines = util.strwrap(val, inner_width)
|
local lines = util.strwrap(val, inner_width)
|
||||||
height = #lines + 1
|
height = #lines + 1
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ local ppm = require("scada-common.ppm")
|
|||||||
local tcd = require("scada-common.tcd")
|
local tcd = require("scada-common.tcd")
|
||||||
local util = require("scada-common.util")
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
|
local check = require("rtu.config.check")
|
||||||
local peripherals = require("rtu.config.peripherals")
|
local peripherals = require("rtu.config.peripherals")
|
||||||
local redstone = require("rtu.config.redstone")
|
local redstone = require("rtu.config.redstone")
|
||||||
local system = require("rtu.config.system")
|
local system = require("rtu.config.system")
|
||||||
@@ -34,7 +35,9 @@ local changes = {
|
|||||||
{ "v1.7.9", { "ConnTimeout can now have a fractional part" } },
|
{ "v1.7.9", { "ConnTimeout can now have a fractional part" } },
|
||||||
{ "v1.7.15", { "Added front panel UI theme", "Added color accessibility modes" } },
|
{ "v1.7.15", { "Added front panel UI theme", "Added color accessibility modes" } },
|
||||||
{ "v1.9.2", { "Added standard with black off state color mode", "Added blue indicator color modes" } },
|
{ "v1.9.2", { "Added standard with black off state color mode", "Added blue indicator color modes" } },
|
||||||
{ "v1.10.2", { "Re-organized peripheral configuration UI, resulting in some input fields being re-ordered" } }
|
{ "v1.10.2", { "Re-organized peripheral configuration UI, resulting in some input fields being re-ordered" } },
|
||||||
|
{ "v1.11.8", { "Added advanced option to invert digital redstone signals" } },
|
||||||
|
{ "v1.12.0", { "Added support for redstone relays" } }
|
||||||
}
|
}
|
||||||
|
|
||||||
---@class rtu_configurator
|
---@class rtu_configurator
|
||||||
@@ -55,6 +58,7 @@ style.btn_dis_fg_bg = cpair(colors.lightGray, colors.white)
|
|||||||
|
|
||||||
---@class _rtu_cfg_tool_ctl
|
---@class _rtu_cfg_tool_ctl
|
||||||
local tool_ctl = {
|
local tool_ctl = {
|
||||||
|
launch_startup = false,
|
||||||
ask_config = false,
|
ask_config = false,
|
||||||
has_config = false,
|
has_config = false,
|
||||||
viewing_config = false,
|
viewing_config = false,
|
||||||
@@ -73,6 +77,7 @@ local tool_ctl = {
|
|||||||
gen_summary = nil, ---@type function
|
gen_summary = nil, ---@type function
|
||||||
load_legacy = nil, ---@type function
|
load_legacy = nil, ---@type function
|
||||||
update_peri_list = nil, ---@type function
|
update_peri_list = nil, ---@type function
|
||||||
|
update_relay_list = nil, ---@type function
|
||||||
gen_peri_summary = nil, ---@type function
|
gen_peri_summary = nil, ---@type function
|
||||||
gen_rs_summary = nil, ---@type function
|
gen_rs_summary = nil, ---@type function
|
||||||
}
|
}
|
||||||
@@ -114,6 +119,7 @@ local fields = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
-- deep copy peripherals defs
|
-- deep copy peripherals defs
|
||||||
|
---@param data rtu_peri_definition[]
|
||||||
function tool_ctl.deep_copy_peri(data)
|
function tool_ctl.deep_copy_peri(data)
|
||||||
local array = {}
|
local array = {}
|
||||||
for _, d in ipairs(data) do table.insert(array, { unit = d.unit, index = d.index, name = d.name }) end
|
for _, d in ipairs(data) do table.insert(array, { unit = d.unit, index = d.index, name = d.name }) end
|
||||||
@@ -121,9 +127,10 @@ function tool_ctl.deep_copy_peri(data)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- deep copy redstone defs
|
-- deep copy redstone defs
|
||||||
|
---@param data rtu_rs_definition[]
|
||||||
function tool_ctl.deep_copy_rs(data)
|
function tool_ctl.deep_copy_rs(data)
|
||||||
local array = {}
|
local array = {}
|
||||||
for _, d in ipairs(data) do table.insert(array, { unit = d.unit, port = d.port, side = d.side, color = d.color }) end
|
for _, d in ipairs(data) do table.insert(array, { unit = d.unit, port = d.port, relay = d.relay, side = d.side, color = d.color, invert = d.invert }) end
|
||||||
return array
|
return array
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -168,15 +175,16 @@ local function config_view(display)
|
|||||||
local changelog = Div{parent=root_pane_div,x=1,y=1}
|
local changelog = Div{parent=root_pane_div,x=1,y=1}
|
||||||
local peri_cfg = Div{parent=root_pane_div,x=1,y=1}
|
local peri_cfg = Div{parent=root_pane_div,x=1,y=1}
|
||||||
local rs_cfg = Div{parent=root_pane_div,x=1,y=1}
|
local rs_cfg = Div{parent=root_pane_div,x=1,y=1}
|
||||||
|
local check_sys = Div{parent=root_pane_div,x=1,y=1}
|
||||||
|
|
||||||
local main_pane = MultiPane{parent=root_pane_div,x=1,y=1,panes={main_page,spkr_cfg,net_cfg,log_cfg,clr_cfg,summary,changelog,peri_cfg,rs_cfg}}
|
local main_pane = MultiPane{parent=root_pane_div,x=1,y=1,panes={main_page,spkr_cfg,net_cfg,log_cfg,clr_cfg,summary,changelog,peri_cfg,rs_cfg,check_sys}}
|
||||||
|
|
||||||
--#region Main Page
|
--#region Main Page
|
||||||
|
|
||||||
local y_start = 2
|
local y_start = 2
|
||||||
|
|
||||||
if tool_ctl.ask_config then
|
if tool_ctl.ask_config then
|
||||||
TextBox{parent=main_page,x=2,y=y_start,height=4,width=49,text="Notice: This device had no valid config so the configurator has been automatically started. If you previously had a valid config, you may want to check the Change Log to see what changed.",fg_bg=cpair(colors.red,colors.lightGray)}
|
TextBox{parent=main_page,x=2,y=y_start,height=4,width=49,text="Notice: This device is not configured for this version of the RTU gateway. If you previously had a valid config, it's not lost. You may want to check the Change Log to see what changed.",fg_bg=cpair(colors.red,colors.lightGray)}
|
||||||
y_start = y_start + 5
|
y_start = y_start + 5
|
||||||
else
|
else
|
||||||
TextBox{parent=main_page,x=2,y=2,height=2,text="Welcome to the RTU gateway configurator! Please select one of the following options."}
|
TextBox{parent=main_page,x=2,y=2,height=2,text="Welcome to the RTU gateway configurator! Please select one of the following options."}
|
||||||
@@ -202,7 +210,6 @@ local function config_view(display)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function show_rs_conns()
|
local function show_rs_conns()
|
||||||
tool_ctl.gen_rs_summary()
|
|
||||||
main_pane.set_value(9)
|
main_pane.set_value(9)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -218,9 +225,18 @@ local function config_view(display)
|
|||||||
main_pane.set_value(5)
|
main_pane.set_value(5)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function startup()
|
||||||
|
tool_ctl.launch_startup = true
|
||||||
|
exit()
|
||||||
|
end
|
||||||
|
|
||||||
PushButton{parent=main_page,x=2,y=17,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=main_page,x=2,y=17,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=btn_act_fg_bg}
|
||||||
tool_ctl.color_cfg = PushButton{parent=main_page,x=23,y=17,min_width=15,text="Color Options",callback=jump_color,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
local start_btn = PushButton{parent=main_page,x=42,y=17,min_width=9,text="Startup",callback=startup,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
||||||
PushButton{parent=main_page,x=39,y=17,min_width=12,text="Change Log",callback=function()main_pane.set_value(7)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=main_page,x=39,y=y_start,min_width=12,text="Self-Check",callback=function()main_pane.set_value(10)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
||||||
|
tool_ctl.color_cfg = PushButton{parent=main_page,x=36,y=y_start+2,min_width=15,text="Color Options",callback=jump_color,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
||||||
|
PushButton{parent=main_page,x=39,y=y_start+4,min_width=12,text="Change Log",callback=function()main_pane.set_value(7)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
|
if tool_ctl.ask_config then start_btn.disable() end
|
||||||
|
|
||||||
if not tool_ctl.has_config then
|
if not tool_ctl.has_config then
|
||||||
tool_ctl.view_gw_cfg.disable()
|
tool_ctl.view_gw_cfg.disable()
|
||||||
@@ -274,6 +290,12 @@ local function config_view(display)
|
|||||||
PushButton{parent=cl,x=1,y=14,text="\x1b Back",callback=function()main_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=cl,x=1,y=14,text="\x1b Back",callback=function()main_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
--#endregion
|
--#endregion
|
||||||
|
|
||||||
|
--#region Self-Check
|
||||||
|
|
||||||
|
check.create(main_pane, settings_cfg, check_sys, style)
|
||||||
|
|
||||||
|
--#endregion
|
||||||
end
|
end
|
||||||
|
|
||||||
-- reset terminal screen
|
-- reset terminal screen
|
||||||
@@ -308,7 +330,7 @@ function configurator.configure(ask_config)
|
|||||||
config_view(display)
|
config_view(display)
|
||||||
|
|
||||||
while true do
|
while true do
|
||||||
local event, param1, param2, param3 = util.pull_event()
|
local event, param1, param2, param3, param4, param5 = util.pull_event()
|
||||||
|
|
||||||
-- handle event
|
-- handle event
|
||||||
if event == "timer" then
|
if event == "timer" then
|
||||||
@@ -321,14 +343,18 @@ function configurator.configure(ask_config)
|
|||||||
if k_e then display.handle_key(k_e) end
|
if k_e then display.handle_key(k_e) end
|
||||||
elseif event == "paste" then
|
elseif event == "paste" then
|
||||||
display.handle_paste(param1)
|
display.handle_paste(param1)
|
||||||
|
elseif event == "modem_message" then
|
||||||
|
check.receive_sv(param1, param2, param3, param4, param5)
|
||||||
elseif event == "peripheral_detach" then
|
elseif event == "peripheral_detach" then
|
||||||
---@diagnostic disable-next-line: discard-returns
|
---@diagnostic disable-next-line: discard-returns
|
||||||
ppm.handle_unmount(param1)
|
ppm.handle_unmount(param1)
|
||||||
tool_ctl.update_peri_list()
|
tool_ctl.update_peri_list()
|
||||||
|
tool_ctl.update_relay_list()
|
||||||
elseif event == "peripheral" then
|
elseif event == "peripheral" then
|
||||||
---@diagnostic disable-next-line: discard-returns
|
---@diagnostic disable-next-line: discard-returns
|
||||||
ppm.mount(param1)
|
ppm.mount(param1)
|
||||||
tool_ctl.update_peri_list()
|
tool_ctl.update_peri_list()
|
||||||
|
tool_ctl.update_relay_list()
|
||||||
end
|
end
|
||||||
|
|
||||||
if event == "terminate" then return end
|
if event == "terminate" then return end
|
||||||
@@ -346,7 +372,7 @@ function configurator.configure(ask_config)
|
|||||||
println("configurator error: " .. error)
|
println("configurator error: " .. error)
|
||||||
end
|
end
|
||||||
|
|
||||||
return status, error
|
return status, error, tool_ctl.launch_startup
|
||||||
end
|
end
|
||||||
|
|
||||||
return configurator
|
return configurator
|
||||||
|
|||||||
@@ -11,10 +11,14 @@ local digital_write = rsio.digital_write
|
|||||||
|
|
||||||
-- create new redstone device
|
-- create new redstone device
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
|
---@param relay? table optional redstone relay to use instead of the computer's redstone interface
|
||||||
---@return rtu_rs_device interface, boolean faulted
|
---@return rtu_rs_device interface, boolean faulted
|
||||||
function redstone_rtu.new()
|
function redstone_rtu.new(relay)
|
||||||
local unit = rtu.init_unit()
|
local unit = rtu.init_unit()
|
||||||
|
|
||||||
|
-- physical interface to use
|
||||||
|
local phy = relay or rs
|
||||||
|
|
||||||
-- get RTU interface
|
-- get RTU interface
|
||||||
local interface = unit.interface()
|
local interface = unit.interface()
|
||||||
|
|
||||||
@@ -30,85 +34,114 @@ function redstone_rtu.new()
|
|||||||
write_holding_reg = interface.write_holding_reg
|
write_holding_reg = interface.write_holding_reg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
-- change the phy in use (a relay or rs)
|
||||||
|
---@param new_phy table
|
||||||
|
function public.remount_phy(new_phy) phy = new_phy end
|
||||||
|
|
||||||
|
-- NOTE: for runtime speed, inversion logic results in extra code here but less code when functions are called
|
||||||
|
|
||||||
-- link digital input
|
-- link digital input
|
||||||
---@param side string
|
---@param side string
|
||||||
---@param color integer
|
---@param color integer
|
||||||
function public.link_di(side, color)
|
---@param invert boolean|nil
|
||||||
local f_read ---@type function
|
---@return integer count count of digital inputs
|
||||||
|
function public.link_di(side, color, invert)
|
||||||
|
local f_read ---@type function
|
||||||
|
|
||||||
if color then
|
if color then
|
||||||
f_read = function ()
|
if invert then
|
||||||
return digital_read(rs.testBundledInput(side, color))
|
f_read = function () return digital_read(not phy.testBundledInput(side, color)) end
|
||||||
|
else
|
||||||
|
f_read = function () return digital_read(phy.testBundledInput(side, color)) end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
f_read = function ()
|
if invert then
|
||||||
return digital_read(rs.getInput(side))
|
f_read = function () return digital_read(not phy.getInput(side)) end
|
||||||
|
else
|
||||||
|
f_read = function () return digital_read(phy.getInput(side)) end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
unit.connect_di(f_read)
|
return unit.connect_di(f_read)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- link digital output
|
-- link digital output
|
||||||
---@param side string
|
---@param side string
|
||||||
---@param color integer
|
---@param color integer
|
||||||
function public.link_do(side, color)
|
---@param invert boolean|nil
|
||||||
local f_read ---@type function
|
---@return integer count count of digital outputs
|
||||||
local f_write ---@type function
|
function public.link_do(side, color, invert)
|
||||||
|
local f_read ---@type function
|
||||||
|
local f_write ---@type function
|
||||||
|
|
||||||
if color then
|
if color then
|
||||||
f_read = function ()
|
if invert then
|
||||||
return digital_read(colors.test(rs.getBundledOutput(side), color))
|
f_read = function () return digital_read(not colors.test(phy.getBundledOutput(side), color)) end
|
||||||
end
|
|
||||||
|
|
||||||
f_write = function (level)
|
f_write = function (level)
|
||||||
if level ~= IO_LVL.FLOATING and level ~= IO_LVL.DISCONNECT then
|
if level ~= IO_LVL.FLOATING and level ~= IO_LVL.DISCONNECT then
|
||||||
local output = rs.getBundledOutput(side)
|
local output = phy.getBundledOutput(side)
|
||||||
|
|
||||||
if digital_write(level) then
|
-- inverted conditions
|
||||||
output = colors.combine(output, color)
|
if digital_write(level) then
|
||||||
else
|
output = colors.subtract(output, color)
|
||||||
output = colors.subtract(output, color)
|
else output = colors.combine(output, color) end
|
||||||
|
|
||||||
|
phy.setBundledOutput(side, output)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
f_read = function () return digital_read(colors.test(phy.getBundledOutput(side), color)) end
|
||||||
|
|
||||||
rs.setBundledOutput(side, output)
|
f_write = function (level)
|
||||||
|
if level ~= IO_LVL.FLOATING and level ~= IO_LVL.DISCONNECT then
|
||||||
|
local output = phy.getBundledOutput(side)
|
||||||
|
|
||||||
|
if digital_write(level) then
|
||||||
|
output = colors.combine(output, color)
|
||||||
|
else output = colors.subtract(output, color) end
|
||||||
|
|
||||||
|
phy.setBundledOutput(side, output)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
f_read = function ()
|
if invert then
|
||||||
return digital_read(rs.getOutput(side))
|
f_read = function () return digital_read(not phy.getOutput(side)) end
|
||||||
end
|
|
||||||
|
|
||||||
f_write = function (level)
|
f_write = function (level)
|
||||||
if level ~= IO_LVL.FLOATING and level ~= IO_LVL.DISCONNECT then
|
if level ~= IO_LVL.FLOATING and level ~= IO_LVL.DISCONNECT then
|
||||||
rs.setOutput(side, digital_write(level))
|
phy.setOutput(side, not digital_write(level))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
f_read = function () return digital_read(phy.getOutput(side)) end
|
||||||
|
|
||||||
|
f_write = function (level)
|
||||||
|
if level ~= IO_LVL.FLOATING and level ~= IO_LVL.DISCONNECT then
|
||||||
|
phy.setOutput(side, digital_write(level))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
unit.connect_coil(f_read, f_write)
|
return unit.connect_coil(f_read, f_write)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- link analog input
|
-- link analog input
|
||||||
---@param side string
|
---@param side string
|
||||||
|
---@return integer count count of analog inputs
|
||||||
function public.link_ai(side)
|
function public.link_ai(side)
|
||||||
unit.connect_input_reg(
|
return unit.connect_input_reg(function () return phy.getAnalogInput(side) end)
|
||||||
function ()
|
|
||||||
return rs.getAnalogInput(side)
|
|
||||||
end
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- link analog output
|
-- link analog output
|
||||||
---@param side string
|
---@param side string
|
||||||
|
---@return integer count count of analog outputs
|
||||||
function public.link_ao(side)
|
function public.link_ao(side)
|
||||||
unit.connect_holding_reg(
|
return unit.connect_holding_reg(
|
||||||
function ()
|
function () return phy.getAnalogOutput(side) end,
|
||||||
return rs.getAnalogOutput(side)
|
function (value) phy.setAnalogOutput(side, value) end
|
||||||
end,
|
|
||||||
function (value)
|
|
||||||
rs.setAnalogOutput(side, value)
|
|
||||||
end
|
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -399,43 +399,41 @@ function modbus.new(rtu_dev, use_parallel_read)
|
|||||||
return public
|
return public
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- create an error reply
|
||||||
|
---@nodiscard
|
||||||
|
---@param packet modbus_frame MODBUS packet frame
|
||||||
|
---@param code MODBUS_EXCODE exception code
|
||||||
|
---@return modbus_packet reply
|
||||||
|
local function excode_reply(packet, code)
|
||||||
|
-- reply back with error flag and exception code
|
||||||
|
local reply = comms.modbus_packet()
|
||||||
|
local fcode = bit.bor(packet.func_code, MODBUS_FCODE.ERROR_FLAG)
|
||||||
|
reply.make(packet.txn_id, packet.unit_id, fcode, { code })
|
||||||
|
return reply
|
||||||
|
end
|
||||||
|
|
||||||
|
-- return a SERVER_DEVICE_FAIL error reply
|
||||||
|
---@nodiscard
|
||||||
|
---@param packet modbus_frame MODBUS packet frame
|
||||||
|
---@return modbus_packet reply
|
||||||
|
function modbus.reply__srv_device_fail(packet) return excode_reply(packet, MODBUS_EXCODE.SERVER_DEVICE_FAIL) end
|
||||||
|
|
||||||
-- return a SERVER_DEVICE_BUSY error reply
|
-- return a SERVER_DEVICE_BUSY error reply
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
---@param packet modbus_frame MODBUS packet frame
|
---@param packet modbus_frame MODBUS packet frame
|
||||||
---@return modbus_packet reply
|
---@return modbus_packet reply
|
||||||
function modbus.reply__srv_device_busy(packet)
|
function modbus.reply__srv_device_busy(packet) return excode_reply(packet, MODBUS_EXCODE.SERVER_DEVICE_BUSY) end
|
||||||
-- reply back with error flag and exception code
|
|
||||||
local reply = comms.modbus_packet()
|
|
||||||
local fcode = bit.bor(packet.func_code, MODBUS_FCODE.ERROR_FLAG)
|
|
||||||
local data = { MODBUS_EXCODE.SERVER_DEVICE_BUSY }
|
|
||||||
reply.make(packet.txn_id, packet.unit_id, fcode, data)
|
|
||||||
return reply
|
|
||||||
end
|
|
||||||
|
|
||||||
-- return a NEG_ACKNOWLEDGE error reply
|
-- return a NEG_ACKNOWLEDGE error reply
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
---@param packet modbus_frame MODBUS packet frame
|
---@param packet modbus_frame MODBUS packet frame
|
||||||
---@return modbus_packet reply
|
---@return modbus_packet reply
|
||||||
function modbus.reply__neg_ack(packet)
|
function modbus.reply__neg_ack(packet) return excode_reply(packet, MODBUS_EXCODE.NEG_ACKNOWLEDGE) end
|
||||||
-- reply back with error flag and exception code
|
|
||||||
local reply = comms.modbus_packet()
|
|
||||||
local fcode = bit.bor(packet.func_code, MODBUS_FCODE.ERROR_FLAG)
|
|
||||||
local data = { MODBUS_EXCODE.NEG_ACKNOWLEDGE }
|
|
||||||
reply.make(packet.txn_id, packet.unit_id, fcode, data)
|
|
||||||
return reply
|
|
||||||
end
|
|
||||||
|
|
||||||
-- return a GATEWAY_PATH_UNAVAILABLE error reply
|
-- return a GATEWAY_PATH_UNAVAILABLE error reply
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
---@param packet modbus_frame MODBUS packet frame
|
---@param packet modbus_frame MODBUS packet frame
|
||||||
---@return modbus_packet reply
|
---@return modbus_packet reply
|
||||||
function modbus.reply__gw_unavailable(packet)
|
function modbus.reply__gw_unavailable(packet) return excode_reply(packet, MODBUS_EXCODE.GATEWAY_PATH_UNAVAILABLE) end
|
||||||
-- reply back with error flag and exception code
|
|
||||||
local reply = comms.modbus_packet()
|
|
||||||
local fcode = bit.bor(packet.func_code, MODBUS_FCODE.ERROR_FLAG)
|
|
||||||
local data = { MODBUS_EXCODE.GATEWAY_PATH_UNAVAILABLE }
|
|
||||||
reply.make(packet.txn_id, packet.unit_id, fcode, data)
|
|
||||||
return reply
|
|
||||||
end
|
|
||||||
|
|
||||||
return modbus
|
return modbus
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ local LED = require("graphics.elements.indicators.LED")
|
|||||||
local LEDPair = require("graphics.elements.indicators.LEDPair")
|
local LEDPair = require("graphics.elements.indicators.LEDPair")
|
||||||
local RGBLED = require("graphics.elements.indicators.RGBLED")
|
local RGBLED = require("graphics.elements.indicators.RGBLED")
|
||||||
|
|
||||||
local LINK_STATE = types.PANEL_LINK_STATE
|
local LINK_STATE = types.PANEL_LINK_STATE
|
||||||
|
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
|
||||||
|
|
||||||
local ALIGN = core.ALIGN
|
local ALIGN = core.ALIGN
|
||||||
|
|
||||||
@@ -35,13 +36,15 @@ local UNIT_TYPE_LABELS = { "UNKNOWN", "REDSTONE", "BOILER", "TURBINE", "DYNAMIC
|
|||||||
local function init(panel, units)
|
local function init(panel, units)
|
||||||
local disabled_fg = style.fp.disabled_fg
|
local disabled_fg = style.fp.disabled_fg
|
||||||
|
|
||||||
|
local term_w, term_h = term.getSize()
|
||||||
|
|
||||||
TextBox{parent=panel,y=1,text="RTU GATEWAY",alignment=ALIGN.CENTER,fg_bg=style.theme.header}
|
TextBox{parent=panel,y=1,text="RTU GATEWAY",alignment=ALIGN.CENTER,fg_bg=style.theme.header}
|
||||||
|
|
||||||
--
|
--
|
||||||
-- system indicators
|
-- system indicators
|
||||||
--
|
--
|
||||||
|
|
||||||
local system = Div{parent=panel,width=14,height=18,x=2,y=3}
|
local system = Div{parent=panel,width=14,height=term_h-5,x=2,y=3}
|
||||||
|
|
||||||
local on = LED{parent=system,label="STATUS",colors=cpair(colors.green,colors.red)}
|
local on = LED{parent=system,label="STATUS",colors=cpair(colors.green,colors.red)}
|
||||||
local heartbeat = LED{parent=system,label="HEARTBEAT",colors=ind_grn}
|
local heartbeat = LED{parent=system,label="HEARTBEAT",colors=ind_grn}
|
||||||
@@ -53,7 +56,7 @@ local function init(panel, units)
|
|||||||
local modem = LED{parent=system,label="MODEM",colors=ind_grn}
|
local modem = LED{parent=system,label="MODEM",colors=ind_grn}
|
||||||
|
|
||||||
if not style.colorblind then
|
if not style.colorblind then
|
||||||
local network = RGBLED{parent=system,label="NETWORK",colors={colors.green,colors.red,colors.orange,colors.yellow,style.ind_bkg}}
|
local network = RGBLED{parent=system,label="NETWORK",colors={colors.green,colors.red,colors.yellow,colors.orange,style.ind_bkg}}
|
||||||
network.update(types.PANEL_LINK_STATE.DISCONNECTED)
|
network.update(types.PANEL_LINK_STATE.DISCONNECTED)
|
||||||
network.register(databus.ps, "link_state", network.update)
|
network.register(databus.ps, "link_state", network.update)
|
||||||
else
|
else
|
||||||
@@ -100,17 +103,17 @@ local function init(panel, units)
|
|||||||
local comp_id = util.sprintf("(%d)", os.getComputerID())
|
local comp_id = util.sprintf("(%d)", os.getComputerID())
|
||||||
TextBox{parent=system,x=9,y=4,width=6,text=comp_id,fg_bg=disabled_fg}
|
TextBox{parent=system,x=9,y=4,width=6,text=comp_id,fg_bg=disabled_fg}
|
||||||
|
|
||||||
TextBox{parent=system,x=1,y=14,text="SPEAKERS",width=8,fg_bg=style.fp.text_fg}
|
TextBox{parent=system,y=term_h-5,text="SPEAKERS",width=8,fg_bg=style.fp.text_fg}
|
||||||
local speaker_count = DataIndicator{parent=system,x=10,y=14,label="",format="%3d",value=0,width=3,fg_bg=style.theme.field_box}
|
local speaker_count = DataIndicator{parent=system,x=10,y=term_h-5,label="",format="%3d",value=0,width=3,fg_bg=style.theme.field_box}
|
||||||
speaker_count.register(databus.ps, "speaker_count", speaker_count.update)
|
speaker_count.register(databus.ps, "speaker_count", speaker_count.update)
|
||||||
|
|
||||||
--
|
--
|
||||||
-- about label
|
-- about label
|
||||||
--
|
--
|
||||||
|
|
||||||
local about = Div{parent=panel,width=15,height=3,x=1,y=18,fg_bg=disabled_fg}
|
local about = Div{parent=panel,width=15,height=2,y=term_h-1,fg_bg=disabled_fg}
|
||||||
local fw_v = TextBox{parent=about,x=1,y=1,text="FW: v00.00.00"}
|
local fw_v = TextBox{parent=about,text="FW: v00.00.00"}
|
||||||
local comms_v = TextBox{parent=about,x=1,y=2,text="NT: v00.00.00"}
|
local comms_v = TextBox{parent=about,text="NT: v00.00.00"}
|
||||||
|
|
||||||
fw_v.register(databus.ps, "version", function (version) fw_v.set_value(util.c("FW: ", version)) end)
|
fw_v.register(databus.ps, "version", function (version) fw_v.set_value(util.c("FW: ", version)) end)
|
||||||
comms_v.register(databus.ps, "comms_version", function (version) comms_v.set_value(util.c("NT: v", version)) end)
|
comms_v.register(databus.ps, "comms_version", function (version) comms_v.set_value(util.c("NT: v", version)) end)
|
||||||
@@ -119,38 +122,53 @@ local function init(panel, units)
|
|||||||
-- unit status list
|
-- unit status list
|
||||||
--
|
--
|
||||||
|
|
||||||
local threads = Div{parent=panel,width=8,height=18,x=17,y=3}
|
local threads = Div{parent=panel,width=8,height=term_h-3,x=17,y=3}
|
||||||
|
|
||||||
-- display up to 16 units
|
-- display as many units as we can with 1 line of padding above and below
|
||||||
local list_length = math.min(#units, 16)
|
local list_length = math.min(#units, term_h - 3)
|
||||||
|
|
||||||
-- show routine statuses
|
-- show routine statuses
|
||||||
for i = 1, list_length do
|
for i = 1, list_length do
|
||||||
TextBox{parent=threads,x=1,y=i,text=util.sprintf("%02d",i)}
|
TextBox{parent=threads,x=1,y=i,text=util.sprintf("%02d",i)}
|
||||||
local rt_unit = LED{parent=threads,x=4,y=i,label="RT",colors=ind_grn}
|
local rt_unit = LED{parent=threads,x=4,y=i,label="RT",colors=util.trinary(units[i].type~=RTU_UNIT_TYPE.REDSTONE,ind_grn,cpair(style.ind_bkg,style.ind_bkg))}
|
||||||
rt_unit.register(databus.ps, "routine__unit_" .. i, rt_unit.update)
|
rt_unit.register(databus.ps, "routine__unit_" .. i, rt_unit.update)
|
||||||
end
|
end
|
||||||
|
|
||||||
local unit_hw_statuses = Div{parent=panel,height=18,x=25,y=3}
|
local unit_hw_statuses = Div{parent=panel,height=term_h-3,x=25,y=3}
|
||||||
|
|
||||||
|
local relay_counter = 0
|
||||||
|
|
||||||
-- show hardware statuses
|
-- show hardware statuses
|
||||||
for i = 1, list_length do
|
for i = 1, list_length do
|
||||||
local unit = units[i]
|
local unit = units[i]
|
||||||
|
|
||||||
|
local is_rs = unit.type == RTU_UNIT_TYPE.REDSTONE
|
||||||
|
|
||||||
-- hardware status
|
-- hardware status
|
||||||
local unit_hw = RGBLED{parent=unit_hw_statuses,y=i,label="",colors={colors.red,colors.orange,colors.yellow,colors.green}}
|
local unit_hw = RGBLED{parent=unit_hw_statuses,y=i,label="",colors={colors.red,colors.orange,colors.yellow,colors.green}}
|
||||||
|
|
||||||
unit_hw.register(databus.ps, "unit_hw_" .. i, unit_hw.update)
|
unit_hw.register(databus.ps, "unit_hw_" .. i, unit_hw.update)
|
||||||
|
|
||||||
-- unit name identifier (type + index)
|
-- unit name identifier (type + index)
|
||||||
local function get_name(t) return util.c(UNIT_TYPE_LABELS[t + 1], " ", util.trinary(util.is_int(unit.index), unit.index, "")) end
|
local function get_name()
|
||||||
local name_box = TextBox{parent=unit_hw_statuses,y=i,x=3,text=get_name(unit.type),width=15}
|
if is_rs then
|
||||||
|
local is_local = unit.name == "redstone_local"
|
||||||
|
relay_counter = relay_counter + util.trinary(is_local, 0, 1)
|
||||||
|
return util.c("REDSTONE", util.trinary(is_local, "", " RELAY " .. relay_counter))
|
||||||
|
else
|
||||||
|
return util.c(UNIT_TYPE_LABELS[unit.type + 1], " ", util.trinary(util.is_int(unit.index), unit.index, ""))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
name_box.register(databus.ps, "unit_type_" .. i, function (t) name_box.set_value(get_name(t)) end)
|
local name_box = TextBox{parent=unit_hw_statuses,y=i,x=3,text=get_name(),width=util.trinary(is_rs,24,15)}
|
||||||
|
|
||||||
|
name_box.register(databus.ps, "unit_type_" .. i, function () name_box.set_value(get_name()) end)
|
||||||
|
|
||||||
-- assignment (unit # or facility)
|
-- assignment (unit # or facility)
|
||||||
local for_unit = util.trinary(unit.reactor == 0, "\x1a FACIL ", "\x1a UNIT " .. unit.reactor)
|
if unit.reactor then
|
||||||
TextBox{parent=unit_hw_statuses,y=i,x=19,text=for_unit,fg_bg=disabled_fg}
|
local for_unit = util.trinary(unit.reactor == 0, "\x1a FACIL ", "\x1a UNIT " .. unit.reactor)
|
||||||
|
TextBox{parent=unit_hw_statuses,y=i,x=term_w-32,text=for_unit,fg_bg=disabled_fg}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
74
rtu/rtu.lua
74
rtu/rtu.lua
@@ -19,6 +19,7 @@ local MGMT_TYPE = comms.MGMT_TYPE
|
|||||||
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
|
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
|
||||||
|
|
||||||
---@type rtu_config
|
---@type rtu_config
|
||||||
|
---@diagnostic disable-next-line: missing-fields
|
||||||
local config = {}
|
local config = {}
|
||||||
|
|
||||||
rtu.config = config
|
rtu.config = config
|
||||||
@@ -45,36 +46,42 @@ function rtu.load_config()
|
|||||||
config.FrontPanelTheme = settings.get("FrontPanelTheme")
|
config.FrontPanelTheme = settings.get("FrontPanelTheme")
|
||||||
config.ColorMode = settings.get("ColorMode")
|
config.ColorMode = settings.get("ColorMode")
|
||||||
|
|
||||||
|
return rtu.validate_config(config)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- validate an RTU gateway configuration
|
||||||
|
---@param cfg rtu_config
|
||||||
|
function rtu.validate_config(cfg)
|
||||||
local cfv = util.new_validator()
|
local cfv = util.new_validator()
|
||||||
|
|
||||||
cfv.assert_type_num(config.SpeakerVolume)
|
cfv.assert_type_num(cfg.SpeakerVolume)
|
||||||
cfv.assert_range(config.SpeakerVolume, 0, 3)
|
cfv.assert_range(cfg.SpeakerVolume, 0, 3)
|
||||||
|
|
||||||
cfv.assert_channel(config.SVR_Channel)
|
cfv.assert_channel(cfg.SVR_Channel)
|
||||||
cfv.assert_channel(config.RTU_Channel)
|
cfv.assert_channel(cfg.RTU_Channel)
|
||||||
cfv.assert_type_num(config.ConnTimeout)
|
cfv.assert_type_num(cfg.ConnTimeout)
|
||||||
cfv.assert_min(config.ConnTimeout, 2)
|
cfv.assert_min(cfg.ConnTimeout, 2)
|
||||||
cfv.assert_type_num(config.TrustedRange)
|
cfv.assert_type_num(cfg.TrustedRange)
|
||||||
cfv.assert_min(config.TrustedRange, 0)
|
cfv.assert_min(cfg.TrustedRange, 0)
|
||||||
cfv.assert_type_str(config.AuthKey)
|
cfv.assert_type_str(cfg.AuthKey)
|
||||||
|
|
||||||
if type(config.AuthKey) == "string" then
|
if type(cfg.AuthKey) == "string" then
|
||||||
local len = string.len(config.AuthKey)
|
local len = string.len(cfg.AuthKey)
|
||||||
cfv.assert(len == 0 or len >= 8)
|
cfv.assert(len == 0 or len >= 8)
|
||||||
end
|
end
|
||||||
|
|
||||||
cfv.assert_type_int(config.LogMode)
|
cfv.assert_type_int(cfg.LogMode)
|
||||||
cfv.assert_range(config.LogMode, 0, 1)
|
cfv.assert_range(cfg.LogMode, 0, 1)
|
||||||
cfv.assert_type_str(config.LogPath)
|
cfv.assert_type_str(cfg.LogPath)
|
||||||
cfv.assert_type_bool(config.LogDebug)
|
cfv.assert_type_bool(cfg.LogDebug)
|
||||||
|
|
||||||
cfv.assert_type_int(config.FrontPanelTheme)
|
cfv.assert_type_int(cfg.FrontPanelTheme)
|
||||||
cfv.assert_range(config.FrontPanelTheme, 1, 2)
|
cfv.assert_range(cfg.FrontPanelTheme, 1, 2)
|
||||||
cfv.assert_type_int(config.ColorMode)
|
cfv.assert_type_int(cfg.ColorMode)
|
||||||
cfv.assert_range(config.ColorMode, 1, themes.COLOR_MODE.NUM_MODES)
|
cfv.assert_range(cfg.ColorMode, 1, themes.COLOR_MODE.NUM_MODES)
|
||||||
|
|
||||||
cfv.assert_type_table(config.Peripherals)
|
cfv.assert_type_table(cfg.Peripherals)
|
||||||
cfv.assert_type_table(config.Redstone)
|
cfv.assert_type_table(cfg.Redstone)
|
||||||
|
|
||||||
return cfv.valid()
|
return cfv.valid()
|
||||||
end
|
end
|
||||||
@@ -331,13 +338,7 @@ function rtu.comms(version, nic, conn_watchdog)
|
|||||||
local unit = units[i]
|
local unit = units[i]
|
||||||
|
|
||||||
if unit.type ~= nil then
|
if unit.type ~= nil then
|
||||||
local advert = { unit.type, unit.index, unit.reactor }
|
insert(advertisement, { unit.type, unit.index, unit.reactor or -1, unit.rs_conns })
|
||||||
|
|
||||||
if unit.type == RTU_UNIT_TYPE.REDSTONE then
|
|
||||||
insert(advert, unit.device)
|
|
||||||
end
|
|
||||||
|
|
||||||
insert(advertisement, advert)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -470,9 +471,10 @@ function rtu.comms(version, nic, conn_watchdog)
|
|||||||
local unit = units[packet.unit_id]
|
local unit = units[packet.unit_id]
|
||||||
local unit_dbg_tag = " (unit " .. packet.unit_id .. ")"
|
local unit_dbg_tag = " (unit " .. packet.unit_id .. ")"
|
||||||
|
|
||||||
if unit.name == "redstone_io" then
|
if unit.type == RTU_UNIT_TYPE.REDSTONE then
|
||||||
-- immediately execute redstone RTU requests
|
-- immediately execute redstone RTU requests
|
||||||
return_code, reply = unit.modbus_io.handle_packet(packet)
|
return_code, reply = unit.modbus_io.handle_packet(packet)
|
||||||
|
|
||||||
if not return_code then
|
if not return_code then
|
||||||
log.warning("requested MODBUS operation failed" .. unit_dbg_tag)
|
log.warning("requested MODBUS operation failed" .. unit_dbg_tag)
|
||||||
end
|
end
|
||||||
@@ -480,18 +482,16 @@ function rtu.comms(version, nic, conn_watchdog)
|
|||||||
-- check validity then pass off to unit comms thread
|
-- check validity then pass off to unit comms thread
|
||||||
return_code, reply = unit.modbus_io.check_request(packet)
|
return_code, reply = unit.modbus_io.check_request(packet)
|
||||||
if return_code then
|
if return_code then
|
||||||
-- check if there are more than 3 active transactions
|
-- check if there are more than 3 active transactions, which will be treated as busy
|
||||||
-- still queue the packet, but this may indicate a problem
|
|
||||||
if unit.pkt_queue.length() > 3 then
|
if unit.pkt_queue.length() > 3 then
|
||||||
reply = modbus.reply__srv_device_busy(packet)
|
reply = modbus.reply__srv_device_busy(packet)
|
||||||
log.debug("queueing new request with " .. unit.pkt_queue.length() ..
|
log.warning("device busy, discarding new request" .. unit_dbg_tag)
|
||||||
" transactions already in the queue" .. unit_dbg_tag)
|
else
|
||||||
|
-- queue the command if not busy
|
||||||
|
unit.pkt_queue.push_packet(packet)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- always queue the command even if busy
|
|
||||||
unit.pkt_queue.push_packet(packet)
|
|
||||||
else
|
else
|
||||||
log.warning("cannot perform requested MODBUS operation" .. unit_dbg_tag)
|
log.warning("requested MODBUS operation failed" .. unit_dbg_tag)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
|
|||||||
246
rtu/startup.lua
246
rtu/startup.lua
@@ -31,7 +31,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.10.14"
|
local RTU_VERSION = "v1.12.2"
|
||||||
|
|
||||||
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
|
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
|
||||||
local RTU_HW_STATE = databus.RTU_HW_STATE
|
local RTU_HW_STATE = databus.RTU_HW_STATE
|
||||||
@@ -140,32 +140,36 @@ local function main()
|
|||||||
local rtu_redstone = config.Redstone
|
local rtu_redstone = config.Redstone
|
||||||
local rtu_devices = config.Peripherals
|
local rtu_devices = config.Peripherals
|
||||||
|
|
||||||
|
-- get a string representation of a port interface
|
||||||
|
---@param entry rtu_rs_definition
|
||||||
|
---@return string
|
||||||
|
local function entry_iface_name(entry)
|
||||||
|
return util.trinary(entry.color ~= nil, util.c(entry.side, "/", rsio.color_name(entry.color)), entry.side)
|
||||||
|
end
|
||||||
|
|
||||||
-- configure RTU gateway based on settings file definitions
|
-- configure RTU gateway based on settings file definitions
|
||||||
local function sys_config()
|
local function sys_config()
|
||||||
-- redstone interfaces
|
--#region Redstone Interfaces
|
||||||
local rs_rtus = {} ---@type { rtu: rtu_rs_device, capabilities: IO_PORT[] }[]
|
|
||||||
|
local rs_rtus = {} ---@type { name: string, hw_state: RTU_HW_STATE, rtu: rtu_rs_device, phy: table, banks: rtu_rs_definition[][] }[]
|
||||||
|
local all_conns = { [0] = {}, {}, {}, {}, {} }
|
||||||
|
|
||||||
-- go through redstone definitions list
|
-- go through redstone definitions list
|
||||||
for entry_idx = 1, #rtu_redstone do
|
for entry_idx = 1, #rtu_redstone do
|
||||||
local entry = rtu_redstone[entry_idx]
|
local entry = rtu_redstone[entry_idx]
|
||||||
|
|
||||||
local assignment
|
local assignment
|
||||||
local for_reactor = entry.unit
|
local for_reactor = entry.unit
|
||||||
local iface_name = util.trinary(entry.color ~= nil, util.c(entry.side, "/", rsio.color_name(entry.color)), entry.side)
|
local phy = entry.relay or 0
|
||||||
|
local phy_name = entry.relay or "local"
|
||||||
|
local iface_name = entry_iface_name(entry)
|
||||||
|
|
||||||
if util.is_int(entry.unit) and entry.unit > 0 and entry.unit < 5 then
|
if util.is_int(entry.unit) and entry.unit > 0 and entry.unit < 5 then
|
||||||
---@cast for_reactor integer
|
---@cast for_reactor integer
|
||||||
assignment = "reactor unit " .. entry.unit
|
assignment = "reactor unit " .. entry.unit
|
||||||
if rs_rtus[for_reactor] == nil then
|
|
||||||
log.debug(util.c("sys_config> allocated redstone RTU for reactor unit ", entry.unit))
|
|
||||||
rs_rtus[for_reactor] = { rtu = redstone_rtu.new(), capabilities = {} }
|
|
||||||
end
|
|
||||||
elseif entry.unit == nil then
|
elseif entry.unit == nil then
|
||||||
assignment = "facility"
|
assignment = "facility"
|
||||||
for_reactor = 0
|
for_reactor = 0
|
||||||
if rs_rtus[for_reactor] == nil then
|
|
||||||
log.debug(util.c("sys_config> allocated redstone RTU for the facility"))
|
|
||||||
rs_rtus[for_reactor] = { rtu = redstone_rtu.new(), capabilities = {} }
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
local message = util.c("sys_config> invalid unit assignment at block index #", entry_idx)
|
local message = util.c("sys_config> invalid unit assignment at block index #", entry_idx)
|
||||||
println(message)
|
println(message)
|
||||||
@@ -173,14 +177,44 @@ local function main()
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- create the appropriate RTU if it doesn't exist and check relay name validity
|
||||||
|
if entry.relay then
|
||||||
|
if type(entry.relay) ~= "string" then
|
||||||
|
local message = util.c("sys_config> invalid redstone relay '", entry.relay, '"')
|
||||||
|
println(message)
|
||||||
|
log.fatal(message)
|
||||||
|
return false
|
||||||
|
elseif not rs_rtus[entry.relay] then
|
||||||
|
log.debug(util.c("sys_config> allocated relay redstone RTU on interface ", entry.relay))
|
||||||
|
|
||||||
|
local hw_state = RTU_HW_STATE.OK
|
||||||
|
local relay = ppm.get_periph(entry.relay)
|
||||||
|
|
||||||
|
if not relay then
|
||||||
|
hw_state = RTU_HW_STATE.OFFLINE
|
||||||
|
log.warning(util.c("sys_config> redstone relay ", entry.relay, " is not connected"))
|
||||||
|
local _, v_device = ppm.mount_virtual()
|
||||||
|
relay = v_device
|
||||||
|
elseif ppm.get_type(entry.relay) ~= "redstone_relay" then
|
||||||
|
hw_state = RTU_HW_STATE.FAULTED
|
||||||
|
log.warning(util.c("sys_config> redstone relay ", entry.relay, " is not a redstone relay"))
|
||||||
|
end
|
||||||
|
|
||||||
|
rs_rtus[entry.relay] = { name = entry.relay, hw_state = hw_state, rtu = redstone_rtu.new(relay), phy = relay, banks = { [0] = {}, {}, {}, {}, {} } }
|
||||||
|
end
|
||||||
|
elseif rs_rtus[0] == nil then
|
||||||
|
log.debug(util.c("sys_config> allocated local redstone RTU"))
|
||||||
|
rs_rtus[0] = { name = "redstone_local", hw_state = RTU_HW_STATE.OK, rtu = redstone_rtu.new(), phy = rs, banks = { [0] = {}, {}, {}, {}, {} } }
|
||||||
|
end
|
||||||
|
|
||||||
-- verify configuration
|
-- verify configuration
|
||||||
local valid = false
|
local valid = false
|
||||||
if rsio.is_valid_port(entry.port) and rsio.is_valid_side(entry.side) then
|
if rsio.is_valid_port(entry.port) and rsio.is_valid_side(entry.side) then
|
||||||
valid = util.trinary(entry.color == nil, true, rsio.is_color(entry.color))
|
valid = util.trinary(entry.color == nil, true, rsio.is_color(entry.color))
|
||||||
end
|
end
|
||||||
|
|
||||||
local rs_rtu = rs_rtus[for_reactor].rtu
|
local bank = rs_rtus[phy].banks[for_reactor]
|
||||||
local capabilities = rs_rtus[for_reactor].capabilities
|
local conns = all_conns[for_reactor]
|
||||||
|
|
||||||
if not valid then
|
if not valid then
|
||||||
local message = util.c("sys_config> invalid redstone definition at block index #", entry_idx)
|
local message = util.c("sys_config> invalid redstone definition at block index #", entry_idx)
|
||||||
@@ -192,73 +226,105 @@ local function main()
|
|||||||
local mode = rsio.get_io_mode(entry.port)
|
local mode = rsio.get_io_mode(entry.port)
|
||||||
if mode == rsio.IO_MODE.DIGITAL_IN then
|
if mode == rsio.IO_MODE.DIGITAL_IN then
|
||||||
-- can't have duplicate inputs
|
-- can't have duplicate inputs
|
||||||
if util.table_contains(capabilities, entry.port) then
|
if util.table_contains(conns, entry.port) then
|
||||||
local message = util.c("sys_config> skipping duplicate input for port ", rsio.to_string(entry.port), " on side ", iface_name)
|
local message = util.c("sys_config> skipping duplicate input for port ", rsio.to_string(entry.port), " on side ", iface_name, " @ ", phy_name)
|
||||||
println(message)
|
println(message)
|
||||||
log.warning(message)
|
log.warning(message)
|
||||||
else
|
else
|
||||||
rs_rtu.link_di(entry.side, entry.color)
|
table.insert(bank, entry)
|
||||||
end
|
end
|
||||||
elseif mode == rsio.IO_MODE.DIGITAL_OUT then
|
|
||||||
rs_rtu.link_do(entry.side, entry.color)
|
|
||||||
elseif mode == rsio.IO_MODE.ANALOG_IN then
|
elseif mode == rsio.IO_MODE.ANALOG_IN then
|
||||||
-- can't have duplicate inputs
|
-- can't have duplicate inputs
|
||||||
if util.table_contains(capabilities, entry.port) then
|
if util.table_contains(conns, entry.port) then
|
||||||
local message = util.c("sys_config> skipping duplicate input for port ", rsio.to_string(entry.port), " on side ", iface_name)
|
local message = util.c("sys_config> skipping duplicate input for port ", rsio.to_string(entry.port), " on side ", iface_name, " @ ", phy_name)
|
||||||
println(message)
|
println(message)
|
||||||
log.warning(message)
|
log.warning(message)
|
||||||
else
|
else
|
||||||
rs_rtu.link_ai(entry.side)
|
table.insert(bank, entry)
|
||||||
end
|
end
|
||||||
elseif mode == rsio.IO_MODE.ANALOG_OUT then
|
elseif (mode == rsio.IO_MODE.DIGITAL_OUT) or (mode == rsio.IO_MODE.ANALOG_OUT) then
|
||||||
rs_rtu.link_ao(entry.side)
|
table.insert(bank, entry)
|
||||||
else
|
else
|
||||||
-- should be unreachable code, we already validated ports
|
-- should be unreachable code, we already validated ports
|
||||||
log.error("sys_config> fell through if chain attempting to identify IO mode at block index #" .. entry_idx, true)
|
log.fatal("sys_config> failed to identify IO mode at block index #" .. entry_idx)
|
||||||
println("sys_config> encountered a software error, check logs")
|
println("sys_config> encountered a software error, check logs")
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
table.insert(capabilities, entry.port)
|
table.insert(conns, entry.port)
|
||||||
|
|
||||||
log.debug(util.c("sys_config> linked redstone ", #capabilities, ": ", rsio.to_string(entry.port), " (", iface_name, ") for ", assignment))
|
log.debug(util.c("sys_config> banked redstone ", #conns, ": ", rsio.to_string(entry.port), " (", iface_name, " @ ", phy_name, ") for ", assignment))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- create unit entries for redstone RTUs
|
-- create unit entries for redstone RTUs
|
||||||
for for_reactor, def in pairs(rs_rtus) do
|
for _, def in pairs(rs_rtus) do
|
||||||
---@class rtu_registry_entry
|
local rtu_conns = { [0] = {}, {}, {}, {}, {} }
|
||||||
|
|
||||||
|
-- connect the IO banks
|
||||||
|
for for_reactor = 0, #def.banks do
|
||||||
|
local bank = def.banks[for_reactor]
|
||||||
|
local conns = rtu_conns[for_reactor]
|
||||||
|
local assign = util.trinary(for_reactor > 0, "reactor unit " .. for_reactor, "the facility")
|
||||||
|
|
||||||
|
-- link redstone to the RTU
|
||||||
|
for i = 1, #bank do
|
||||||
|
local conn = bank[i]
|
||||||
|
local phy_name = conn.relay or "local"
|
||||||
|
|
||||||
|
local mode = rsio.get_io_mode(conn.port)
|
||||||
|
if mode == rsio.IO_MODE.DIGITAL_IN then
|
||||||
|
def.rtu.link_di(conn.side, conn.color, conn.invert)
|
||||||
|
elseif mode == rsio.IO_MODE.DIGITAL_OUT then
|
||||||
|
def.rtu.link_do(conn.side, conn.color, conn.invert)
|
||||||
|
elseif mode == rsio.IO_MODE.ANALOG_IN then
|
||||||
|
def.rtu.link_ai(conn.side)
|
||||||
|
elseif mode == rsio.IO_MODE.ANALOG_OUT then
|
||||||
|
def.rtu.link_ao(conn.side)
|
||||||
|
else
|
||||||
|
log.fatal(util.c("sys_config> failed to identify IO mode of ", rsio.to_string(conn.port), " (", entry_iface_name(conn), " @ ", phy_name, ") for ", assign))
|
||||||
|
println("sys_config> encountered a software error, check logs")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(conns, conn.port)
|
||||||
|
|
||||||
|
log.debug(util.c("sys_config> linked redstone ", for_reactor, ".", #conns, ": ", rsio.to_string(conn.port), " (", entry_iface_name(conn), ")", " @ ", phy_name, ") for ", assign))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@type rtu_registry_entry
|
||||||
local unit = {
|
local unit = {
|
||||||
uid = 0, ---@type integer
|
uid = 0,
|
||||||
name = "redstone_io", ---@type string
|
name = def.name,
|
||||||
type = RTU_UNIT_TYPE.REDSTONE, ---@type RTU_UNIT_TYPE
|
type = RTU_UNIT_TYPE.REDSTONE,
|
||||||
index = false, ---@type integer|false
|
index = false,
|
||||||
reactor = for_reactor, ---@type integer
|
reactor = nil,
|
||||||
device = def.capabilities, ---@type IO_PORT[] use device field for redstone ports
|
device = def.phy,
|
||||||
is_multiblock = false, ---@type boolean
|
rs_conns = rtu_conns,
|
||||||
formed = nil, ---@type boolean|nil
|
is_multiblock = false,
|
||||||
hw_state = RTU_HW_STATE.OK, ---@type RTU_HW_STATE
|
formed = nil,
|
||||||
rtu = def.rtu, ---@type rtu_device|rtu_rs_device
|
hw_state = def.hw_state,
|
||||||
|
rtu = def.rtu,
|
||||||
modbus_io = modbus.new(def.rtu, false),
|
modbus_io = modbus.new(def.rtu, false),
|
||||||
pkt_queue = nil, ---@type mqueue|nil
|
pkt_queue = nil,
|
||||||
thread = nil ---@type parallel_thread|nil
|
thread = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
table.insert(units, unit)
|
table.insert(units, unit)
|
||||||
|
|
||||||
local for_message = "facility"
|
local type = util.trinary(def.phy == rs, "redstone", "redstone_relay")
|
||||||
if util.is_int(for_reactor) then
|
|
||||||
for_message = util.c("reactor unit ", for_reactor)
|
|
||||||
end
|
|
||||||
|
|
||||||
log.info(util.c("sys_config> initialized RTU unit #", #units, ": redstone_io (redstone) [1] for ", for_message))
|
log.info(util.c("sys_config> initialized RTU unit #", #units, ": ", unit.name, " (", type, ")"))
|
||||||
|
|
||||||
unit.uid = #units
|
unit.uid = #units
|
||||||
|
|
||||||
databus.tx_unit_hw_status(unit.uid, unit.hw_state)
|
databus.tx_unit_hw_status(unit.uid, unit.hw_state)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- mounted peripherals
|
--#endregion
|
||||||
|
--#region Mounted Peripherals
|
||||||
|
|
||||||
for i = 1, #rtu_devices do
|
for i = 1, #rtu_devices do
|
||||||
local entry = rtu_devices[i] ---@type rtu_peri_definition
|
local entry = rtu_devices[i] ---@type rtu_peri_definition
|
||||||
local name = entry.name
|
local name = entry.name
|
||||||
@@ -338,9 +404,8 @@ local function main()
|
|||||||
formed = device.isFormed()
|
formed = device.isFormed()
|
||||||
|
|
||||||
if formed == ppm.ACCESS_FAULT then
|
if formed == ppm.ACCESS_FAULT then
|
||||||
println_ts(util.c("sys_config> failed to check if '", name, "' is formed"))
|
println_ts(util.c("sys_config> failed to check if '", name, "' is formed"))
|
||||||
log.fatal(util.c("sys_config> failed to check if '", name, "' is a formed boiler multiblock"))
|
log.warning(util.c("sys_config> failed to check if '", name, "' is a formed boiler multiblock"))
|
||||||
return false
|
|
||||||
end
|
end
|
||||||
elseif type == "turbineValve" then
|
elseif type == "turbineValve" then
|
||||||
-- turbine multiblock
|
-- turbine multiblock
|
||||||
@@ -353,9 +418,8 @@ local function main()
|
|||||||
formed = device.isFormed()
|
formed = device.isFormed()
|
||||||
|
|
||||||
if formed == ppm.ACCESS_FAULT then
|
if formed == ppm.ACCESS_FAULT then
|
||||||
println_ts(util.c("sys_config> failed to check if '", name, "' is formed"))
|
println_ts(util.c("sys_config> failed to check if '", name, "' is formed"))
|
||||||
log.fatal(util.c("sys_config> failed to check if '", name, "' is a formed turbine multiblock"))
|
log.warning(util.c("sys_config> failed to check if '", name, "' is a formed turbine multiblock"))
|
||||||
return false
|
|
||||||
end
|
end
|
||||||
elseif type == "dynamicValve" then
|
elseif type == "dynamicValve" then
|
||||||
-- dynamic tank multiblock
|
-- dynamic tank multiblock
|
||||||
@@ -373,9 +437,8 @@ local function main()
|
|||||||
formed = device.isFormed()
|
formed = device.isFormed()
|
||||||
|
|
||||||
if formed == ppm.ACCESS_FAULT then
|
if formed == ppm.ACCESS_FAULT then
|
||||||
println_ts(util.c("sys_config> failed to check if '", name, "' is formed"))
|
println_ts(util.c("sys_config> failed to check if '", name, "' is formed"))
|
||||||
log.fatal(util.c("sys_config> failed to check if '", name, "' is a formed dynamic tank multiblock"))
|
log.warning(util.c("sys_config> failed to check if '", name, "' is a formed dynamic tank multiblock"))
|
||||||
return false
|
|
||||||
end
|
end
|
||||||
elseif type == "inductionPort" then
|
elseif type == "inductionPort" then
|
||||||
-- induction matrix multiblock
|
-- induction matrix multiblock
|
||||||
@@ -387,9 +450,8 @@ local function main()
|
|||||||
formed = device.isFormed()
|
formed = device.isFormed()
|
||||||
|
|
||||||
if formed == ppm.ACCESS_FAULT then
|
if formed == ppm.ACCESS_FAULT then
|
||||||
println_ts(util.c("sys_config> failed to check if '", name, "' is formed"))
|
println_ts(util.c("sys_config> failed to check if '", name, "' is formed"))
|
||||||
log.fatal(util.c("sys_config> failed to check if '", name, "' is a formed induction matrix multiblock"))
|
log.warning(util.c("sys_config> failed to check if '", name, "' is a formed induction matrix multiblock"))
|
||||||
return false
|
|
||||||
end
|
end
|
||||||
elseif type == "spsPort" then
|
elseif type == "spsPort" then
|
||||||
-- SPS multiblock
|
-- SPS multiblock
|
||||||
@@ -401,9 +463,8 @@ local function main()
|
|||||||
formed = device.isFormed()
|
formed = device.isFormed()
|
||||||
|
|
||||||
if formed == ppm.ACCESS_FAULT then
|
if formed == ppm.ACCESS_FAULT then
|
||||||
println_ts(util.c("sys_config> failed to check if '", name, "' is formed"))
|
println_ts(util.c("sys_config> failed to check if '", name, "' is formed"))
|
||||||
log.fatal(util.c("sys_config> failed to check if '", name, "' is a formed SPS multiblock"))
|
log.warning(util.c("sys_config> failed to check if '", name, "' is a formed SPS multiblock"))
|
||||||
return false
|
|
||||||
end
|
end
|
||||||
elseif type == "solarNeutronActivator" then
|
elseif type == "solarNeutronActivator" then
|
||||||
-- SNA
|
-- SNA
|
||||||
@@ -411,7 +472,7 @@ local function main()
|
|||||||
|
|
||||||
rtu_type = RTU_UNIT_TYPE.SNA
|
rtu_type = RTU_UNIT_TYPE.SNA
|
||||||
rtu_iface, faulted = sna_rtu.new(device)
|
rtu_iface, faulted = sna_rtu.new(device)
|
||||||
elseif type == "environmentDetector" then
|
elseif type == "environmentDetector" or type == "environment_detector" then
|
||||||
-- advanced peripherals environment detector
|
-- advanced peripherals environment detector
|
||||||
if not validate_index(1) then return false end
|
if not validate_index(1) then return false end
|
||||||
if not validate_assign(entry.unit == nil) then return false end
|
if not validate_assign(entry.unit == nil) then return false end
|
||||||
@@ -431,7 +492,9 @@ local function main()
|
|||||||
|
|
||||||
if is_multiblock then
|
if is_multiblock then
|
||||||
if not formed then
|
if not formed then
|
||||||
log.info(util.c("sys_config> device '", name, "' is not formed"))
|
if formed == false then
|
||||||
|
log.info(util.c("sys_config> device '", name, "' is not formed"))
|
||||||
|
else formed = false end
|
||||||
elseif faulted then
|
elseif faulted then
|
||||||
-- sometimes there is a race condition on server boot where it reports formed, but
|
-- sometimes there is a race condition on server boot where it reports formed, but
|
||||||
-- the other functions are not yet defined (that's the theory at least). mark as unformed to attempt connection later
|
-- the other functions are not yet defined (that's the theory at least). mark as unformed to attempt connection later
|
||||||
@@ -442,26 +505,27 @@ local function main()
|
|||||||
|
|
||||||
---@class rtu_registry_entry
|
---@class rtu_registry_entry
|
||||||
local rtu_unit = {
|
local rtu_unit = {
|
||||||
uid = 0, ---@type integer
|
uid = 0, ---@type integer RTU unit ID
|
||||||
name = name, ---@type string
|
name = name, ---@type string unit name
|
||||||
type = rtu_type, ---@type RTU_UNIT_TYPE
|
type = rtu_type, ---@type RTU_UNIT_TYPE unit type
|
||||||
index = index or false, ---@type integer|false
|
index = index or false, ---@type integer|false device index
|
||||||
reactor = for_reactor, ---@type integer
|
reactor = for_reactor, ---@type integer|nil unit/facility assignment
|
||||||
device = device, ---@type table peripheral reference
|
device = device, ---@type table peripheral reference
|
||||||
is_multiblock = is_multiblock, ---@type boolean
|
rs_conns = nil, ---@type IO_PORT[][]|nil available redstone connections
|
||||||
formed = formed, ---@type boolean|nil
|
is_multiblock = is_multiblock, ---@type boolean if this is for a multiblock peripheral
|
||||||
hw_state = RTU_HW_STATE.OFFLINE, ---@type RTU_HW_STATE
|
formed = formed, ---@type boolean|nil if this peripheral is currently formed
|
||||||
rtu = rtu_iface, ---@type rtu_device|rtu_rs_device
|
hw_state = RTU_HW_STATE.OFFLINE, ---@type RTU_HW_STATE hardware device status
|
||||||
modbus_io = modbus.new(rtu_iface, true),
|
rtu = rtu_iface, ---@type rtu_device|rtu_rs_device RTU hardware interface
|
||||||
pkt_queue = mqueue.new(), ---@type mqueue|nil
|
modbus_io = modbus.new(rtu_iface, true), ---@type modbus MODBUS interface
|
||||||
thread = nil ---@type parallel_thread|nil
|
pkt_queue = mqueue.new(), ---@type mqueue|nil packet queue
|
||||||
|
thread = nil ---@type parallel_thread|nil associated RTU thread
|
||||||
}
|
}
|
||||||
|
|
||||||
rtu_unit.thread = threads.thread__unit_comms(__shared_memory, rtu_unit)
|
rtu_unit.thread = threads.thread__unit_comms(__shared_memory, rtu_unit)
|
||||||
|
|
||||||
table.insert(units, rtu_unit)
|
table.insert(units, rtu_unit)
|
||||||
|
|
||||||
local for_message = "facility"
|
local for_message = "the facility"
|
||||||
if for_reactor > 0 then
|
if for_reactor > 0 then
|
||||||
for_message = util.c("reactor ", for_reactor)
|
for_message = util.c("reactor ", for_reactor)
|
||||||
end
|
end
|
||||||
@@ -488,6 +552,8 @@ local function main()
|
|||||||
databus.tx_unit_hw_status(rtu_unit.uid, rtu_unit.hw_state)
|
databus.tx_unit_hw_status(rtu_unit.uid, rtu_unit.hw_state)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -498,17 +564,6 @@ local function main()
|
|||||||
log.debug("boot> running sys_config()")
|
log.debug("boot> running sys_config()")
|
||||||
|
|
||||||
if sys_config() then
|
if sys_config() then
|
||||||
-- start UI
|
|
||||||
local message
|
|
||||||
rtu_state.fp_ok, message = renderer.try_start_ui(units, config.FrontPanelTheme, config.ColorMode)
|
|
||||||
|
|
||||||
if not rtu_state.fp_ok then
|
|
||||||
println_ts(util.c("UI error: ", message))
|
|
||||||
println("startup> running without front panel")
|
|
||||||
log.error(util.c("front panel GUI render failed with error ", message))
|
|
||||||
log.info("startup> running in headless mode without front panel")
|
|
||||||
end
|
|
||||||
|
|
||||||
-- check modem
|
-- check modem
|
||||||
if smem_dev.modem == nil then
|
if smem_dev.modem == nil then
|
||||||
println("startup> wireless modem not found")
|
println("startup> wireless modem not found")
|
||||||
@@ -530,6 +585,17 @@ local function main()
|
|||||||
|
|
||||||
databus.tx_hw_spkr_count(#smem_dev.sounders)
|
databus.tx_hw_spkr_count(#smem_dev.sounders)
|
||||||
|
|
||||||
|
-- start UI
|
||||||
|
local message
|
||||||
|
rtu_state.fp_ok, message = renderer.try_start_ui(units, config.FrontPanelTheme, config.ColorMode)
|
||||||
|
|
||||||
|
if not rtu_state.fp_ok then
|
||||||
|
println_ts(util.c("UI error: ", message))
|
||||||
|
println("startup> running without front panel")
|
||||||
|
log.error(util.c("front panel GUI render failed with error ", message))
|
||||||
|
log.info("startup> running in headless mode without front panel")
|
||||||
|
end
|
||||||
|
|
||||||
-- start connection watchdog
|
-- start connection watchdog
|
||||||
smem_sys.conn_watchdog = util.new_watchdog(config.ConnTimeout)
|
smem_sys.conn_watchdog = util.new_watchdog(config.ConnTimeout)
|
||||||
log.debug("startup> conn watchdog started")
|
log.debug("startup> conn watchdog started")
|
||||||
@@ -556,7 +622,7 @@ local function main()
|
|||||||
-- run threads
|
-- run threads
|
||||||
parallel.waitForAll(table.unpack(_threads))
|
parallel.waitForAll(table.unpack(_threads))
|
||||||
else
|
else
|
||||||
println("configuration failed, exiting...")
|
println("system initialization failed, exiting...")
|
||||||
end
|
end
|
||||||
|
|
||||||
renderer.close_ui()
|
renderer.close_ui()
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ local function handle_unit_mount(smem, println_ts, iface, type, device, unit)
|
|||||||
if unit.reactor < 1 or unit.reactor > 4 then fail(util.c("SNA '", unit.name, "' cannot init, not assigned to a valid unit")) end
|
if unit.reactor < 1 or unit.reactor > 4 then fail(util.c("SNA '", unit.name, "' cannot init, not assigned to a valid unit")) end
|
||||||
|
|
||||||
unit.type = RTU_UNIT_TYPE.SNA
|
unit.type = RTU_UNIT_TYPE.SNA
|
||||||
elseif type == "environmentDetector" then
|
elseif type == "environmentDetector" or type == "environment_detector" then
|
||||||
-- advanced peripherals environment detector
|
-- advanced peripherals environment detector
|
||||||
if unit.reactor < 0 or unit.reactor > 4 then fail(util.c("environment detector '", unit.name, "' cannot init, no valid assignment provided")) end
|
if unit.reactor < 0 or unit.reactor > 4 then fail(util.c("environment detector '", unit.name, "' cannot init, no valid assignment provided")) end
|
||||||
if (unit.index == false) or unit.index < 1 then fail(util.c("environment detector '", unit.name, "' cannot init, invalid index provided")) end
|
if (unit.index == false) or unit.index < 1 then fail(util.c("environment detector '", unit.name, "' cannot init, invalid index provided")) end
|
||||||
@@ -132,6 +132,8 @@ local function handle_unit_mount(smem, println_ts, iface, type, device, unit)
|
|||||||
unit.rtu, faulted = sna_rtu.new(device)
|
unit.rtu, faulted = sna_rtu.new(device)
|
||||||
elseif unit.type == RTU_UNIT_TYPE.ENV_DETECTOR then
|
elseif unit.type == RTU_UNIT_TYPE.ENV_DETECTOR then
|
||||||
unit.rtu, faulted = envd_rtu.new(device)
|
unit.rtu, faulted = envd_rtu.new(device)
|
||||||
|
elseif unit.type == RTU_UNIT_TYPE.REDSTONE then
|
||||||
|
unit.rtu.remount_phy(device)
|
||||||
else
|
else
|
||||||
unknown = true
|
unknown = true
|
||||||
log.error(util.c("failed to identify reconnected RTU unit type (", unit.name, ")"), true)
|
log.error(util.c("failed to identify reconnected RTU unit type (", unit.name, ")"), true)
|
||||||
@@ -466,6 +468,9 @@ end
|
|||||||
---@param smem rtu_shared_memory
|
---@param smem rtu_shared_memory
|
||||||
---@param unit rtu_registry_entry
|
---@param unit rtu_registry_entry
|
||||||
function threads.thread__unit_comms(smem, unit)
|
function threads.thread__unit_comms(smem, unit)
|
||||||
|
-- print a log message to the terminal as long as the UI isn't running
|
||||||
|
local function println_ts(message) if not smem.rtu_state.fp_ok then util.println_ts(message) end end
|
||||||
|
|
||||||
---@class parallel_thread
|
---@class parallel_thread
|
||||||
local public = {}
|
local public = {}
|
||||||
|
|
||||||
@@ -483,7 +488,9 @@ function threads.thread__unit_comms(smem, unit)
|
|||||||
|
|
||||||
local last_f_check = 0
|
local last_f_check = 0
|
||||||
|
|
||||||
local detail_name = util.c(types.rtu_type_to_string(unit.type), " (", unit.name, ") [", unit.index, "] for reactor ", unit.reactor)
|
local detail_name = util.c(types.rtu_type_to_string(unit.type), " (", unit.name, ") ",
|
||||||
|
util.trinary(unit.index == false, "", util.c("[", unit.index, "] ")), "for ",
|
||||||
|
util.trinary(unit.reactor == 0, "the facility", util.c("reactor ", unit.reactor)))
|
||||||
local short_name = util.c(types.rtu_type_to_string(unit.type), " (", unit.name, ")")
|
local short_name = util.c(types.rtu_type_to_string(unit.type), " (", unit.name, ")")
|
||||||
|
|
||||||
if packet_queue == nil then
|
if packet_queue == nil then
|
||||||
@@ -538,6 +545,15 @@ function threads.thread__unit_comms(smem, unit)
|
|||||||
rtu_comms.send_remounted(unit.uid)
|
rtu_comms.send_remounted(unit.uid)
|
||||||
elseif (is_formed == false) and unit.formed then
|
elseif (is_formed == false) and unit.formed then
|
||||||
log.warning(util.c(detail_name, " is no longer formed"))
|
log.warning(util.c(detail_name, " is no longer formed"))
|
||||||
|
elseif (is_formed == nil) and (unit.hw_state ~= RTU_HW_STATE.OFFLINE) then
|
||||||
|
log.error(util.c(detail_name, " failed to check if formed, attempting remount..."))
|
||||||
|
|
||||||
|
local type, dev = ppm.remount(unit.name)
|
||||||
|
if type and dev then
|
||||||
|
handle_unit_mount(smem, println_ts, unit.name, type, dev, unit)
|
||||||
|
else
|
||||||
|
log.error(util.c(detail_name, " failed to remount"))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
unit.formed = is_formed
|
unit.formed = is_formed
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ local max_distance = nil
|
|||||||
local comms = {}
|
local comms = {}
|
||||||
|
|
||||||
-- protocol/data versions (protocol/data independent changes tracked by util.lua version)
|
-- protocol/data versions (protocol/data independent changes tracked by util.lua version)
|
||||||
comms.version = "3.0.1"
|
comms.version = "3.0.7"
|
||||||
comms.api_version = "0.0.6"
|
comms.api_version = "0.0.10"
|
||||||
|
|
||||||
---@enum PROTOCOL
|
---@enum PROTOCOL
|
||||||
local PROTOCOL = {
|
local PROTOCOL = {
|
||||||
@@ -60,16 +60,20 @@ local MGMT_TYPE = {
|
|||||||
---@enum CRDN_TYPE
|
---@enum CRDN_TYPE
|
||||||
local CRDN_TYPE = {
|
local CRDN_TYPE = {
|
||||||
INITIAL_BUILDS = 0, -- initial, complete builds packet to the coordinator
|
INITIAL_BUILDS = 0, -- initial, complete builds packet to the coordinator
|
||||||
FAC_BUILDS = 1, -- facility RTU builds
|
PROCESS_READY = 1, -- process init is complete + last set of info for supervisor startup recovery
|
||||||
FAC_STATUS = 2, -- state of facility and facility devices
|
FAC_BUILDS = 2, -- facility RTU builds
|
||||||
FAC_CMD = 3, -- faility command
|
FAC_STATUS = 3, -- state of facility and facility devices
|
||||||
UNIT_BUILDS = 4, -- build of each reactor unit (reactor + RTUs)
|
FAC_CMD = 4, -- faility command
|
||||||
UNIT_STATUSES = 5, -- state of each of the reactor units
|
UNIT_BUILDS = 5, -- build of each reactor unit (reactor + RTUs)
|
||||||
UNIT_CMD = 6, -- command a reactor unit
|
UNIT_STATUSES = 6, -- state of each of the reactor units
|
||||||
API_GET_FAC = 7, -- API: get all the facility data
|
UNIT_CMD = 7, -- command a reactor unit
|
||||||
API_GET_UNIT = 8, -- API: get reactor unit data
|
API_GET_FAC = 8, -- API: get the facility general data
|
||||||
API_GET_CTRL = 9, -- API: get data used for the control app
|
API_GET_FAC_DTL = 9, -- API: get (detailed) data for the facility app
|
||||||
API_GET_PROC = 10 -- API: get data used for the process app
|
API_GET_UNIT = 10, -- API: get reactor unit data
|
||||||
|
API_GET_CTRL = 11, -- API: get data for the control app
|
||||||
|
API_GET_PROC = 12, -- API: get data for the process app
|
||||||
|
API_GET_WASTE = 13, -- API: get data for the waste app
|
||||||
|
API_GET_RAD = 14 -- API: get data for the radiation monitor app
|
||||||
}
|
}
|
||||||
|
|
||||||
---@enum ESTABLISH_ACK
|
---@enum ESTABLISH_ACK
|
||||||
|
|||||||
@@ -72,6 +72,8 @@ local rs = {}
|
|||||||
|
|
||||||
rs.IMATRIX_CHARGE_LOW = 0.05 -- activation threshold (less than) for F_MATRIX_LOW
|
rs.IMATRIX_CHARGE_LOW = 0.05 -- activation threshold (less than) for F_MATRIX_LOW
|
||||||
rs.IMATRIX_CHARGE_HIGH = 0.95 -- activation threshold (greater than) for F_MATRIX_HIGH
|
rs.IMATRIX_CHARGE_HIGH = 0.95 -- activation threshold (greater than) for F_MATRIX_HIGH
|
||||||
|
rs.AUX_COOL_ENABLE = 0.60 -- actiation threshold (less than or equal) for U_AUX_COOL
|
||||||
|
rs.AUX_COOL_DISABLE = 1.00 -- deactivation threshold (greater than or equal) for U_AUX_COOL
|
||||||
|
|
||||||
constants.RS_THRESHOLDS = rs
|
constants.RS_THRESHOLDS = rs
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
-- Crash Handler
|
-- Crash Handler
|
||||||
--
|
--
|
||||||
|
|
||||||
|
---@diagnostic disable-next-line: undefined-global
|
||||||
|
local _is_pocket_env = pocket -- luacheck: ignore pocket
|
||||||
|
|
||||||
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 util = require("scada-common.util")
|
local util = require("scada-common.util")
|
||||||
@@ -36,6 +39,74 @@ local function log_versions(log_msg)
|
|||||||
if has_lockbox then log_msg(util.c("LOCKBOX VERSION: ", lockbox.version)) end
|
if has_lockbox then log_msg(util.c("LOCKBOX VERSION: ", lockbox.version)) end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- render the standard computer crash screen
|
||||||
|
---@param exit function callback on exit button press
|
||||||
|
---@return DisplayBox display
|
||||||
|
local function draw_computer_crash(exit)
|
||||||
|
local DisplayBox = require("graphics.elements.DisplayBox")
|
||||||
|
local Div = require("graphics.elements.Div")
|
||||||
|
local Rectangle = require("graphics.elements.Rectangle")
|
||||||
|
local TextBox = require("graphics.elements.TextBox")
|
||||||
|
local PushButton = require("graphics.elements.controls.PushButton")
|
||||||
|
|
||||||
|
local display = DisplayBox{window=term.current(),fg_bg=core.cpair(colors.white,colors.lightGray)}
|
||||||
|
|
||||||
|
local warning = Div{parent=display,x=2,y=2}
|
||||||
|
TextBox{parent=warning,x=7,text="\x90\n \x90\n \x90\n \x90\n \x90",fg_bg=core.cpair(colors.yellow,colors.lightGray)}
|
||||||
|
TextBox{parent=warning,x=5,y=1,text="\x9f ",width=2,fg_bg=core.cpair(colors.lightGray,colors.yellow)}
|
||||||
|
TextBox{parent=warning,x=4,text="\x9f ",width=4,fg_bg=core.cpair(colors.lightGray,colors.yellow)}
|
||||||
|
TextBox{parent=warning,x=3,text="\x9f ",width=6,fg_bg=core.cpair(colors.lightGray,colors.yellow)}
|
||||||
|
TextBox{parent=warning,x=2,text="\x9f ",width=8,fg_bg=core.cpair(colors.lightGray,colors.yellow)}
|
||||||
|
TextBox{parent=warning,text="\x9f ",width=10,fg_bg=core.cpair(colors.lightGray,colors.yellow)}
|
||||||
|
TextBox{parent=warning,text="\x8f\x8f\x8f\x8f\x8f\x8f\x8f\x8f\x8f\x8f\x8f",width=11,fg_bg=core.cpair(colors.yellow,colors.lightGray)}
|
||||||
|
TextBox{parent=warning,x=6,y=3,text=" \n \x83",width=1,fg_bg=core.cpair(colors.yellow,colors.white)}
|
||||||
|
|
||||||
|
TextBox{parent=display,x=13,y=2,text="Critical Software Fault Encountered",alignment=core.ALIGN.CENTER,fg_bg=core.cpair(colors.yellow,colors._INHERIT)}
|
||||||
|
TextBox{parent=display,x=15,y=4,text="Please consider reporting this on the cc-mek-scada Discord or GitHub.",width=36,alignment=core.ALIGN.CENTER}
|
||||||
|
TextBox{parent=display,x=14,y=7,text="refer to the log file for more info",alignment=core.ALIGN.CENTER,fg_bg=core.cpair(colors.gray,colors._INHERIT)}
|
||||||
|
|
||||||
|
local box = Rectangle{parent=display,x=2,y=9,width=display.get_width()-2,height=8,border=core.border(1,colors.gray,true),thin=true,fg_bg=core.cpair(colors.black,colors.white)}
|
||||||
|
TextBox{parent=box,text=err}
|
||||||
|
|
||||||
|
PushButton{parent=display,x=23,y=18,text=" Exit ",callback=exit,active_fg_bg=core.cpair(colors.white,colors.gray),fg_bg=core.cpair(colors.black,colors.red)}
|
||||||
|
|
||||||
|
return display
|
||||||
|
end
|
||||||
|
|
||||||
|
-- render the pocket crash screen
|
||||||
|
---@param exit function callback on exit button press
|
||||||
|
---@return DisplayBox display
|
||||||
|
local function draw_pocket_crash(exit)
|
||||||
|
local DisplayBox = require("graphics.elements.DisplayBox")
|
||||||
|
local Div = require("graphics.elements.Div")
|
||||||
|
local Rectangle = require("graphics.elements.Rectangle")
|
||||||
|
local TextBox = require("graphics.elements.TextBox")
|
||||||
|
local PushButton = require("graphics.elements.controls.PushButton")
|
||||||
|
|
||||||
|
local display = DisplayBox{window=term.current(),fg_bg=core.cpair(colors.white,colors.lightGray)}
|
||||||
|
|
||||||
|
local warning = Div{parent=display,x=2,y=1}
|
||||||
|
TextBox{parent=warning,x=4,y=1,text="\x90",width=1,fg_bg=core.cpair(colors.yellow,colors.lightGray)}
|
||||||
|
TextBox{parent=warning,x=3,text="\x81 ",width=2,fg_bg=core.cpair(colors.lightGray,colors.yellow)}
|
||||||
|
TextBox{parent=warning,x=5,y=2,text="\x94",width=1,fg_bg=core.cpair(colors.yellow,colors.lightGray)}
|
||||||
|
TextBox{parent=warning,x=2,text="\x81 ",width=4,fg_bg=core.cpair(colors.lightGray,colors.yellow)}
|
||||||
|
TextBox{parent=warning,x=6,y=3,text="\x94",width=1,fg_bg=core.cpair(colors.yellow,colors.lightGray)}
|
||||||
|
TextBox{parent=warning,text="\x8e\x8f\x8f\x8e\x8f\x8f\x84",width=7,fg_bg=core.cpair(colors.yellow,colors.lightGray)}
|
||||||
|
TextBox{parent=warning,x=4,y=2,text="\x90",width=1,fg_bg=core.cpair(colors.lightGray,colors.yellow)}
|
||||||
|
TextBox{parent=warning,x=4,y=3,text="\x85",width=1,fg_bg=core.cpair(colors.lightGray,colors.yellow)}
|
||||||
|
|
||||||
|
TextBox{parent=display,x=10,y=2,text=" Critical Software Fault",width=16,alignment=core.ALIGN.CENTER,fg_bg=core.cpair(colors.yellow,colors._INHERIT)}
|
||||||
|
TextBox{parent=display,x=2,y=5,text="Consider reporting this on the cc-mek-scada Discord or GitHub.",width=36,alignment=core.ALIGN.CENTER}
|
||||||
|
|
||||||
|
local box = Rectangle{parent=display,y=9,width=display.get_width(),height=8,fg_bg=core.cpair(colors.black,colors.white)}
|
||||||
|
TextBox{parent=box,text=err}
|
||||||
|
|
||||||
|
PushButton{parent=display,x=11,y=18,text=" Exit ",callback=exit,active_fg_bg=core.cpair(colors.white,colors.gray),fg_bg=core.cpair(colors.black,colors.red)}
|
||||||
|
TextBox{parent=display,x=2,y=20,text="see logs for details",width=24,alignment=core.ALIGN.CENTER,fg_bg=core.cpair(colors.gray,colors._INHERIT)}
|
||||||
|
|
||||||
|
return display
|
||||||
|
end
|
||||||
|
|
||||||
-- when running with debug logs, log the useful information that the crash handler knows
|
-- when running with debug logs, log the useful information that the crash handler knows
|
||||||
function crash.dbg_log_env() log_versions(log.debug) end
|
function crash.dbg_log_env() log_versions(log.debug) end
|
||||||
|
|
||||||
@@ -54,9 +125,41 @@ end
|
|||||||
|
|
||||||
-- final error print on failed xpcall, app exits here
|
-- final error print on failed xpcall, app exits here
|
||||||
function crash.exit()
|
function crash.exit()
|
||||||
|
local handled, run = false, true
|
||||||
|
local display ---@type DisplayBox
|
||||||
|
|
||||||
|
-- special graphical crash screen
|
||||||
|
if has_graphics then
|
||||||
|
handled, display = pcall(util.trinary(_is_pocket_env, draw_pocket_crash, draw_computer_crash), function () run = false end)
|
||||||
|
|
||||||
|
-- event loop
|
||||||
|
while display and run do
|
||||||
|
local event, param1, param2, param3 = util.pull_event()
|
||||||
|
|
||||||
|
-- handle event
|
||||||
|
if event == "mouse_click" or event == "mouse_up" or event == "double_click" then
|
||||||
|
local mouse = core.events.new_mouse_event(event, param1, param2, param3)
|
||||||
|
if mouse then display.handle_mouse(mouse) end
|
||||||
|
elseif event == "terminate" then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
display.delete()
|
||||||
|
|
||||||
|
term.setCursorPos(1, 1)
|
||||||
|
term.setTextColor(colors.white)
|
||||||
|
term.setBackgroundColor(colors.black)
|
||||||
|
term.clear()
|
||||||
|
end
|
||||||
|
|
||||||
log.close()
|
log.close()
|
||||||
util.println("fatal error occured in main application:")
|
|
||||||
error(err, 0)
|
-- default text failure message
|
||||||
|
if not handled then
|
||||||
|
util.println("fatal error occured in main application:")
|
||||||
|
error(err, 0)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return crash
|
return crash
|
||||||
|
|||||||
@@ -8,6 +8,10 @@ local util = require("scada-common.util")
|
|||||||
local DBG_TAG, INF_TAG, WRN_TAG, ERR_TAG, FTL_TAG = "[DBG] ", "[INF] ", "[WRN] ", "[ERR] ", "[FTL] "
|
local DBG_TAG, INF_TAG, WRN_TAG, ERR_TAG, FTL_TAG = "[DBG] ", "[INF] ", "[WRN] ", "[ERR] ", "[FTL] "
|
||||||
local COLON, FUNC, ARROW = ":", "():", " > "
|
local COLON, FUNC, ARROW = ":", "():", " > "
|
||||||
|
|
||||||
|
local MIN_SPACE = 512
|
||||||
|
local OUT_OF_SPACE = "Out of space"
|
||||||
|
local TIME_FMT = "%F %T "
|
||||||
|
|
||||||
---@class logger
|
---@class logger
|
||||||
local log = {}
|
local log = {}
|
||||||
|
|
||||||
@@ -34,14 +38,20 @@ local free_space = fs.getFreeSpace
|
|||||||
-- PRIVATE FUNCTIONS --
|
-- PRIVATE FUNCTIONS --
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|
||||||
|
-- check if the provided error indicates out of space or if insufficient space available
|
||||||
|
---@param err_msg string|nil error message
|
||||||
|
---@return boolean out_of_space
|
||||||
|
local function check_out_of_space(err_msg)
|
||||||
|
return (free_space(logger.path) < MIN_SPACE) or ((err_msg ~= nil) and (string.find(err_msg, OUT_OF_SPACE) ~= nil))
|
||||||
|
end
|
||||||
|
|
||||||
-- private log write function
|
-- private log write function
|
||||||
---@param msg_bits any[]
|
---@param msg_bits any[]
|
||||||
local function _log(msg_bits)
|
local function _log(msg_bits)
|
||||||
if logger.not_ready then return end
|
if logger.not_ready then return end
|
||||||
|
|
||||||
local out_of_space = false
|
local time_stamp = os.date(TIME_FMT)
|
||||||
local time_stamp = os.date("[%c] ")
|
local stamped = util.c(time_stamp, table.unpack(msg_bits))
|
||||||
local stamped = util.c(time_stamp, table.unpack(msg_bits))
|
|
||||||
|
|
||||||
-- attempt to write log
|
-- attempt to write log
|
||||||
local status, result = pcall(function ()
|
local status, result = pcall(function ()
|
||||||
@@ -50,18 +60,7 @@ local function _log(msg_bits)
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
-- if we don't have space, we need to create a new log file
|
-- if we don't have space, we need to create a new log file
|
||||||
|
if check_out_of_space() then
|
||||||
if (not status) and (result ~= nil) then
|
|
||||||
out_of_space = string.find(result, "Out of space") ~= nil
|
|
||||||
|
|
||||||
if out_of_space then
|
|
||||||
-- will delete log file
|
|
||||||
else
|
|
||||||
util.println("unknown error writing to logfile: " .. result)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if out_of_space or (free_space(logger.path) < 512) then
|
|
||||||
-- delete the old log file before opening a new one
|
-- delete the old log file before opening a new one
|
||||||
logger.file.close()
|
logger.file.close()
|
||||||
fs.delete(logger.path)
|
fs.delete(logger.path)
|
||||||
@@ -69,10 +68,12 @@ local function _log(msg_bits)
|
|||||||
-- re-init logger and pass dmesg_out so that it doesn't change
|
-- re-init logger and pass dmesg_out so that it doesn't change
|
||||||
log.init(logger.path, logger.mode, logger.debug, logger.dmesg_out)
|
log.init(logger.path, logger.mode, logger.debug, logger.dmesg_out)
|
||||||
|
|
||||||
-- leave a message
|
-- log the message and recycle warning
|
||||||
logger.file.writeLine(time_stamp .. "recycled log file")
|
logger.file.writeLine(time_stamp .. WRN_TAG .. "recycled log file")
|
||||||
logger.file.writeLine(stamped)
|
logger.file.writeLine(stamped)
|
||||||
logger.file.flush()
|
logger.file.flush()
|
||||||
|
elseif (not status) and (result ~= nil) then
|
||||||
|
util.println("unexpected error writing to the log file: " .. result)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -86,15 +87,12 @@ end
|
|||||||
---@param include_debug boolean whether or not to include debug logs
|
---@param include_debug boolean whether or not to include debug logs
|
||||||
---@param dmesg_redirect? Redirect terminal/window to direct dmesg to
|
---@param dmesg_redirect? Redirect terminal/window to direct dmesg to
|
||||||
function log.init(path, write_mode, include_debug, dmesg_redirect)
|
function log.init(path, write_mode, include_debug, dmesg_redirect)
|
||||||
|
local err_msg
|
||||||
|
|
||||||
logger.path = path
|
logger.path = path
|
||||||
logger.mode = write_mode
|
logger.mode = write_mode
|
||||||
logger.debug = include_debug
|
logger.debug = include_debug
|
||||||
|
logger.file, err_msg = fs.open(path, util.trinary(logger.mode == MODE.APPEND, "a", "w"))
|
||||||
if logger.mode == MODE.APPEND then
|
|
||||||
logger.file = fs.open(path, "a")
|
|
||||||
else
|
|
||||||
logger.file = fs.open(path, "w")
|
|
||||||
end
|
|
||||||
|
|
||||||
if dmesg_redirect then
|
if dmesg_redirect then
|
||||||
logger.dmesg_out = dmesg_redirect
|
logger.dmesg_out = dmesg_redirect
|
||||||
@@ -102,6 +100,25 @@ function log.init(path, write_mode, include_debug, dmesg_redirect)
|
|||||||
logger.dmesg_out = term.current()
|
logger.dmesg_out = term.current()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- check for space issues
|
||||||
|
local out_of_space = check_out_of_space(err_msg)
|
||||||
|
|
||||||
|
-- try to handle problems
|
||||||
|
if logger.file == nil or out_of_space then
|
||||||
|
if out_of_space then
|
||||||
|
if fs.exists(logger.path) then
|
||||||
|
fs.delete(logger.path)
|
||||||
|
|
||||||
|
logger.file, err_msg = fs.open(path, util.trinary(logger.mode == MODE.APPEND, "a", "w"))
|
||||||
|
|
||||||
|
if logger.file then
|
||||||
|
logger.file.writeLine(os.date(TIME_FMT) .. WRN_TAG .. "init recycled log file")
|
||||||
|
logger.file.flush()
|
||||||
|
else error("failed to setup the log file: " .. err_msg) end
|
||||||
|
else error("failed to make space for the log file, please delete unused files") end
|
||||||
|
else error("unexpected error setting up the log file: " .. err_msg) end
|
||||||
|
end
|
||||||
|
|
||||||
logger.not_ready = false
|
logger.not_ready = false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -242,7 +242,7 @@ function ppm.mount_all()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- mount a particular device
|
-- mount a specified device
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
---@param iface string CC peripheral interface
|
---@param iface string CC peripheral interface
|
||||||
---@return string|nil type, table|nil device
|
---@return string|nil type, table|nil device
|
||||||
@@ -266,6 +266,33 @@ function ppm.mount(iface)
|
|||||||
return pm_type, pm_dev
|
return pm_type, pm_dev
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- unmount and remount a specified device
|
||||||
|
---@nodiscard
|
||||||
|
---@param iface string CC peripheral interface
|
||||||
|
---@return string|nil type, table|nil device
|
||||||
|
function ppm.remount(iface)
|
||||||
|
local ifaces = peripheral.getNames()
|
||||||
|
local pm_dev = nil
|
||||||
|
local pm_type = nil
|
||||||
|
|
||||||
|
for i = 1, #ifaces do
|
||||||
|
if iface == ifaces[i] then
|
||||||
|
log.info(util.c("PPM: remount(", iface, ") -> is a ", pm_type))
|
||||||
|
ppm.unmount(ppm_sys.mounts[iface].dev)
|
||||||
|
|
||||||
|
ppm_sys.mounts[iface] = peri_init(iface)
|
||||||
|
|
||||||
|
pm_type = ppm_sys.mounts[iface].type
|
||||||
|
pm_dev = ppm_sys.mounts[iface].dev
|
||||||
|
|
||||||
|
log.info(util.c("PPM: remount(", iface, ") -> remounted a ", pm_type))
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return pm_type, pm_dev
|
||||||
|
end
|
||||||
|
|
||||||
-- mount a virtual, placeholder device (specifically designed for RTU startup with missing devices)
|
-- mount a virtual, placeholder device (specifically designed for RTU startup with missing devices)
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
---@return string type, table device
|
---@return string type, table device
|
||||||
|
|||||||
@@ -74,6 +74,13 @@ function psil.create()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- get the currently stored value for a key, or nil if not set
|
||||||
|
---@param key string data key
|
||||||
|
---@return any
|
||||||
|
function public.get(key)
|
||||||
|
if ic[key] ~= nil then return ic[key].value else return nil end
|
||||||
|
end
|
||||||
|
|
||||||
-- clear the contents of the interconnect
|
-- clear the contents of the interconnect
|
||||||
function public.purge() ic = {} end
|
function public.purge() ic = {} end
|
||||||
|
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ local IO_PORT = {
|
|||||||
-- unit outputs
|
-- unit outputs
|
||||||
U_ALARM = 25, -- active high, unit alarm
|
U_ALARM = 25, -- active high, unit alarm
|
||||||
U_EMER_COOL = 26, -- active low, emergency coolant control
|
U_EMER_COOL = 26, -- active low, emergency coolant control
|
||||||
|
U_AUX_COOL = 30, -- active low, auxiliary coolant control
|
||||||
|
|
||||||
-- analog outputs --
|
-- analog outputs --
|
||||||
|
|
||||||
@@ -90,8 +91,8 @@ rsio.IO_DIR = IO_DIR
|
|||||||
rsio.IO_MODE = IO_MODE
|
rsio.IO_MODE = IO_MODE
|
||||||
rsio.IO = IO_PORT
|
rsio.IO = IO_PORT
|
||||||
|
|
||||||
rsio.NUM_PORTS = 29
|
rsio.NUM_PORTS = 30
|
||||||
rsio.NUM_DIG_PORTS = 28
|
rsio.NUM_DIG_PORTS = 29
|
||||||
rsio.NUM_ANA_PORTS = 1
|
rsio.NUM_ANA_PORTS = 1
|
||||||
|
|
||||||
-- self checks
|
-- self checks
|
||||||
@@ -149,6 +150,7 @@ local MODES = {
|
|||||||
[IO.R_PLC_TIMEOUT] = IO_MODE.DIGITAL_OUT,
|
[IO.R_PLC_TIMEOUT] = IO_MODE.DIGITAL_OUT,
|
||||||
[IO.U_ALARM] = IO_MODE.DIGITAL_OUT,
|
[IO.U_ALARM] = IO_MODE.DIGITAL_OUT,
|
||||||
[IO.U_EMER_COOL] = IO_MODE.DIGITAL_OUT,
|
[IO.U_EMER_COOL] = IO_MODE.DIGITAL_OUT,
|
||||||
|
[IO.U_AUX_COOL] = IO_MODE.DIGITAL_OUT,
|
||||||
[IO.F_MATRIX_CHG] = IO_MODE.ANALOG_OUT
|
[IO.F_MATRIX_CHG] = IO_MODE.ANALOG_OUT
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,10 +210,11 @@ local RS_DIO_MAP = {
|
|||||||
[IO.R_PLC_TIMEOUT] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
[IO.R_PLC_TIMEOUT] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
||||||
|
|
||||||
[IO.U_ALARM] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
[IO.U_ALARM] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
||||||
[IO.U_EMER_COOL] = { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT }
|
[IO.U_EMER_COOL] = { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT },
|
||||||
|
[IO.U_AUX_COOL] = { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT }
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(rsio.NUM_DIG_PORTS == #RS_DIO_MAP, "RS_DIO_MAP length incorrect")
|
assert(rsio.NUM_DIG_PORTS == util.table_len(RS_DIO_MAP), "RS_DIO_MAP length incorrect")
|
||||||
|
|
||||||
-- get the I/O direction of a port
|
-- get the I/O direction of a port
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ function types.new_zero_coordinate() return { x = 0, y = 0, z = 0 } end
|
|||||||
---@field type RTU_UNIT_TYPE
|
---@field type RTU_UNIT_TYPE
|
||||||
---@field index integer|false
|
---@field index integer|false
|
||||||
---@field reactor integer
|
---@field reactor integer
|
||||||
---@field rsio IO_PORT[]|nil
|
---@field rs_conns IO_PORT[][]|nil
|
||||||
|
|
||||||
-- create a new reactor database
|
-- create a new reactor database
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
@@ -253,6 +253,61 @@ types.ENERGY_SCALE_UNITS = {
|
|||||||
"RF"
|
"RF"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local GENERIC_STATE = {
|
||||||
|
OFFLINE = 1,
|
||||||
|
UNFORMED = 2,
|
||||||
|
FAULT = 3,
|
||||||
|
IDLE = 4,
|
||||||
|
ACTIVE = 5
|
||||||
|
}
|
||||||
|
|
||||||
|
---@enum REACTOR_STATE
|
||||||
|
types.REACTOR_STATE = {
|
||||||
|
OFFLINE = 1,
|
||||||
|
UNFORMED = 2,
|
||||||
|
FAULT = 3,
|
||||||
|
DISABLED = 4,
|
||||||
|
ACTIVE = 5,
|
||||||
|
SCRAMMED = 6,
|
||||||
|
FORCE_DISABLED = 7
|
||||||
|
}
|
||||||
|
|
||||||
|
---@enum BOILER_STATE
|
||||||
|
types.BOILER_STATE = GENERIC_STATE
|
||||||
|
|
||||||
|
---@enum TURBINE_STATE
|
||||||
|
types.TURBINE_STATE = {
|
||||||
|
OFFLINE = 1,
|
||||||
|
UNFORMED = 2,
|
||||||
|
FAULT = 3,
|
||||||
|
IDLE = 4,
|
||||||
|
ACTIVE = 5,
|
||||||
|
TRIPPED = 6
|
||||||
|
}
|
||||||
|
|
||||||
|
---@enum TANK_STATE
|
||||||
|
types.TANK_STATE = {
|
||||||
|
OFFLINE = 1,
|
||||||
|
UNFORMED = 2,
|
||||||
|
FAULT = 3,
|
||||||
|
ONLINE = 4,
|
||||||
|
LOW_FILL = 5,
|
||||||
|
HIGH_FILL = 6
|
||||||
|
}
|
||||||
|
|
||||||
|
---@enum IMATRIX_STATE
|
||||||
|
types.IMATRIX_STATE = {
|
||||||
|
OFFLINE = 1,
|
||||||
|
UNFORMED = 2,
|
||||||
|
FAULT = 3,
|
||||||
|
ONLINE = 4,
|
||||||
|
LOW_CHARGE = 5,
|
||||||
|
HIGH_CHARGE = 6
|
||||||
|
}
|
||||||
|
|
||||||
|
---@enum SPS_STATE
|
||||||
|
types.SPS_STATE = GENERIC_STATE
|
||||||
|
|
||||||
---@enum PANEL_LINK_STATE
|
---@enum PANEL_LINK_STATE
|
||||||
types.PANEL_LINK_STATE = {
|
types.PANEL_LINK_STATE = {
|
||||||
LINKED = 1,
|
LINKED = 1,
|
||||||
@@ -363,6 +418,12 @@ types.AUTO_GROUP_NAMES = {
|
|||||||
"Backup"
|
"Backup"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
---@enum COOLANT_TYPE
|
||||||
|
types.COOLANT_TYPE = {
|
||||||
|
WATER = 1,
|
||||||
|
SODIUM = 2
|
||||||
|
}
|
||||||
|
|
||||||
---@enum WASTE_MODE
|
---@enum WASTE_MODE
|
||||||
types.WASTE_MODE = {
|
types.WASTE_MODE = {
|
||||||
AUTO = 1,
|
AUTO = 1,
|
||||||
@@ -404,7 +465,8 @@ types.ALARM = {
|
|||||||
ReactorHighWaste = 9,
|
ReactorHighWaste = 9,
|
||||||
RPSTransient = 10,
|
RPSTransient = 10,
|
||||||
RCSTransient = 11,
|
RCSTransient = 11,
|
||||||
TurbineTrip = 12
|
TurbineTrip = 12,
|
||||||
|
FacilityRadiation = 13
|
||||||
}
|
}
|
||||||
|
|
||||||
types.ALARM_NAMES = {
|
types.ALARM_NAMES = {
|
||||||
@@ -419,7 +481,8 @@ types.ALARM_NAMES = {
|
|||||||
"ReactorHighWaste",
|
"ReactorHighWaste",
|
||||||
"RPSTransient",
|
"RPSTransient",
|
||||||
"RCSTransient",
|
"RCSTransient",
|
||||||
"TurbineTrip"
|
"TurbineTrip",
|
||||||
|
"FacilityRadiation"
|
||||||
}
|
}
|
||||||
|
|
||||||
---@enum ALARM_PRIORITY
|
---@enum ALARM_PRIORITY
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ local t_pack = table.pack
|
|||||||
local util = {}
|
local util = {}
|
||||||
|
|
||||||
-- scada-common version
|
-- scada-common version
|
||||||
util.version = "1.4.6"
|
util.version = "1.5.2"
|
||||||
|
|
||||||
util.TICK_TIME_S = 0.05
|
util.TICK_TIME_S = 0.05
|
||||||
util.TICK_TIME_MS = 50
|
util.TICK_TIME_MS = 50
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
local BOOTLOADER_VERSION = "1.1"
|
local BOOTLOADER_VERSION = "1.2"
|
||||||
|
|
||||||
print("SCADA BOOTLOADER V" .. BOOTLOADER_VERSION)
|
print("SCADA BOOTLOADER V" .. BOOTLOADER_VERSION)
|
||||||
print("BOOT> SCANNING FOR APPLICATIONS...")
|
print("BOOT> SCANNING FOR APPLICATIONS...")
|
||||||
|
|||||||
137
supervisor/alarm_ctl.lua
Normal file
137
supervisor/alarm_ctl.lua
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
local log = require("scada-common.log")
|
||||||
|
local types = require("scada-common.types")
|
||||||
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
|
local ALARM_STATE = types.ALARM_STATE
|
||||||
|
|
||||||
|
---@class alarm_def
|
||||||
|
---@field state ALARM_INT_STATE internal alarm state
|
||||||
|
---@field trip_time integer time (ms) when first tripped
|
||||||
|
---@field hold_time integer time (s) to hold before tripping
|
||||||
|
---@field id ALARM alarm ID
|
||||||
|
---@field tier integer alarm urgency tier (0 = highest)
|
||||||
|
|
||||||
|
local AISTATE_NAMES = {
|
||||||
|
"INACTIVE",
|
||||||
|
"TRIPPING",
|
||||||
|
"TRIPPED",
|
||||||
|
"ACKED",
|
||||||
|
"RING_BACK",
|
||||||
|
"RING_BACK_TRIPPING"
|
||||||
|
}
|
||||||
|
|
||||||
|
---@enum ALARM_INT_STATE
|
||||||
|
local AISTATE = {
|
||||||
|
INACTIVE = 1,
|
||||||
|
TRIPPING = 2,
|
||||||
|
TRIPPED = 3,
|
||||||
|
ACKED = 4,
|
||||||
|
RING_BACK = 5,
|
||||||
|
RING_BACK_TRIPPING = 6
|
||||||
|
}
|
||||||
|
|
||||||
|
local alarm_ctl = {}
|
||||||
|
|
||||||
|
alarm_ctl.AISTATE = AISTATE
|
||||||
|
alarm_ctl.AISTATE_NAMES = AISTATE_NAMES
|
||||||
|
|
||||||
|
-- update an alarm state based on its current status and if it is tripped
|
||||||
|
---@param caller_tag string tag to use in log messages
|
||||||
|
---@param alarm_states { [ALARM]: ALARM_STATE } unit instance
|
||||||
|
---@param tripped boolean if the alarm condition is sti ll active
|
||||||
|
---@param alarm alarm_def alarm table
|
||||||
|
---@param no_ring_back boolean? true to skip the ring back state, returning to inactive instead
|
||||||
|
---@return boolean new_trip if the alarm just changed to being tripped
|
||||||
|
function alarm_ctl.update_alarm_state(caller_tag, alarm_states, tripped, alarm, no_ring_back)
|
||||||
|
local int_state = alarm.state
|
||||||
|
local ext_state = alarm_states[alarm.id]
|
||||||
|
|
||||||
|
-- alarm inactive
|
||||||
|
if int_state == AISTATE.INACTIVE then
|
||||||
|
if tripped then
|
||||||
|
alarm.trip_time = util.time_ms()
|
||||||
|
if alarm.hold_time > 0 then
|
||||||
|
alarm.state = AISTATE.TRIPPING
|
||||||
|
alarm_states[alarm.id] = ALARM_STATE.INACTIVE
|
||||||
|
else
|
||||||
|
alarm.state = AISTATE.TRIPPED
|
||||||
|
alarm_states[alarm.id] = ALARM_STATE.TRIPPED
|
||||||
|
log.info(util.c(caller_tag, " ALARM ", alarm.id, " (", types.ALARM_NAMES[alarm.id], "): TRIPPED [PRIORITY ",
|
||||||
|
types.ALARM_PRIORITY_NAMES[alarm.tier],"]"))
|
||||||
|
end
|
||||||
|
else
|
||||||
|
alarm.trip_time = util.time_ms()
|
||||||
|
alarm_states[alarm.id] = ALARM_STATE.INACTIVE
|
||||||
|
end
|
||||||
|
-- alarm condition met, but not yet for required hold time
|
||||||
|
elseif (int_state == AISTATE.TRIPPING) or (int_state == AISTATE.RING_BACK_TRIPPING) then
|
||||||
|
if tripped then
|
||||||
|
local elapsed = util.time_ms() - alarm.trip_time
|
||||||
|
if elapsed > (alarm.hold_time * 1000) then
|
||||||
|
alarm.state = AISTATE.TRIPPED
|
||||||
|
alarm_states[alarm.id] = ALARM_STATE.TRIPPED
|
||||||
|
log.info(util.c(caller_tag, " ALARM ", alarm.id, " (", types.ALARM_NAMES[alarm.id], "): TRIPPED [PRIORITY ",
|
||||||
|
types.ALARM_PRIORITY_NAMES[alarm.tier],"]"))
|
||||||
|
end
|
||||||
|
elseif int_state == AISTATE.RING_BACK_TRIPPING then
|
||||||
|
alarm.trip_time = 0
|
||||||
|
alarm.state = AISTATE.RING_BACK
|
||||||
|
alarm_states[alarm.id] = ALARM_STATE.RING_BACK
|
||||||
|
else
|
||||||
|
alarm.trip_time = 0
|
||||||
|
alarm.state = AISTATE.INACTIVE
|
||||||
|
alarm_states[alarm.id] = ALARM_STATE.INACTIVE
|
||||||
|
end
|
||||||
|
-- alarm tripped and alarming
|
||||||
|
elseif int_state == AISTATE.TRIPPED then
|
||||||
|
if tripped then
|
||||||
|
if ext_state == ALARM_STATE.ACKED then
|
||||||
|
-- was acked by coordinator
|
||||||
|
alarm.state = AISTATE.ACKED
|
||||||
|
end
|
||||||
|
elseif no_ring_back then
|
||||||
|
alarm.state = AISTATE.INACTIVE
|
||||||
|
alarm_states[alarm.id] = ALARM_STATE.INACTIVE
|
||||||
|
else
|
||||||
|
alarm.state = AISTATE.RING_BACK
|
||||||
|
alarm_states[alarm.id] = ALARM_STATE.RING_BACK
|
||||||
|
end
|
||||||
|
-- alarm acknowledged but still tripped
|
||||||
|
elseif int_state == AISTATE.ACKED then
|
||||||
|
if not tripped then
|
||||||
|
if no_ring_back then
|
||||||
|
alarm.state = AISTATE.INACTIVE
|
||||||
|
alarm_states[alarm.id] = ALARM_STATE.INACTIVE
|
||||||
|
else
|
||||||
|
alarm.state = AISTATE.RING_BACK
|
||||||
|
alarm_states[alarm.id] = ALARM_STATE.RING_BACK
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- alarm no longer tripped, operator must reset to clear
|
||||||
|
elseif int_state == AISTATE.RING_BACK then
|
||||||
|
if tripped then
|
||||||
|
alarm.trip_time = util.time_ms()
|
||||||
|
if alarm.hold_time > 0 then
|
||||||
|
alarm.state = AISTATE.RING_BACK_TRIPPING
|
||||||
|
else
|
||||||
|
alarm.state = AISTATE.TRIPPED
|
||||||
|
alarm_states[alarm.id] = ALARM_STATE.TRIPPED
|
||||||
|
end
|
||||||
|
elseif ext_state == ALARM_STATE.INACTIVE then
|
||||||
|
-- was reset by coordinator
|
||||||
|
alarm.state = AISTATE.INACTIVE
|
||||||
|
alarm.trip_time = 0
|
||||||
|
end
|
||||||
|
else
|
||||||
|
log.error(util.c(caller_tag, " invalid alarm state for alarm ", alarm.id), true)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- check for state change
|
||||||
|
if alarm.state ~= int_state then
|
||||||
|
local change_str = util.c(AISTATE_NAMES[int_state], " -> ", AISTATE_NAMES[alarm.state])
|
||||||
|
log.debug(util.c(caller_tag, " ALARM ", alarm.id, " (", types.ALARM_NAMES[alarm.id], "): ", change_str))
|
||||||
|
return alarm.state == AISTATE.TRIPPED
|
||||||
|
else return false end
|
||||||
|
end
|
||||||
|
|
||||||
|
return alarm_ctl
|
||||||
@@ -18,12 +18,148 @@ local tri = util.trinary
|
|||||||
local cpair = core.cpair
|
local cpair = core.cpair
|
||||||
|
|
||||||
local self = {
|
local self = {
|
||||||
vis_ftanks = {}, ---@type { line: Div, pipe_conn?: TextBox, pipe_chain?: TextBox, pipe_direct?: TextBox, label?: TextBox }[]
|
tank_fluid_opts = {}, ---@type Radio2D[]
|
||||||
vis_utanks = {} ---@type { line: Div, label: TextBox }[]
|
|
||||||
|
vis_draw = nil, ---@type function
|
||||||
|
draw_fluid_ops = nil, ---@type function
|
||||||
|
|
||||||
|
vis_ftanks = {}, ---@type { line: Div, pipe_conn?: TextBox, pipe_chain?: TextBox, pipe_direct?: TextBox, label?: TextBox }[]
|
||||||
|
vis_utanks = {} ---@type { line: Div, label: TextBox }[]
|
||||||
}
|
}
|
||||||
|
|
||||||
local facility = {}
|
local facility = {}
|
||||||
|
|
||||||
|
-- generate the tank list and tank connections tables
|
||||||
|
---@param mode integer facility tank mode
|
||||||
|
---@param defs table facility tank definitions
|
||||||
|
---@return table tank_list, table tank_conns
|
||||||
|
function facility.generate_tank_list_and_conns(mode, defs)
|
||||||
|
local tank_mode = mode
|
||||||
|
local tank_defs = defs
|
||||||
|
local tank_list = { table.unpack(tank_defs) }
|
||||||
|
local tank_conns = { table.unpack(tank_defs) }
|
||||||
|
|
||||||
|
local function calc_fdef(start_idx, end_idx)
|
||||||
|
local first = 4
|
||||||
|
for i = start_idx, end_idx do
|
||||||
|
if tank_defs[i] == 2 then
|
||||||
|
if i < first then first = i end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return first
|
||||||
|
end
|
||||||
|
|
||||||
|
-- set units using their own tanks as connected to their respective unit tank
|
||||||
|
for i = 1, #tank_defs do
|
||||||
|
if tank_defs[i] == 1 then tank_conns[i] = i end
|
||||||
|
end
|
||||||
|
|
||||||
|
if tank_mode == 1 then
|
||||||
|
-- (1) 1 total facility tank (A A A A)
|
||||||
|
local first_fdef = calc_fdef(1, #tank_defs)
|
||||||
|
for i = 1, #tank_defs do
|
||||||
|
if (i >= first_fdef) and (tank_defs[i] == 2) then
|
||||||
|
tank_conns[i] = first_fdef
|
||||||
|
|
||||||
|
if i > first_fdef then tank_list[i] = 0 end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif tank_mode == 2 then
|
||||||
|
-- (2) 2 total facility tanks (A A A B)
|
||||||
|
local first_fdef = calc_fdef(1, math.min(3, #tank_defs))
|
||||||
|
for i = 1, #tank_defs do
|
||||||
|
if (i >= first_fdef) and (tank_defs[i] == 2) then
|
||||||
|
if i == 4 then
|
||||||
|
tank_conns[i] = 4
|
||||||
|
else
|
||||||
|
tank_conns[i] = first_fdef
|
||||||
|
|
||||||
|
if i > first_fdef then tank_list[i] = 0 end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif tank_mode == 3 then
|
||||||
|
-- (3) 2 total facility tanks (A A B B)
|
||||||
|
for _, a in pairs({ 1, 3 }) do
|
||||||
|
local b = a + 1
|
||||||
|
|
||||||
|
if tank_defs[a] == 2 then
|
||||||
|
tank_conns[a] = a
|
||||||
|
elseif tank_defs[b] == 2 then
|
||||||
|
tank_conns[b] = b
|
||||||
|
end
|
||||||
|
|
||||||
|
if (tank_defs[a] == 2) and (tank_defs[b] == 2) then
|
||||||
|
tank_list[b] = 0
|
||||||
|
tank_conns[b] = a
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif tank_mode == 4 then
|
||||||
|
-- (4) 2 total facility tanks (A B B B)
|
||||||
|
local first_fdef = calc_fdef(2, #tank_defs)
|
||||||
|
for i = 1, #tank_defs do
|
||||||
|
if tank_defs[i] == 2 then
|
||||||
|
if i == 1 then
|
||||||
|
tank_conns[i] = 1
|
||||||
|
elseif i >= first_fdef then
|
||||||
|
tank_conns[i] = first_fdef
|
||||||
|
|
||||||
|
if i > first_fdef then tank_list[i] = 0 end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif tank_mode == 5 then
|
||||||
|
-- (5) 3 total facility tanks (A A B C)
|
||||||
|
local first_fdef = calc_fdef(1, math.min(2, #tank_defs))
|
||||||
|
for i = 1, #tank_defs do
|
||||||
|
if (i >= first_fdef) and (tank_defs[i] == 2) then
|
||||||
|
if i == 3 or i == 4 then
|
||||||
|
tank_conns[i] = i
|
||||||
|
elseif i >= first_fdef then
|
||||||
|
tank_conns[i] = first_fdef
|
||||||
|
|
||||||
|
if i > first_fdef then tank_list[i] = 0 end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif tank_mode == 6 then
|
||||||
|
-- (6) 3 total facility tanks (A B B C)
|
||||||
|
local first_fdef = calc_fdef(2, math.min(3, #tank_defs))
|
||||||
|
for i = 1, #tank_defs do
|
||||||
|
if tank_defs[i] == 2 then
|
||||||
|
if i == 1 or i == 4 then
|
||||||
|
tank_conns[i] = i
|
||||||
|
elseif i >= first_fdef then
|
||||||
|
tank_conns[i] = first_fdef
|
||||||
|
|
||||||
|
if i > first_fdef then tank_list[i] = 0 end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif tank_mode == 7 then
|
||||||
|
-- (7) 3 total facility tanks (A B C C)
|
||||||
|
local first_fdef = calc_fdef(3, #tank_defs)
|
||||||
|
for i = 1, #tank_defs do
|
||||||
|
if tank_defs[i] == 2 then
|
||||||
|
if i == 1 or i == 2 then
|
||||||
|
tank_conns[i] = i
|
||||||
|
elseif i >= first_fdef then
|
||||||
|
tank_conns[i] = first_fdef
|
||||||
|
|
||||||
|
if i > first_fdef then tank_list[i] = 0 end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif tank_mode == 8 then
|
||||||
|
-- (8) 4 total facility tanks (A B C D)
|
||||||
|
for i = 1, #tank_defs do
|
||||||
|
if tank_defs[i] == 2 then tank_conns[i] = i end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return tank_list, tank_conns
|
||||||
|
end
|
||||||
|
|
||||||
-- create the facility configuration view
|
-- create the facility configuration view
|
||||||
---@param tool_ctl _svr_cfg_tool_ctl
|
---@param tool_ctl _svr_cfg_tool_ctl
|
||||||
---@param main_pane MultiPane
|
---@param main_pane MultiPane
|
||||||
@@ -48,14 +184,19 @@ function facility.create(tool_ctl, main_pane, cfg_sys, fac_cfg, style)
|
|||||||
local fac_c_5 = Div{parent=fac_cfg,x=2,y=4,width=49}
|
local fac_c_5 = Div{parent=fac_cfg,x=2,y=4,width=49}
|
||||||
local fac_c_6 = Div{parent=fac_cfg,x=2,y=4,width=49}
|
local fac_c_6 = Div{parent=fac_cfg,x=2,y=4,width=49}
|
||||||
local fac_c_7 = Div{parent=fac_cfg,x=2,y=4,width=49}
|
local fac_c_7 = Div{parent=fac_cfg,x=2,y=4,width=49}
|
||||||
|
local fac_c_8 = Div{parent=fac_cfg,x=2,y=4,width=49}
|
||||||
|
local fac_c_9 = Div{parent=fac_cfg,x=2,y=4,width=49}
|
||||||
|
|
||||||
local fac_pane = MultiPane{parent=fac_cfg,x=1,y=4,panes={fac_c_1,fac_c_2,fac_c_3,fac_c_4,fac_c_5,fac_c_6,fac_c_7}}
|
local fac_pane = MultiPane{parent=fac_cfg,x=1,y=4,panes={fac_c_1,fac_c_2,fac_c_3,fac_c_4,fac_c_5,fac_c_6,fac_c_7,fac_c_8,fac_c_9}}
|
||||||
|
|
||||||
TextBox{parent=fac_cfg,x=1,y=2,text=" Facility Configuration",fg_bg=cpair(colors.black,colors.yellow)}
|
TextBox{parent=fac_cfg,x=1,y=2,text=" Facility Configuration",fg_bg=cpair(colors.black,colors.yellow)}
|
||||||
|
|
||||||
|
--#region Unit Count
|
||||||
|
|
||||||
TextBox{parent=fac_c_1,x=1,y=1,height=3,text="Please enter the number of reactors you have, also referred to as reactor units or 'units' for short. A maximum of 4 is currently supported."}
|
TextBox{parent=fac_c_1,x=1,y=1,height=3,text="Please enter the number of reactors you have, also referred to as reactor units or 'units' for short. A maximum of 4 is currently supported."}
|
||||||
tool_ctl.num_units = NumberField{parent=fac_c_1,x=1,y=5,width=5,max_chars=2,default=ini_cfg.UnitCount,min=1,max=4,fg_bg=bw_fg_bg}
|
tool_ctl.num_units = NumberField{parent=fac_c_1,x=1,y=5,width=5,max_chars=2,default=ini_cfg.UnitCount,min=1,max=4,fg_bg=bw_fg_bg}
|
||||||
TextBox{parent=fac_c_1,x=7,y=5,text="reactors"}
|
TextBox{parent=fac_c_1,x=7,y=5,text="reactors"}
|
||||||
|
TextBox{parent=fac_c_1,x=1,y=7,height=3,text="If you already configured your coordinator, make sure you update the coordinator's configured unit count.",fg_bg=cpair(colors.yellow,colors._INHERIT)}
|
||||||
|
|
||||||
local nu_error = TextBox{parent=fac_c_1,x=8,y=14,width=35,text="Please set the number of reactors.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
local nu_error = TextBox{parent=fac_c_1,x=8,y=14,width=35,text="Please set the number of reactors.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||||
|
|
||||||
@@ -65,10 +206,18 @@ function facility.create(tool_ctl, main_pane, cfg_sys, fac_cfg, style)
|
|||||||
nu_error.hide(true)
|
nu_error.hide(true)
|
||||||
tmp_cfg.UnitCount = count
|
tmp_cfg.UnitCount = count
|
||||||
|
|
||||||
local confs = tool_ctl.cooling_elems
|
local c_confs = tool_ctl.cooling_elems
|
||||||
if count >= 2 then confs[2].line.show() else confs[2].line.hide(true) end
|
local a_confs = tool_ctl.aux_cool_elems
|
||||||
if count >= 3 then confs[3].line.show() else confs[3].line.hide(true) end
|
|
||||||
if count == 4 then confs[4].line.show() else confs[4].line.hide(true) end
|
for i = 2, 4 do
|
||||||
|
if count >= i then
|
||||||
|
c_confs[i].line.show()
|
||||||
|
a_confs[i].line.show()
|
||||||
|
else
|
||||||
|
c_confs[i].line.hide(true)
|
||||||
|
a_confs[i].line.hide(true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
fac_pane.set_value(2)
|
fac_pane.set_value(2)
|
||||||
else nu_error.show() end
|
else nu_error.show() end
|
||||||
@@ -77,6 +226,9 @@ function facility.create(tool_ctl, main_pane, cfg_sys, fac_cfg, style)
|
|||||||
PushButton{parent=fac_c_1,x=1,y=14,text="\x1b Back",callback=function()main_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=fac_c_1,x=1,y=14,text="\x1b Back",callback=function()main_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
PushButton{parent=fac_c_1,x=44,y=14,text="Next \x1a",callback=submit_num_units,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=fac_c_1,x=44,y=14,text="Next \x1a",callback=submit_num_units,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
--#region Cooling Configuration
|
||||||
|
|
||||||
TextBox{parent=fac_c_2,x=1,y=1,height=4,text="Please provide the reactor cooling configuration below. This includes the number of turbines, boilers, and if that reactor has a connection to a dynamic tank for emergency coolant."}
|
TextBox{parent=fac_c_2,x=1,y=1,height=4,text="Please provide the reactor cooling configuration below. This includes the number of turbines, boilers, and if that reactor has a connection to a dynamic tank for emergency coolant."}
|
||||||
TextBox{parent=fac_c_2,x=1,y=6,text="UNIT TURBINES BOILERS HAS TANK CONNECTION?",fg_bg=g_lg_fg_bg}
|
TextBox{parent=fac_c_2,x=1,y=6,text="UNIT TURBINES BOILERS HAS TANK CONNECTION?",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
@@ -142,6 +294,14 @@ function facility.create(tool_ctl, main_pane, cfg_sys, fac_cfg, style)
|
|||||||
else elem.div.hide(true) end
|
else elem.div.hide(true) end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if not any_has_tank then
|
||||||
|
tmp_cfg.FacilityTankMode = 0
|
||||||
|
tmp_cfg.FacilityTankDefs = {}
|
||||||
|
tmp_cfg.FacilityTankList = {}
|
||||||
|
tmp_cfg.FacilityTankConns = {}
|
||||||
|
tmp_cfg.TankFluidTypes = {}
|
||||||
|
end
|
||||||
|
|
||||||
if any_has_tank then fac_pane.set_value(3) else main_pane.set_value(3) end
|
if any_has_tank then fac_pane.set_value(3) else main_pane.set_value(3) end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -149,6 +309,9 @@ function facility.create(tool_ctl, main_pane, cfg_sys, fac_cfg, style)
|
|||||||
PushButton{parent=fac_c_2,x=1,y=14,text="\x1b Back",callback=function()fac_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=fac_c_2,x=1,y=14,text="\x1b Back",callback=function()fac_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
PushButton{parent=fac_c_2,x=44,y=14,text="Next \x1a",callback=submit_cooling,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=fac_c_2,x=44,y=14,text="Next \x1a",callback=submit_cooling,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
--#region Facility Tanks Option
|
||||||
|
|
||||||
TextBox{parent=fac_c_3,x=1,y=1,height=6,text="You have set one or more of your units to use dynamic tanks for emergency coolant. You have two paths for configuration. The first is to assign dynamic tanks to reactor units; one tank per reactor, only connected to that reactor. RTU configurations must also assign it as such."}
|
TextBox{parent=fac_c_3,x=1,y=1,height=6,text="You have set one or more of your units to use dynamic tanks for emergency coolant. You have two paths for configuration. The first is to assign dynamic tanks to reactor units; one tank per reactor, only connected to that reactor. RTU configurations must also assign it as such."}
|
||||||
TextBox{parent=fac_c_3,x=1,y=8,height=3,text="Alternatively, you can configure them as facility tanks to connect to multiple reactor units. These can intermingle with unit-specific tanks."}
|
TextBox{parent=fac_c_3,x=1,y=8,height=3,text="Alternatively, you can configure them as facility tanks to connect to multiple reactor units. These can intermingle with unit-specific tanks."}
|
||||||
|
|
||||||
@@ -161,6 +324,16 @@ function facility.create(tool_ctl, main_pane, cfg_sys, fac_cfg, style)
|
|||||||
else
|
else
|
||||||
tmp_cfg.FacilityTankMode = 0
|
tmp_cfg.FacilityTankMode = 0
|
||||||
tmp_cfg.FacilityTankDefs = {}
|
tmp_cfg.FacilityTankDefs = {}
|
||||||
|
|
||||||
|
-- on facility tank mode 0, setup tank defs to match unit tank option
|
||||||
|
for i = 1, tmp_cfg.UnitCount do
|
||||||
|
tmp_cfg.FacilityTankDefs[i] = tri(tmp_cfg.CoolingConfig[i].TankConnection, 1, 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
tmp_cfg.FacilityTankList, tmp_cfg.FacilityTankConns = facility.generate_tank_list_and_conns(tmp_cfg.FacilityTankMode, tmp_cfg.FacilityTankDefs)
|
||||||
|
|
||||||
|
self.draw_fluid_ops()
|
||||||
|
|
||||||
fac_pane.set_value(7)
|
fac_pane.set_value(7)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -168,6 +341,9 @@ function facility.create(tool_ctl, main_pane, cfg_sys, fac_cfg, style)
|
|||||||
PushButton{parent=fac_c_3,x=1,y=14,text="\x1b Back",callback=function()fac_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=fac_c_3,x=1,y=14,text="\x1b Back",callback=function()fac_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
PushButton{parent=fac_c_3,x=44,y=14,text="Next \x1a",callback=submit_en_fac_tank,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=fac_c_3,x=44,y=14,text="Next \x1a",callback=submit_en_fac_tank,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
--#region Facility Tank Connections
|
||||||
|
|
||||||
TextBox{parent=fac_c_4,x=1,y=1,height=4,text="Please set unit connections to dynamic tanks, selecting at least one facility tank. The layout for facility tanks will be configured next."}
|
TextBox{parent=fac_c_4,x=1,y=1,height=4,text="Please set unit connections to dynamic tanks, selecting at least one facility tank. The layout for facility tanks will be configured next."}
|
||||||
|
|
||||||
for i = 1, 4 do
|
for i = 1, 4 do
|
||||||
@@ -220,7 +396,7 @@ function facility.create(tool_ctl, main_pane, cfg_sys, fac_cfg, style)
|
|||||||
self.vis_utanks[i].line.hide(true)
|
self.vis_utanks[i].line.hide(true)
|
||||||
end
|
end
|
||||||
|
|
||||||
tool_ctl.vis_draw(tmp_cfg.FacilityTankMode)
|
self.vis_draw(tmp_cfg.FacilityTankMode)
|
||||||
|
|
||||||
if any_fac then
|
if any_fac then
|
||||||
tank_err.hide(true)
|
tank_err.hide(true)
|
||||||
@@ -231,6 +407,9 @@ function facility.create(tool_ctl, main_pane, cfg_sys, fac_cfg, style)
|
|||||||
PushButton{parent=fac_c_4,x=1,y=14,text="\x1b Back",callback=function()fac_pane.set_value(3)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=fac_c_4,x=1,y=14,text="\x1b Back",callback=function()fac_pane.set_value(3)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
PushButton{parent=fac_c_4,x=44,y=14,text="Next \x1a",callback=submit_tank_defs,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=fac_c_4,x=44,y=14,text="Next \x1a",callback=submit_tank_defs,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
--#region Facility Tank Mode
|
||||||
|
|
||||||
TextBox{parent=fac_c_5,x=1,y=1,text="Please select your dynamic tank layout."}
|
TextBox{parent=fac_c_5,x=1,y=1,text="Please select your dynamic tank layout."}
|
||||||
TextBox{parent=fac_c_5,x=12,y=3,text="Facility Tanks Unit Tanks",fg_bg=g_lg_fg_bg}
|
TextBox{parent=fac_c_5,x=12,y=3,text="Facility Tanks Unit Tanks",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
@@ -269,7 +448,7 @@ function facility.create(tool_ctl, main_pane, cfg_sys, fac_cfg, style)
|
|||||||
|
|
||||||
-- draw the pipe visualization
|
-- draw the pipe visualization
|
||||||
---@param mode integer pipe mode
|
---@param mode integer pipe mode
|
||||||
function tool_ctl.vis_draw(mode)
|
function self.vis_draw(mode)
|
||||||
-- is a facility tank connected to this unit
|
-- is a facility tank connected to this unit
|
||||||
---@param i integer unit 1 - 4
|
---@param i integer unit 1 - 4
|
||||||
---@return boolean connected
|
---@return boolean connected
|
||||||
@@ -391,7 +570,7 @@ function facility.create(tool_ctl, main_pane, cfg_sys, fac_cfg, style)
|
|||||||
|
|
||||||
local function change_mode(mode)
|
local function change_mode(mode)
|
||||||
tmp_cfg.FacilityTankMode = mode
|
tmp_cfg.FacilityTankMode = mode
|
||||||
tool_ctl.vis_draw(mode)
|
self.vis_draw(mode)
|
||||||
end
|
end
|
||||||
|
|
||||||
local tank_modes = { "Mode 1", "Mode 2", "Mode 3", "Mode 4", "Mode 5", "Mode 6", "Mode 7", "Mode 8" }
|
local tank_modes = { "Mode 1", "Mode 2", "Mode 3", "Mode 4", "Mode 5", "Mode 6", "Mode 7", "Mode 8" }
|
||||||
@@ -399,33 +578,161 @@ function facility.create(tool_ctl, main_pane, cfg_sys, fac_cfg, style)
|
|||||||
|
|
||||||
--#endregion
|
--#endregion
|
||||||
|
|
||||||
|
local function next_from_tank_mode()
|
||||||
|
-- determine tank list and connections
|
||||||
|
tmp_cfg.FacilityTankList, tmp_cfg.FacilityTankConns = facility.generate_tank_list_and_conns(tmp_cfg.FacilityTankMode, tmp_cfg.FacilityTankDefs)
|
||||||
|
|
||||||
|
self.draw_fluid_ops()
|
||||||
|
|
||||||
|
fac_pane.set_value(7)
|
||||||
|
end
|
||||||
|
|
||||||
PushButton{parent=fac_c_5,x=1,y=14,text="\x1b Back",callback=function()fac_pane.set_value(4)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=fac_c_5,x=1,y=14,text="\x1b Back",callback=function()fac_pane.set_value(4)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
PushButton{parent=fac_c_5,x=44,y=14,text="Next \x1a",callback=function()fac_pane.set_value(7)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=fac_c_5,x=44,y=14,text="Next \x1a",callback=next_from_tank_mode,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
PushButton{parent=fac_c_5,x=8,y=14,min_width=7,text="About",callback=function()fac_pane.set_value(6)end,fg_bg=cpair(colors.black,colors.lightBlue),active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=fac_c_5,x=8,y=14,min_width=7,text="About",callback=function()fac_pane.set_value(6)end,fg_bg=cpair(colors.black,colors.lightBlue),active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
--#region Facility Tank Mode About
|
||||||
|
|
||||||
TextBox{parent=fac_c_6,height=3,text="This visualization tool shows the pipe connections required for a particular dynamic tank configuration you have selected."}
|
TextBox{parent=fac_c_6,height=3,text="This visualization tool shows the pipe connections required for a particular dynamic tank configuration you have selected."}
|
||||||
TextBox{parent=fac_c_6,y=5,height=4,text="Examples: A U2 tank should be configured on an RTU as the dynamic tank for unit #2. An F3 tank should be configured on an RTU as the #3 dynamic tank for the facility."}
|
TextBox{parent=fac_c_6,y=5,height=4,text="Examples: A U2 tank should be configured on an RTU as the dynamic tank for unit #2. An F3 tank should be configured on an RTU as the #3 dynamic tank for the facility."}
|
||||||
TextBox{parent=fac_c_6,y=10,height=3,text="Some modes may look the same if you are not using 4 total reactor units. The wiki has details. Modes that look the same will function the same.",fg_bg=g_lg_fg_bg}
|
TextBox{parent=fac_c_6,y=10,height=3,text="Some modes may look the same if you are not using 4 total reactor units. The wiki has details. Modes that look the same will function the same.",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
PushButton{parent=fac_c_6,x=1,y=14,text="\x1b Back",callback=function()fac_pane.set_value(5)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=fac_c_6,x=1,y=14,text="\x1b Back",callback=function()fac_pane.set_value(5)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=fac_c_7,height=6,text="Charge control provides automatic control to maintain an induction matrix charge level. In order to have smoother control, reactors that were activated will be held on at 0.01 mB/t for a short period before allowing them to turn off. This minimizes overshooting the charge target."}
|
--#endregion
|
||||||
TextBox{parent=fac_c_7,y=8,height=3,text="You can extend this to a full minute to minimize reactors flickering on/off, but there may be more overshoot of the target."}
|
--#region Dynamic Tank Fluid Types
|
||||||
|
|
||||||
local ext_idling = Checkbox{parent=fac_c_7,x=1,y=12,label="Enable Extended Idling",default=ini_cfg.ExtChargeIdling,box_fg_bg=cpair(colors.yellow,colors.black)}
|
TextBox{parent=fac_c_7,height=3,text="Specify each tank's coolant type, for display use only. Water is the only option if one or more of the connected units is water cooled."}
|
||||||
|
|
||||||
local function back_from_idling()
|
local tank_fluid_list = Div{parent=fac_c_7,x=1,y=5,height=8}
|
||||||
|
|
||||||
|
function self.draw_fluid_ops()
|
||||||
|
tank_fluid_list.remove_all()
|
||||||
|
|
||||||
|
local tank_list = tmp_cfg.FacilityTankList
|
||||||
|
local tank_conns = tmp_cfg.FacilityTankConns
|
||||||
|
|
||||||
|
local next_f = 1
|
||||||
|
|
||||||
|
for i = 1, #tank_list do
|
||||||
|
local type = tmp_cfg.TankFluidTypes[i]
|
||||||
|
|
||||||
|
if type == 0 then type = 1 end
|
||||||
|
|
||||||
|
self.tank_fluid_opts[i] = nil
|
||||||
|
|
||||||
|
if tank_list[i] == 1 then
|
||||||
|
local row = Div{parent=tank_fluid_list,height=2}
|
||||||
|
|
||||||
|
TextBox{parent=row,width=11,text="Unit Tank "..i}
|
||||||
|
TextBox{parent=row,text="Connected to: Unit "..i,fg_bg=cpair(colors.gray,colors.lightGray)}
|
||||||
|
|
||||||
|
local tank_fluid = Radio2D{parent=row,x=34,y=1,rows=1,columns=2,default=type,options={"Water","Sodium"},radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.yellow,disable_color=colors.gray,disable_fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
|
if tmp_cfg.CoolingConfig[i].BoilerCount == 0 then
|
||||||
|
tank_fluid.set_value(1)
|
||||||
|
tank_fluid.disable()
|
||||||
|
end
|
||||||
|
|
||||||
|
self.tank_fluid_opts[i] = tank_fluid
|
||||||
|
elseif tank_list[i] == 2 then
|
||||||
|
local row = Div{parent=tank_fluid_list,height=2}
|
||||||
|
|
||||||
|
TextBox{parent=row,width=15,text="Facility Tank "..next_f}
|
||||||
|
|
||||||
|
local conns = ""
|
||||||
|
local any_bwr = false
|
||||||
|
|
||||||
|
for u = 1, #tank_conns do
|
||||||
|
if tank_conns[u] == i then
|
||||||
|
conns = conns .. tri(conns == "", "", ", ") .. "Unit " .. u
|
||||||
|
any_bwr = any_bwr or (tmp_cfg.CoolingConfig[u].BoilerCount == 0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
TextBox{parent=row,text="Connected to: "..conns,fg_bg=cpair(colors.gray,colors.lightGray)}
|
||||||
|
|
||||||
|
local tank_fluid = Radio2D{parent=row,x=34,y=1,rows=1,columns=2,default=type,options={"Water","Sodium"},radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.yellow,disable_color=colors.gray,disable_fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
|
if any_bwr then
|
||||||
|
tank_fluid.set_value(1)
|
||||||
|
tank_fluid.disable()
|
||||||
|
end
|
||||||
|
|
||||||
|
self.tank_fluid_opts[i] = tank_fluid
|
||||||
|
|
||||||
|
next_f = next_f + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function back_from_fluids()
|
||||||
fac_pane.set_value(tri(tmp_cfg.FacilityTankMode == 0, 3, 5))
|
fac_pane.set_value(tri(tmp_cfg.FacilityTankMode == 0, 3, 5))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function submit_tank_fluids()
|
||||||
|
tmp_cfg.TankFluidTypes = {}
|
||||||
|
|
||||||
|
for i = 1, #tmp_cfg.FacilityTankList do
|
||||||
|
if self.tank_fluid_opts[i] ~= nil then
|
||||||
|
tmp_cfg.TankFluidTypes[i] = self.tank_fluid_opts[i].get_value()
|
||||||
|
else
|
||||||
|
tmp_cfg.TankFluidTypes[i] = 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
fac_pane.set_value(8)
|
||||||
|
end
|
||||||
|
|
||||||
|
PushButton{parent=fac_c_7,x=1,y=14,text="\x1b Back",callback=back_from_fluids,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
PushButton{parent=fac_c_7,x=44,y=14,text="Next \x1a",callback=submit_tank_fluids,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
--#region Auxiliary Coolant
|
||||||
|
|
||||||
|
TextBox{parent=fac_c_8,height=5,text="Auxiliary water coolant can be enabled for units to provide extra water during turbine ramp-up. For water cooled reactors, this goes to the reactor. For sodium cooled reactors, water goes to the boiler."}
|
||||||
|
|
||||||
|
for i = 1, 4 do
|
||||||
|
local line = Div{parent=fac_c_8,x=1,y=7+i,height=1}
|
||||||
|
|
||||||
|
TextBox{parent=line,text="Unit "..i.." -",width=8}
|
||||||
|
local aux_cool = Checkbox{parent=line,x=10,y=1,label="Has Auxiliary Coolant",default=ini_cfg.AuxiliaryCoolant[i],box_fg_bg=cpair(colors.yellow,colors.black)}
|
||||||
|
|
||||||
|
tool_ctl.aux_cool_elems[i] = { line = line, enable = aux_cool }
|
||||||
|
end
|
||||||
|
|
||||||
|
local function submit_aux_cool()
|
||||||
|
tmp_cfg.AuxiliaryCoolant = {}
|
||||||
|
|
||||||
|
for i = 1, tmp_cfg.UnitCount do
|
||||||
|
tmp_cfg.AuxiliaryCoolant[i] = tool_ctl.aux_cool_elems[i].enable.get_value()
|
||||||
|
end
|
||||||
|
|
||||||
|
fac_pane.set_value(9)
|
||||||
|
end
|
||||||
|
|
||||||
|
PushButton{parent=fac_c_8,x=1,y=14,text="\x1b Back",callback=function()fac_pane.set_value(7)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
PushButton{parent=fac_c_8,x=44,y=14,text="Next \x1a",callback=submit_aux_cool,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
--#region Extended Idling
|
||||||
|
|
||||||
|
TextBox{parent=fac_c_9,height=6,text="Charge control provides automatic control to maintain an induction matrix charge level. In order to have smoother control, reactors that were activated will be held on at 0.01 mB/t for a short period before allowing them to turn off. This minimizes overshooting the charge target."}
|
||||||
|
TextBox{parent=fac_c_9,y=8,height=3,text="You can extend this to a full minute to minimize reactors flickering on/off, but there may be more overshoot of the target."}
|
||||||
|
|
||||||
|
local ext_idling = Checkbox{parent=fac_c_9,x=1,y=12,label="Enable Extended Idling",default=ini_cfg.ExtChargeIdling,box_fg_bg=cpair(colors.yellow,colors.black)}
|
||||||
|
|
||||||
local function submit_idling()
|
local function submit_idling()
|
||||||
tmp_cfg.ExtChargeIdling = ext_idling.get_value()
|
tmp_cfg.ExtChargeIdling = ext_idling.get_value()
|
||||||
main_pane.set_value(3)
|
main_pane.set_value(3)
|
||||||
end
|
end
|
||||||
|
|
||||||
PushButton{parent=fac_c_7,x=1,y=14,text="\x1b Back",callback=back_from_idling,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=fac_c_9,x=1,y=14,text="\x1b Back",callback=function()fac_pane.set_value(8)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
PushButton{parent=fac_c_7,x=44,y=14,text="Next \x1a",callback=submit_idling,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=fac_c_9,x=44,y=14,text="Next \x1a",callback=submit_idling,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
--#endregion
|
--#endregion
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
local log = require("scada-common.log")
|
local log = require("scada-common.log")
|
||||||
|
local types = require("scada-common.types")
|
||||||
local util = require("scada-common.util")
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
|
local facility = require("supervisor.config.facility")
|
||||||
|
|
||||||
local core = require("graphics.core")
|
local core = require("graphics.core")
|
||||||
local themes = require("graphics.themes")
|
local themes = require("graphics.themes")
|
||||||
|
|
||||||
@@ -399,6 +402,10 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit
|
|||||||
try_set(tool_ctl.tank_elems[i].tank_opt, ini_cfg.FacilityTankDefs[i])
|
try_set(tool_ctl.tank_elems[i].tank_opt, ini_cfg.FacilityTankDefs[i])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
for i = 1, #ini_cfg.AuxiliaryCoolant do
|
||||||
|
try_set(tool_ctl.aux_cool_elems[i].enable, ini_cfg.AuxiliaryCoolant[i])
|
||||||
|
end
|
||||||
|
|
||||||
tool_ctl.en_fac_tanks.set_value(ini_cfg.FacilityTankMode > 0)
|
tool_ctl.en_fac_tanks.set_value(ini_cfg.FacilityTankMode > 0)
|
||||||
|
|
||||||
tool_ctl.view_cfg.enable()
|
tool_ctl.view_cfg.enable()
|
||||||
@@ -508,8 +515,15 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit
|
|||||||
else
|
else
|
||||||
tmp_cfg.FacilityTankMode = 0
|
tmp_cfg.FacilityTankMode = 0
|
||||||
tmp_cfg.FacilityTankDefs = {}
|
tmp_cfg.FacilityTankDefs = {}
|
||||||
|
|
||||||
|
-- on facility tank mode 0, setup tank defs to match unit tank option
|
||||||
|
for i = 1, tmp_cfg.UnitCount do
|
||||||
|
tmp_cfg.FacilityTankDefs[i] = tri(tmp_cfg.CoolingConfig[i].TankConnection, 1, 0)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
tmp_cfg.FacilityTankList, tmp_cfg.FacilityTankConns = facility.generate_tank_list_and_conns(tmp_cfg.FacilityTankMode, tmp_cfg.FacilityTankDefs)
|
||||||
|
|
||||||
tmp_cfg.SVR_Channel = config.SVR_CHANNEL
|
tmp_cfg.SVR_Channel = config.SVR_CHANNEL
|
||||||
tmp_cfg.PLC_Channel = config.PLC_CHANNEL
|
tmp_cfg.PLC_Channel = config.PLC_CHANNEL
|
||||||
tmp_cfg.RTU_Channel = config.RTU_CHANNEL
|
tmp_cfg.RTU_Channel = config.RTU_CHANNEL
|
||||||
@@ -557,6 +571,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit
|
|||||||
local val_max_w = (inner_width - label_w) + 1
|
local val_max_w = (inner_width - label_w) + 1
|
||||||
local raw = cfg[f[1]]
|
local raw = cfg[f[1]]
|
||||||
local val = util.strval(raw)
|
local val = util.strval(raw)
|
||||||
|
local skip = false
|
||||||
|
|
||||||
if f[1] == "AuthKey" then val = string.rep("*", string.len(val))
|
if f[1] == "AuthKey" then val = string.rep("*", string.len(val))
|
||||||
elseif f[1] == "LogMode" then val = tri(raw == log.MODE.APPEND, "append", "replace")
|
elseif f[1] == "LogMode" then val = tri(raw == log.MODE.APPEND, "append", "replace")
|
||||||
@@ -577,45 +592,114 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit
|
|||||||
end
|
end
|
||||||
|
|
||||||
if val == "" then val = "no facility tanks" end
|
if val == "" then val = "no facility tanks" end
|
||||||
elseif f[1] == "FacilityTankMode" and raw == 0 then val = "0 (n/a, unit mode)"
|
elseif f[1] == "FacilityTankMode" and raw == 0 then val = "no facility tanks"
|
||||||
elseif f[1] == "FacilityTankDefs" and type(cfg.FacilityTankDefs) == "table" then
|
elseif f[1] == "FacilityTankDefs" and type(cfg.FacilityTankDefs) == "table" then
|
||||||
|
local tank_name_list = { table.unpack(cfg.FacilityTankList) } ---@type (string|integer)[]
|
||||||
|
local next_f = 1
|
||||||
|
|
||||||
val = ""
|
val = ""
|
||||||
|
|
||||||
|
for idx = 1, #tank_name_list do
|
||||||
|
if tank_name_list[idx] == 1 then
|
||||||
|
tank_name_list[idx] = "U" .. idx
|
||||||
|
elseif tank_name_list[idx] == 2 then
|
||||||
|
tank_name_list[idx] = "F" .. next_f
|
||||||
|
next_f = next_f + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
for idx = 1, #cfg.FacilityTankDefs do
|
for idx = 1, #cfg.FacilityTankDefs do
|
||||||
local t_mode = "not connected to a tank"
|
local t_mode = "not connected to a tank"
|
||||||
if cfg.FacilityTankDefs[idx] == 1 then
|
if cfg.FacilityTankDefs[idx] == 1 then
|
||||||
t_mode = "connected to its unit tank"
|
t_mode = "connected to its unit tank (" .. tank_name_list[cfg.FacilityTankConns[idx]] .. ")"
|
||||||
elseif cfg.FacilityTankDefs[idx] == 2 then
|
elseif cfg.FacilityTankDefs[idx] == 2 then
|
||||||
t_mode = "connected to a facility tank"
|
t_mode = "connected to facility tank " .. tank_name_list[cfg.FacilityTankConns[idx]]
|
||||||
end
|
end
|
||||||
|
|
||||||
val = val .. tri(idx == 1, "", "\n") .. util.sprintf(" \x07 unit %d - %s", idx, t_mode)
|
val = val .. tri(idx == 1, "", "\n") .. util.sprintf(" \x07 unit %d - %s", idx, t_mode)
|
||||||
end
|
end
|
||||||
|
|
||||||
if val == "" then val = "no facility tanks" end
|
if val == "" then val = "no facility tanks" end
|
||||||
|
elseif f[1] == "FacilityTankList" or f[1] == "FacilityTankConns" then
|
||||||
|
-- hide these since this info is available in the FacilityTankDefs list (connections) and TankFluidTypes list (list of tanks)
|
||||||
|
skip = true
|
||||||
|
elseif f[1] == "TankFluidTypes" and type(cfg.TankFluidTypes) == "table" and type(cfg.FacilityTankList) == "table" then
|
||||||
|
local tank_list = cfg.FacilityTankList
|
||||||
|
local next_f = 1
|
||||||
|
|
||||||
|
val = ""
|
||||||
|
|
||||||
|
local count = 0
|
||||||
|
for idx = 1, #tank_list do
|
||||||
|
if tank_list[idx] > 0 then count = count + 1 end
|
||||||
|
end
|
||||||
|
|
||||||
|
local bullet = tri(count < 2, "", " \x07 ")
|
||||||
|
|
||||||
|
for idx = 1, #tank_list do
|
||||||
|
local prefix = "?"
|
||||||
|
local fluid = "water"
|
||||||
|
local type = cfg.TankFluidTypes[idx]
|
||||||
|
|
||||||
|
if tank_list[idx] > 0 then
|
||||||
|
if tank_list[idx] == 1 then
|
||||||
|
prefix = "U" .. idx
|
||||||
|
elseif tank_list[idx] == 2 then
|
||||||
|
prefix = "F" .. next_f
|
||||||
|
next_f = next_f + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
if type == types.COOLANT_TYPE.SODIUM then
|
||||||
|
fluid = "sodium"
|
||||||
|
end
|
||||||
|
|
||||||
|
val = val .. tri(val == "", "", "\n") .. util.sprintf(bullet .. "tank %s - %s", prefix, fluid)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if val == "" then val = "no emergency coolant tanks" end
|
||||||
|
elseif f[1] == "AuxiliaryCoolant" then
|
||||||
|
val = ""
|
||||||
|
|
||||||
|
local count = 0
|
||||||
|
for idx = 1, #cfg.AuxiliaryCoolant do
|
||||||
|
if cfg.AuxiliaryCoolant[idx] then count = count + 1 end
|
||||||
|
end
|
||||||
|
|
||||||
|
local bullet = tri(count < 2, "", " \x07 ")
|
||||||
|
|
||||||
|
for idx = 1, #cfg.AuxiliaryCoolant do
|
||||||
|
if cfg.AuxiliaryCoolant[idx] then
|
||||||
|
val = val .. tri(val == "", "", "\n") .. util.sprintf(bullet .. "unit %d", idx)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if val == "" then val = "no auxiliary coolant" end
|
||||||
end
|
end
|
||||||
|
|
||||||
if val == "nil" then val = "<not set>" end
|
if not skip then
|
||||||
|
if val == "nil" then val = "<not set>" end
|
||||||
|
|
||||||
local c = tri(alternate, g_lg_fg_bg, cpair(colors.gray,colors.white))
|
local c = tri(alternate, g_lg_fg_bg, cpair(colors.gray,colors.white))
|
||||||
alternate = not alternate
|
alternate = not alternate
|
||||||
|
|
||||||
if string.len(val) > val_max_w then
|
if (string.len(val) > val_max_w) or string.find(val, "\n") then
|
||||||
local lines = util.strwrap(val, inner_width)
|
local lines = util.strwrap(val, inner_width)
|
||||||
height = #lines + 1
|
height = #lines + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
local line = Div{parent=setting_list,height=height,fg_bg=c}
|
||||||
|
TextBox{parent=line,text=f[2],width=string.len(f[2]),fg_bg=cpair(colors.black,line.get_fg_bg().bkg)}
|
||||||
|
|
||||||
|
local textbox
|
||||||
|
if height > 1 then
|
||||||
|
textbox = TextBox{parent=line,x=1,y=2,text=val,height=height-1}
|
||||||
|
else
|
||||||
|
textbox = TextBox{parent=line,x=label_w+1,y=1,text=val,alignment=RIGHT}
|
||||||
|
end
|
||||||
|
|
||||||
|
if f[1] == "AuthKey" then self.auth_key_textbox = textbox end
|
||||||
end
|
end
|
||||||
|
|
||||||
local line = Div{parent=setting_list,height=height,fg_bg=c}
|
|
||||||
TextBox{parent=line,text=f[2],width=string.len(f[2]),fg_bg=cpair(colors.black,line.get_fg_bg().bkg)}
|
|
||||||
|
|
||||||
local textbox
|
|
||||||
if height > 1 then
|
|
||||||
textbox = TextBox{parent=line,x=1,y=2,text=val,height=height-1}
|
|
||||||
else
|
|
||||||
textbox = TextBox{parent=line,x=label_w+1,y=1,text=val,alignment=RIGHT}
|
|
||||||
end
|
|
||||||
|
|
||||||
if f[1] == "AuthKey" then self.auth_key_textbox = textbox end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ local CENTER = core.ALIGN.CENTER
|
|||||||
-- changes to the config data/format to let the user know
|
-- changes to the config data/format to let the user know
|
||||||
local changes = {
|
local changes = {
|
||||||
{ "v1.2.12", { "Added front panel UI theme", "Added color accessibility modes" } },
|
{ "v1.2.12", { "Added front panel UI theme", "Added color accessibility modes" } },
|
||||||
{ "v1.3.2", { "Added standard with black off state color mode", "Added blue indicator color modes" } }
|
{ "v1.3.2", { "Added standard with black off state color mode", "Added blue indicator color modes" } },
|
||||||
|
{ "v1.6.0", { "Added sodium emergency coolant option" } }
|
||||||
}
|
}
|
||||||
|
|
||||||
---@class svr_configurator
|
---@class svr_configurator
|
||||||
@@ -51,6 +52,7 @@ style.btn_dis_fg_bg = cpair(colors.lightGray, colors.white)
|
|||||||
|
|
||||||
---@class _svr_cfg_tool_ctl
|
---@class _svr_cfg_tool_ctl
|
||||||
local tool_ctl = {
|
local tool_ctl = {
|
||||||
|
launch_startup = false,
|
||||||
ask_config = false,
|
ask_config = false,
|
||||||
has_config = false,
|
has_config = false,
|
||||||
viewing_config = false,
|
viewing_config = false,
|
||||||
@@ -70,15 +72,20 @@ local tool_ctl = {
|
|||||||
load_legacy = nil, ---@type function
|
load_legacy = nil, ---@type function
|
||||||
|
|
||||||
cooling_elems = {}, ---@type { line: Div, turbines: NumberField, boilers: NumberField, tank: Checkbox }[]
|
cooling_elems = {}, ---@type { line: Div, turbines: NumberField, boilers: NumberField, tank: Checkbox }[]
|
||||||
tank_elems = {} ---@type { div: Div, tank_opt: Radio2D, no_tank: TextBox }[]
|
tank_elems = {}, ---@type { div: Div, tank_opt: Radio2D, no_tank: TextBox }[]
|
||||||
|
aux_cool_elems = {} ---@type { line: Div, enable: Checkbox }[]
|
||||||
}
|
}
|
||||||
|
|
||||||
---@class svr_config
|
---@class svr_config
|
||||||
local tmp_cfg = {
|
local tmp_cfg = {
|
||||||
UnitCount = 1,
|
UnitCount = 1,
|
||||||
CoolingConfig = {}, ---@type { TurbineCount: integer, BoilerCount: integer, TankConnection: boolean }[]
|
CoolingConfig = {}, ---@type { TurbineCount: integer, BoilerCount: integer, TankConnection: boolean }[]
|
||||||
FacilityTankMode = 0,
|
FacilityTankMode = 0, -- dynamic tank emergency coolant layout
|
||||||
FacilityTankDefs = {}, ---@type integer[]
|
FacilityTankDefs = {}, ---@type integer[] each unit's tank connection target (0 = disconnected, 1 = unit, 2 = facility)
|
||||||
|
FacilityTankList = {}, ---@type integer[] list of tanks by slot (0 = none or covered by an above tank, 1 = unit tank, 2 = facility tank)
|
||||||
|
FacilityTankConns = {}, ---@type integer[] map of unit tank connections (indicies are units, values are tank indicies in the tank list)
|
||||||
|
TankFluidTypes = {}, ---@type integer[] which type of fluid each tank in the tank list should be containing
|
||||||
|
AuxiliaryCoolant = {}, ---@type boolean[] if a unit has auxiliary coolant
|
||||||
ExtChargeIdling = false,
|
ExtChargeIdling = false,
|
||||||
SVR_Channel = nil, ---@type integer
|
SVR_Channel = nil, ---@type integer
|
||||||
PLC_Channel = nil, ---@type integer
|
PLC_Channel = nil, ---@type integer
|
||||||
@@ -109,6 +116,10 @@ local fields = {
|
|||||||
{ "CoolingConfig", "Cooling Configuration", {} },
|
{ "CoolingConfig", "Cooling Configuration", {} },
|
||||||
{ "FacilityTankMode", "Facility Tank Mode", 0 },
|
{ "FacilityTankMode", "Facility Tank Mode", 0 },
|
||||||
{ "FacilityTankDefs", "Facility Tank Definitions", {} },
|
{ "FacilityTankDefs", "Facility Tank Definitions", {} },
|
||||||
|
{ "FacilityTankList", "Facility Tank List", {} }, -- hidden
|
||||||
|
{ "FacilityTankConns", "Facility Tank Connections", {} }, -- hidden
|
||||||
|
{ "TankFluidTypes", "Tank Fluid Types", {} },
|
||||||
|
{ "AuxiliaryCoolant", "Auxiliary Water Coolant", {} },
|
||||||
{ "ExtChargeIdling", "Extended Charge Idling", false },
|
{ "ExtChargeIdling", "Extended Charge Idling", false },
|
||||||
{ "SVR_Channel", "SVR Channel", 16240 },
|
{ "SVR_Channel", "SVR Channel", 16240 },
|
||||||
{ "PLC_Channel", "PLC Channel", 16241 },
|
{ "PLC_Channel", "PLC Channel", 16241 },
|
||||||
@@ -175,7 +186,7 @@ local function config_view(display)
|
|||||||
TextBox{parent=main_page,x=2,y=2,height=2,text="Welcome to the Supervisor configurator! Please select one of the following options."}
|
TextBox{parent=main_page,x=2,y=2,height=2,text="Welcome to the Supervisor configurator! Please select one of the following options."}
|
||||||
|
|
||||||
if tool_ctl.ask_config then
|
if tool_ctl.ask_config then
|
||||||
TextBox{parent=main_page,x=2,y=y_start,height=4,width=49,text="Notice: This device had no valid config so the configurator has been automatically started. If you previously had a valid config, you may want to check the Change Log to see what changed.",fg_bg=cpair(colors.red,colors.lightGray)}
|
TextBox{parent=main_page,x=2,y=y_start,height=4,width=49,text="Notice: This device is not configured for this version of the supervisor. If you previously had a valid config, it's not lost. You may want to check the Change Log to see what changed.",fg_bg=cpair(colors.red,colors.lightGray)}
|
||||||
y_start = y_start + 5
|
y_start = y_start + 5
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -201,9 +212,17 @@ local function config_view(display)
|
|||||||
main_pane.set_value(5)
|
main_pane.set_value(5)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function startup()
|
||||||
|
tool_ctl.launch_startup = true
|
||||||
|
exit()
|
||||||
|
end
|
||||||
|
|
||||||
PushButton{parent=main_page,x=2,y=17,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=main_page,x=2,y=17,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=btn_act_fg_bg}
|
||||||
tool_ctl.color_cfg = PushButton{parent=main_page,x=23,y=17,min_width=15,text="Color Options",callback=jump_color,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
local start_btn = PushButton{parent=main_page,x=42,y=17,min_width=9,text="Startup",callback=startup,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
||||||
PushButton{parent=main_page,x=39,y=17,min_width=12,text="Change Log",callback=function()main_pane.set_value(7)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
tool_ctl.color_cfg = PushButton{parent=main_page,x=36,y=y_start,min_width=15,text="Color Options",callback=jump_color,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
||||||
|
PushButton{parent=main_page,x=39,y=y_start+2,min_width=12,text="Change Log",callback=function()main_pane.set_value(7)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
|
if tool_ctl.ask_config then start_btn.disable() end
|
||||||
|
|
||||||
if not tool_ctl.has_config then
|
if not tool_ctl.has_config then
|
||||||
tool_ctl.view_cfg.disable()
|
tool_ctl.view_cfg.disable()
|
||||||
@@ -266,6 +285,10 @@ function configurator.configure(ask_config)
|
|||||||
load_settings(settings_cfg, true)
|
load_settings(settings_cfg, true)
|
||||||
tool_ctl.has_config = load_settings(ini_cfg)
|
tool_ctl.has_config = load_settings(ini_cfg)
|
||||||
|
|
||||||
|
-- these need to be initialized as they are used before being set
|
||||||
|
tmp_cfg.FacilityTankMode = ini_cfg.FacilityTankMode
|
||||||
|
tmp_cfg.TankFluidTypes = { table.unpack(ini_cfg.TankFluidTypes) }
|
||||||
|
|
||||||
reset_term()
|
reset_term()
|
||||||
|
|
||||||
-- set overridden colors
|
-- set overridden colors
|
||||||
@@ -308,7 +331,7 @@ function configurator.configure(ask_config)
|
|||||||
println("configurator error: " .. error)
|
println("configurator error: " .. error)
|
||||||
end
|
end
|
||||||
|
|
||||||
return status, error
|
return status, error, tool_ctl.launch_startup
|
||||||
end
|
end
|
||||||
|
|
||||||
return configurator
|
return configurator
|
||||||
|
|||||||
@@ -2,13 +2,19 @@ local log = require("scada-common.log")
|
|||||||
local types = require("scada-common.types")
|
local types = require("scada-common.types")
|
||||||
local util = require("scada-common.util")
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
|
local alarm_ctl = require("supervisor.alarm_ctl")
|
||||||
local unit = require("supervisor.unit")
|
local unit = require("supervisor.unit")
|
||||||
local fac_update = require("supervisor.facility_update")
|
local fac_update = require("supervisor.facility_update")
|
||||||
|
|
||||||
local rsctl = require("supervisor.session.rsctl")
|
local rsctl = require("supervisor.session.rsctl")
|
||||||
local svsessions = require("supervisor.session.svsessions")
|
local svsessions = require("supervisor.session.svsessions")
|
||||||
|
|
||||||
|
local AISTATE = alarm_ctl.AISTATE
|
||||||
|
|
||||||
|
local ALARM = types.ALARM
|
||||||
|
local ALARM_STATE = types.ALARM_STATE
|
||||||
local AUTO_GROUP = types.AUTO_GROUP
|
local AUTO_GROUP = types.AUTO_GROUP
|
||||||
|
local PRIO = types.ALARM_PRIORITY
|
||||||
local PROCESS = types.PROCESS
|
local PROCESS = types.PROCESS
|
||||||
local RTU_ID_FAIL = types.RTU_ID_FAIL
|
local RTU_ID_FAIL = types.RTU_ID_FAIL
|
||||||
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
|
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
|
||||||
@@ -17,7 +23,7 @@ local WASTE = types.WASTE_PRODUCT
|
|||||||
---@enum AUTO_SCRAM
|
---@enum AUTO_SCRAM
|
||||||
local AUTO_SCRAM = {
|
local AUTO_SCRAM = {
|
||||||
NONE = 0,
|
NONE = 0,
|
||||||
MATRIX_DC = 1,
|
MATRIX_FAULT = 1,
|
||||||
MATRIX_FILL = 2,
|
MATRIX_FILL = 2,
|
||||||
CRIT_ALARM = 3,
|
CRIT_ALARM = 3,
|
||||||
RADIATION = 4,
|
RADIATION = 4,
|
||||||
@@ -31,6 +37,17 @@ local START_STATUS = {
|
|||||||
BLADE_MISMATCH = 2
|
BLADE_MISMATCH = 2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
---@enum RECOVERY_STATE
|
||||||
|
local RCV_STATE = {
|
||||||
|
INACTIVE = 0,
|
||||||
|
PRIMED = 1,
|
||||||
|
RUNNING = 2,
|
||||||
|
STOPPED = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
local CHARGE_SCALER = 1000000 -- convert MFE to FE
|
||||||
|
local GEN_SCALER = 1000 -- convert kFE to FE
|
||||||
|
|
||||||
---@class facility_management
|
---@class facility_management
|
||||||
local facility = {}
|
local facility = {}
|
||||||
|
|
||||||
@@ -41,7 +58,7 @@ function facility.new(config)
|
|||||||
---@class _facility_self
|
---@class _facility_self
|
||||||
local self = {
|
local self = {
|
||||||
units = {}, ---@type reactor_unit[]
|
units = {}, ---@type reactor_unit[]
|
||||||
types = { AUTO_SCRAM = AUTO_SCRAM, START_STATUS = START_STATUS },
|
types = { AUTO_SCRAM = AUTO_SCRAM, START_STATUS = START_STATUS, RCV_STATE = RCV_STATE },
|
||||||
status_text = { "START UP", "initializing..." },
|
status_text = { "START UP", "initializing..." },
|
||||||
all_sys_ok = false,
|
all_sys_ok = false,
|
||||||
allow_testing = false,
|
allow_testing = false,
|
||||||
@@ -51,7 +68,10 @@ function facility.new(config)
|
|||||||
r_cool = config.CoolingConfig,
|
r_cool = config.CoolingConfig,
|
||||||
fac_tank_mode = config.FacilityTankMode,
|
fac_tank_mode = config.FacilityTankMode,
|
||||||
fac_tank_defs = config.FacilityTankDefs,
|
fac_tank_defs = config.FacilityTankDefs,
|
||||||
fac_tank_list = {} ---@type integer[]
|
fac_tank_list = config.FacilityTankList,
|
||||||
|
fac_tank_conns = config.FacilityTankConns,
|
||||||
|
tank_fluid_types = config.TankFluidTypes,
|
||||||
|
aux_coolant = config.AuxiliaryCoolant
|
||||||
},
|
},
|
||||||
-- rtus
|
-- rtus
|
||||||
rtu_gw_conn_count = 0,
|
rtu_gw_conn_count = 0,
|
||||||
@@ -64,12 +84,15 @@ function facility.new(config)
|
|||||||
-- redstone I/O control
|
-- redstone I/O control
|
||||||
io_ctl = nil, ---@type rs_controller
|
io_ctl = nil, ---@type rs_controller
|
||||||
-- process control
|
-- process control
|
||||||
|
recovery = RCV_STATE.INACTIVE, ---@type RECOVERY_STATE
|
||||||
|
recovery_boot_state = nil, ---@type sv_boot_state|nil
|
||||||
|
last_unit_states = {}, ---@type boolean[]
|
||||||
units_ready = false,
|
units_ready = false,
|
||||||
mode = PROCESS.INACTIVE,
|
mode = PROCESS.INACTIVE, ---@type PROCESS
|
||||||
last_mode = PROCESS.INACTIVE,
|
last_mode = PROCESS.INACTIVE, ---@type PROCESS
|
||||||
return_mode = PROCESS.INACTIVE,
|
return_mode = PROCESS.INACTIVE, ---@type PROCESS
|
||||||
mode_set = PROCESS.MAX_BURN,
|
mode_set = PROCESS.MAX_BURN, ---@type PROCESS
|
||||||
start_fail = START_STATUS.OK,
|
start_fail = START_STATUS.OK, ---@type START_STATUS
|
||||||
max_burn_combined = 0.0, -- maximum burn rate to clamp at
|
max_burn_combined = 0.0, -- maximum burn rate to clamp at
|
||||||
burn_target = 0.1, -- burn rate target for aggregate burn mode
|
burn_target = 0.1, -- burn rate target for aggregate burn mode
|
||||||
charge_setpoint = 0, -- FE charge target setpoint
|
charge_setpoint = 0, -- FE charge target setpoint
|
||||||
@@ -81,7 +104,7 @@ function facility.new(config)
|
|||||||
ascram_reason = AUTO_SCRAM.NONE,
|
ascram_reason = AUTO_SCRAM.NONE,
|
||||||
---@class ascram_status
|
---@class ascram_status
|
||||||
ascram_status = {
|
ascram_status = {
|
||||||
matrix_dc = false,
|
matrix_fault = false,
|
||||||
matrix_fill = false,
|
matrix_fill = false,
|
||||||
crit_alarm = false,
|
crit_alarm = false,
|
||||||
radiation = false,
|
radiation = false,
|
||||||
@@ -91,16 +114,16 @@ function facility.new(config)
|
|||||||
charge_conversion = 1.0,
|
charge_conversion = 1.0,
|
||||||
time_start = 0.0,
|
time_start = 0.0,
|
||||||
initial_ramp = true,
|
initial_ramp = true,
|
||||||
waiting_on_ramp = false,
|
waiting_on_ramp = false, -- waiting on auto ramping
|
||||||
waiting_on_stable = false,
|
waiting_on_stable = false, -- waiting on gen rate stabilization
|
||||||
accumulator = 0.0,
|
accumulator = 0.0,
|
||||||
saturated = false,
|
saturated = false,
|
||||||
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 processing
|
||||||
waste_product = WASTE.PLUTONIUM,
|
waste_product = WASTE.PLUTONIUM, ---@type WASTE_PRODUCT
|
||||||
current_waste_product = WASTE.PLUTONIUM,
|
current_waste_product = WASTE.PLUTONIUM, ---@type WASTE_PRODUCT
|
||||||
pu_fallback = false,
|
pu_fallback = false,
|
||||||
sps_low_power = false,
|
sps_low_power = false,
|
||||||
disabled_sps = false,
|
disabled_sps = false,
|
||||||
@@ -121,24 +144,36 @@ function facility.new(config)
|
|||||||
imtx_last_charge = 0,
|
imtx_last_charge = 0,
|
||||||
imtx_last_charge_t = 0,
|
imtx_last_charge_t = 0,
|
||||||
-- track faulted induction matrix update times to reject
|
-- track faulted induction matrix update times to reject
|
||||||
imtx_faulted_times = { 0, 0, 0 }
|
imtx_faulted_times = { 0, 0, 0 },
|
||||||
|
-- facility alarms
|
||||||
|
---@type { [string]: alarm_def }
|
||||||
|
alarms = {
|
||||||
|
-- radiation monitor alarm for the facility
|
||||||
|
FacilityRadiation = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 0, id = ALARM.FacilityRadiation, tier = PRIO.CRITICAL },
|
||||||
|
},
|
||||||
|
---@type { [ALARM]: ALARM_STATE }
|
||||||
|
alarm_states = {
|
||||||
|
[ALARM.FacilityRadiation] = ALARM_STATE.INACTIVE
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
--#region SETUP
|
||||||
|
|
||||||
-- provide self to facility update functions
|
-- provide self to facility update functions
|
||||||
local f_update = fac_update(self)
|
local f_update = fac_update(self)
|
||||||
|
|
||||||
-- create units
|
-- create units
|
||||||
for i = 1, config.UnitCount do
|
for i = 1, config.UnitCount do
|
||||||
table.insert(self.units,
|
table.insert(self.units, unit.new(i, self.cooling_conf.r_cool[i].BoilerCount, self.cooling_conf.r_cool[i].TurbineCount, config.ExtChargeIdling, self.cooling_conf.aux_coolant[i]))
|
||||||
unit.new(i, self.cooling_conf.r_cool[i].BoilerCount, self.cooling_conf.r_cool[i].TurbineCount, config.ExtChargeIdling))
|
|
||||||
table.insert(self.group_map, AUTO_GROUP.MANUAL)
|
table.insert(self.group_map, AUTO_GROUP.MANUAL)
|
||||||
|
table.insert(self.last_unit_states, false)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- list for RTU session management
|
-- list for RTU session management
|
||||||
self.rtu_list = { self.redstone, self.induction, self.sps, self.tanks, self.envd }
|
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, 0)
|
||||||
|
|
||||||
-- fill blank alarm/tone states
|
-- fill blank alarm/tone states
|
||||||
for _ = 1, 12 do table.insert(self.test_alarm_states, false) end
|
for _ = 1, 12 do table.insert(self.test_alarm_states, false) end
|
||||||
@@ -147,99 +182,70 @@ function facility.new(config)
|
|||||||
table.insert(self.test_tone_states, false)
|
table.insert(self.test_tone_states, false)
|
||||||
end
|
end
|
||||||
|
|
||||||
--#region decode tank configuration
|
-- init next boot state
|
||||||
|
settings.set("LastProcessState", PROCESS.INACTIVE)
|
||||||
local cool_conf = self.cooling_conf
|
settings.set("LastUnitStates", self.last_unit_states)
|
||||||
|
if not settings.save("/supervisor.settings") then
|
||||||
-- determine tank information
|
log.warning("FAC: failed to save initial control state into supervisor settings file")
|
||||||
if cool_conf.fac_tank_mode == 0 then
|
|
||||||
cool_conf.fac_tank_defs = {}
|
|
||||||
|
|
||||||
-- on facility tank mode 0, setup tank defs to match unit tank option
|
|
||||||
for i = 1, config.UnitCount do
|
|
||||||
cool_conf.fac_tank_defs[i] = util.trinary(cool_conf.r_cool[i].TankConnection, 1, 0)
|
|
||||||
end
|
|
||||||
|
|
||||||
cool_conf.fac_tank_list = { table.unpack(cool_conf.fac_tank_defs) }
|
|
||||||
else
|
|
||||||
-- decode the layout of tanks from the connections definitions
|
|
||||||
local tank_mode = cool_conf.fac_tank_mode
|
|
||||||
local tank_defs = cool_conf.fac_tank_defs
|
|
||||||
local tank_list = { table.unpack(tank_defs) }
|
|
||||||
|
|
||||||
local function calc_fdef(start_idx, end_idx)
|
|
||||||
local first = 4
|
|
||||||
for i = start_idx, end_idx do
|
|
||||||
if tank_defs[i] == 2 then
|
|
||||||
if i < first then first = i end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return first
|
|
||||||
end
|
|
||||||
|
|
||||||
if tank_mode == 1 then
|
|
||||||
-- (1) 1 total facility tank (A A A A)
|
|
||||||
local first_fdef = calc_fdef(1, #tank_defs)
|
|
||||||
for i = 1, #tank_defs do
|
|
||||||
if i > first_fdef and tank_defs[i] == 2 then
|
|
||||||
tank_list[i] = 0
|
|
||||||
end
|
|
||||||
end
|
|
||||||
elseif tank_mode == 2 then
|
|
||||||
-- (2) 2 total facility tanks (A A A B)
|
|
||||||
local first_fdef = calc_fdef(1, math.min(3, #tank_defs))
|
|
||||||
for i = 1, #tank_defs do
|
|
||||||
if (i ~= 4) and (i > first_fdef) and (tank_defs[i] == 2) then
|
|
||||||
tank_list[i] = 0
|
|
||||||
end
|
|
||||||
end
|
|
||||||
elseif tank_mode == 3 then
|
|
||||||
-- (3) 2 total facility tanks (A A B B)
|
|
||||||
for _, a in pairs({ 1, 3 }) do
|
|
||||||
local b = a + 1
|
|
||||||
if (tank_defs[a] == 2) and (tank_defs[b] == 2) then
|
|
||||||
tank_list[b] = 0
|
|
||||||
end
|
|
||||||
end
|
|
||||||
elseif tank_mode == 4 then
|
|
||||||
-- (4) 2 total facility tanks (A B B B)
|
|
||||||
local first_fdef = calc_fdef(2, #tank_defs)
|
|
||||||
for i = 1, #tank_defs do
|
|
||||||
if (i ~= 1) and (i > first_fdef) and (tank_defs[i] == 2) then
|
|
||||||
tank_list[i] = 0
|
|
||||||
end
|
|
||||||
end
|
|
||||||
elseif tank_mode == 5 then
|
|
||||||
-- (5) 3 total facility tanks (A A B C)
|
|
||||||
local first_fdef = calc_fdef(1, math.min(2, #tank_defs))
|
|
||||||
for i = 1, #tank_defs do
|
|
||||||
if (not (i == 3 or i == 4)) and (i > first_fdef) and (tank_defs[i] == 2) then
|
|
||||||
tank_list[i] = 0
|
|
||||||
end
|
|
||||||
end
|
|
||||||
elseif tank_mode == 6 then
|
|
||||||
-- (6) 3 total facility tanks (A B B C)
|
|
||||||
local first_fdef = calc_fdef(2, math.min(3, #tank_defs))
|
|
||||||
for i = 1, #tank_defs do
|
|
||||||
if (not (i == 1 or i == 4)) and (i > first_fdef) and (tank_defs[i] == 2) then
|
|
||||||
tank_list[i] = 0
|
|
||||||
end
|
|
||||||
end
|
|
||||||
elseif tank_mode == 7 then
|
|
||||||
-- (7) 3 total facility tanks (A B C C)
|
|
||||||
local first_fdef = calc_fdef(3, #tank_defs)
|
|
||||||
for i = 1, #tank_defs do
|
|
||||||
if (not (i == 1 or i == 2)) and (i > first_fdef) and (tank_defs[i] == 2) then
|
|
||||||
tank_list[i] = 0
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
cool_conf.fac_tank_list = tank_list
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--#endregion
|
--#endregion
|
||||||
|
|
||||||
|
-- PRIVATE FUNCTIONS --
|
||||||
|
|
||||||
|
-- check an auto process control configuration and save it if its valid (does not start the process)
|
||||||
|
---@param auto_cfg start_auto_config configuration
|
||||||
|
---@return boolean ready, number[] unit_limits
|
||||||
|
local function _auto_check_and_save(auto_cfg)
|
||||||
|
local ready = false
|
||||||
|
|
||||||
|
-- load up current limits
|
||||||
|
local limits = {}
|
||||||
|
for i = 1, config.UnitCount do
|
||||||
|
limits[i] = self.units[i].get_control_inf().lim_br100 * 100
|
||||||
|
end
|
||||||
|
|
||||||
|
-- only allow changes if not running
|
||||||
|
if self.mode == PROCESS.INACTIVE then
|
||||||
|
if (type(auto_cfg.mode) == "number") and (auto_cfg.mode > PROCESS.INACTIVE) and (auto_cfg.mode <= PROCESS.GEN_RATE) then
|
||||||
|
self.mode_set = auto_cfg.mode
|
||||||
|
end
|
||||||
|
|
||||||
|
if (type(auto_cfg.burn_target) == "number") and auto_cfg.burn_target >= 0.1 then
|
||||||
|
self.burn_target = auto_cfg.burn_target
|
||||||
|
end
|
||||||
|
|
||||||
|
if (type(auto_cfg.charge_target) == "number") and auto_cfg.charge_target >= 0 then
|
||||||
|
self.charge_setpoint = auto_cfg.charge_target * CHARGE_SCALER
|
||||||
|
end
|
||||||
|
|
||||||
|
if (type(auto_cfg.gen_target) == "number") and auto_cfg.gen_target >= 0 then
|
||||||
|
self.gen_rate_setpoint = auto_cfg.gen_target * GEN_SCALER
|
||||||
|
end
|
||||||
|
|
||||||
|
if (type(auto_cfg.limits) == "table") and (#auto_cfg.limits == config.UnitCount) then
|
||||||
|
for i = 1, config.UnitCount do
|
||||||
|
local limit = auto_cfg.limits[i]
|
||||||
|
|
||||||
|
if (type(limit) == "number") and (limit >= 0.1) then
|
||||||
|
limits[i] = limit
|
||||||
|
self.units[i].set_burn_limit(limit)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
ready = self.mode_set > 0
|
||||||
|
|
||||||
|
if ((self.mode_set == PROCESS.CHARGE) and (self.charge_setpoint <= 0)) or
|
||||||
|
((self.mode_set == PROCESS.GEN_RATE) and (self.gen_rate_setpoint <= 0)) or
|
||||||
|
((self.mode_set == PROCESS.BURN_RATE) and (self.burn_target < 0.1)) then
|
||||||
|
ready = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return ready, limits
|
||||||
|
end
|
||||||
|
|
||||||
-- PUBLIC FUNCTIONS --
|
-- PUBLIC FUNCTIONS --
|
||||||
|
|
||||||
---@class facility
|
---@class facility
|
||||||
@@ -330,6 +336,9 @@ function facility.new(config)
|
|||||||
|
|
||||||
-- update (iterate) the facility management
|
-- update (iterate) the facility management
|
||||||
function public.update()
|
function public.update()
|
||||||
|
-- run reboot recovery routine if needed
|
||||||
|
f_update.boot_recovery()
|
||||||
|
|
||||||
-- run process control and evaluate automatic SCRAM
|
-- run process control and evaluate automatic SCRAM
|
||||||
f_update.pre_auto()
|
f_update.pre_auto()
|
||||||
f_update.auto_control(config.ExtChargeIdling)
|
f_update.auto_control(config.ExtChargeIdling)
|
||||||
@@ -342,6 +351,9 @@ function facility.new(config)
|
|||||||
-- unit tasks
|
-- unit tasks
|
||||||
f_update.unit_mgmt()
|
f_update.unit_mgmt()
|
||||||
|
|
||||||
|
-- update alarm states right before updating the audio
|
||||||
|
f_update.update_alarms()
|
||||||
|
|
||||||
-- update alarm tones
|
-- update alarm tones
|
||||||
f_update.alarm_audio()
|
f_update.alarm_audio()
|
||||||
end
|
end
|
||||||
@@ -358,6 +370,50 @@ function facility.new(config)
|
|||||||
|
|
||||||
--#endregion
|
--#endregion
|
||||||
|
|
||||||
|
--#region Startup Recovery
|
||||||
|
|
||||||
|
-- on exit, use this to clear the boot state so we don't resume when exiting cleanly
|
||||||
|
function public.clear_boot_state()
|
||||||
|
settings.unset("LastProcessState")
|
||||||
|
settings.unset("LastUnitStates")
|
||||||
|
|
||||||
|
if not settings.save("/supervisor.settings") then
|
||||||
|
log.warning("facility.clear_boot_state(): failed to save supervisor settings file")
|
||||||
|
else
|
||||||
|
log.debug("FAC: cleared boot state on exit")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- initialize facility resume boot recovery
|
||||||
|
---@param state sv_boot_state|nil
|
||||||
|
function public.boot_recovery_init(state)
|
||||||
|
if self.recovery == RCV_STATE.INACTIVE and state then
|
||||||
|
self.recovery_boot_state = state
|
||||||
|
self.recovery = RCV_STATE.PRIMED
|
||||||
|
log.info("FAC: startup resume ready")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- attempt facility resume boot recovery
|
||||||
|
---@param auto_cfg start_auto_config configuration
|
||||||
|
function public.boot_recovery_start(auto_cfg)
|
||||||
|
if self.recovery == RCV_STATE.PRIMED then
|
||||||
|
self.recovery = util.trinary(_auto_check_and_save(auto_cfg), RCV_STATE.RUNNING, RCV_STATE.STOPPED)
|
||||||
|
log.info(util.c("FAC: startup resume ", util.trinary(self.recovery == RCV_STATE.RUNNING, "started", "failed")))
|
||||||
|
else self.recovery = RCV_STATE.STOPPED end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- used on certain coordinator commands to end reboot recovery (remain in current operational state)
|
||||||
|
function public.cancel_recovery()
|
||||||
|
if self.recovery == RCV_STATE.RUNNING then
|
||||||
|
self.recovery = RCV_STATE.STOPPED
|
||||||
|
self.recovery_boot_state = nil
|
||||||
|
log.info("FAC: process startup resume cancelled by user operation")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
--#region Commands
|
--#region Commands
|
||||||
|
|
||||||
-- SCRAM all reactor units
|
-- SCRAM all reactor units
|
||||||
@@ -367,10 +423,14 @@ function facility.new(config)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- ack all alarms on all reactor units
|
-- ack all alarms on all reactor units and the facility
|
||||||
function public.ack_all()
|
function public.ack_all()
|
||||||
for i = 1, #self.units do
|
-- unit alarms
|
||||||
self.units[i].ack_all()
|
for i = 1, #self.units do self.units[i].ack_all() end
|
||||||
|
|
||||||
|
-- facility alarms
|
||||||
|
for id, state in pairs(self.alarm_states) do
|
||||||
|
if state == ALARM_STATE.TRIPPED then self.alarm_states[id] = ALARM_STATE.ACKED end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -381,59 +441,13 @@ function facility.new(config)
|
|||||||
function public.auto_stop() self.mode = PROCESS.INACTIVE end
|
function public.auto_stop() self.mode = PROCESS.INACTIVE end
|
||||||
|
|
||||||
-- set automatic control configuration and start the process
|
-- set automatic control configuration and start the process
|
||||||
---@param auto_cfg sys_auto_config configuration
|
---@param auto_cfg start_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(auto_cfg)
|
function public.auto_start(auto_cfg)
|
||||||
local charge_scaler = 1000000 -- convert MFE to FE
|
local ready, limits = _auto_check_and_save(auto_cfg)
|
||||||
local gen_scaler = 1000 -- convert kFE to FE
|
|
||||||
local ready = false
|
|
||||||
|
|
||||||
-- load up current limits
|
if ready and self.units_ready then
|
||||||
local limits = {}
|
self.mode = self.mode_set
|
||||||
for i = 1, config.UnitCount do
|
|
||||||
limits[i] = self.units[i].get_control_inf().lim_br100 * 100
|
|
||||||
end
|
|
||||||
|
|
||||||
-- only allow changes if not running
|
|
||||||
if self.mode == PROCESS.INACTIVE then
|
|
||||||
if (type(auto_cfg.mode) == "number") and (auto_cfg.mode > PROCESS.INACTIVE) and (auto_cfg.mode <= PROCESS.GEN_RATE) then
|
|
||||||
self.mode_set = auto_cfg.mode
|
|
||||||
end
|
|
||||||
|
|
||||||
if (type(auto_cfg.burn_target) == "number") and auto_cfg.burn_target >= 0.1 then
|
|
||||||
self.burn_target = auto_cfg.burn_target
|
|
||||||
end
|
|
||||||
|
|
||||||
if (type(auto_cfg.charge_target) == "number") and auto_cfg.charge_target >= 0 then
|
|
||||||
self.charge_setpoint = auto_cfg.charge_target * charge_scaler
|
|
||||||
end
|
|
||||||
|
|
||||||
if (type(auto_cfg.gen_target) == "number") and auto_cfg.gen_target >= 0 then
|
|
||||||
self.gen_rate_setpoint = auto_cfg.gen_target * gen_scaler
|
|
||||||
end
|
|
||||||
|
|
||||||
if (type(auto_cfg.limits) == "table") and (#auto_cfg.limits == config.UnitCount) then
|
|
||||||
for i = 1, config.UnitCount do
|
|
||||||
local limit = auto_cfg.limits[i]
|
|
||||||
|
|
||||||
if (type(limit) == "number") and (limit >= 0.1) then
|
|
||||||
limits[i] = limit
|
|
||||||
self.units[i].set_burn_limit(limit)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
ready = self.mode_set > 0
|
|
||||||
|
|
||||||
if ((self.mode_set == PROCESS.CHARGE) and (self.charge_setpoint <= 0)) or
|
|
||||||
((self.mode_set == PROCESS.GEN_RATE) and (self.gen_rate_setpoint <= 0)) or
|
|
||||||
((self.mode_set == PROCESS.BURN_RATE) and (self.burn_target < 0.1)) then
|
|
||||||
ready = false
|
|
||||||
end
|
|
||||||
|
|
||||||
ready = ready and self.units_ready
|
|
||||||
|
|
||||||
if ready then self.mode = self.mode_set end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
log.debug(util.c("FAC: process start ", util.trinary(ready, "accepted", "rejected")))
|
log.debug(util.c("FAC: process start ", util.trinary(ready, "accepted", "rejected")))
|
||||||
@@ -442,8 +456,8 @@ function facility.new(config)
|
|||||||
ready,
|
ready,
|
||||||
self.mode_set,
|
self.mode_set,
|
||||||
self.burn_target,
|
self.burn_target,
|
||||||
self.charge_setpoint / charge_scaler,
|
self.charge_setpoint / CHARGE_SCALER,
|
||||||
self.gen_rate_setpoint / gen_scaler,
|
self.gen_rate_setpoint / GEN_SCALER,
|
||||||
limits
|
limits
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
@@ -599,7 +613,7 @@ function facility.new(config)
|
|||||||
self.waiting_on_ramp or self.waiting_on_stable,
|
self.waiting_on_ramp or self.waiting_on_stable,
|
||||||
self.at_max_burn or self.saturated,
|
self.at_max_burn or self.saturated,
|
||||||
self.ascram,
|
self.ascram,
|
||||||
astat.matrix_dc,
|
astat.matrix_fault,
|
||||||
astat.matrix_fill,
|
astat.matrix_fill,
|
||||||
astat.crit_alarm,
|
astat.crit_alarm,
|
||||||
astat.radiation,
|
astat.radiation,
|
||||||
|
|||||||
@@ -1,17 +1,23 @@
|
|||||||
local audio = require("scada-common.audio")
|
local audio = require("scada-common.audio")
|
||||||
local const = require("scada-common.constants")
|
local const = require("scada-common.constants")
|
||||||
local log = require("scada-common.log")
|
local log = require("scada-common.log")
|
||||||
local rsio = require("scada-common.rsio")
|
local rsio = require("scada-common.rsio")
|
||||||
local types = require("scada-common.types")
|
local types = require("scada-common.types")
|
||||||
local util = require("scada-common.util")
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
local qtypes = require("supervisor.session.rtu.qtypes")
|
local alarm_ctl = require("supervisor.alarm_ctl")
|
||||||
|
|
||||||
|
local plc = require("supervisor.session.plc")
|
||||||
|
local svsessions = require("supervisor.session.svsessions")
|
||||||
|
|
||||||
|
local qtypes = require("supervisor.session.rtu.qtypes")
|
||||||
|
|
||||||
local TONE = audio.TONE
|
local TONE = audio.TONE
|
||||||
|
|
||||||
local ALARM = types.ALARM
|
local ALARM = types.ALARM
|
||||||
local PRIO = types.ALARM_PRIORITY
|
local PRIO = types.ALARM_PRIORITY
|
||||||
local ALARM_STATE = types.ALARM_STATE
|
local ALARM_STATE = types.ALARM_STATE
|
||||||
|
local AUTO_GROUP = types.AUTO_GROUP
|
||||||
local CONTAINER_MODE = types.CONTAINER_MODE
|
local CONTAINER_MODE = types.CONTAINER_MODE
|
||||||
local PROCESS = types.PROCESS
|
local PROCESS = types.PROCESS
|
||||||
local PROCESS_NAMES = types.PROCESS_NAMES
|
local PROCESS_NAMES = types.PROCESS_NAMES
|
||||||
@@ -131,6 +137,54 @@ end
|
|||||||
|
|
||||||
--#region PUBLIC FUNCTIONS
|
--#region PUBLIC FUNCTIONS
|
||||||
|
|
||||||
|
-- run reboot recovery routine if needed
|
||||||
|
function update.boot_recovery()
|
||||||
|
local RCV_STATE = self.types.RCV_STATE
|
||||||
|
|
||||||
|
-- attempt reboot recovery if in progress
|
||||||
|
if self.recovery == RCV_STATE.RUNNING then
|
||||||
|
local was_inactive = self.recovery_boot_state.mode == PROCESS.INACTIVE or self.recovery_boot_state.mode == PROCESS.SYSTEM_ALARM_IDLE
|
||||||
|
|
||||||
|
-- try to start auto control
|
||||||
|
if self.recovery_boot_state.mode ~= nil and self.units_ready then
|
||||||
|
if not was_inactive then
|
||||||
|
self.mode = self.mode_set
|
||||||
|
log.info("FAC: process startup resume initiated")
|
||||||
|
end
|
||||||
|
|
||||||
|
self.recovery_boot_state.mode = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local recovered = self.recovery_boot_state.mode == nil or was_inactive
|
||||||
|
|
||||||
|
-- restore manual control reactors
|
||||||
|
for i = 1, #self.units do
|
||||||
|
local u = self.units[i]
|
||||||
|
|
||||||
|
if self.recovery_boot_state.unit_states[i] and self.group_map[i] == AUTO_GROUP.MANUAL then
|
||||||
|
recovered = false
|
||||||
|
|
||||||
|
if u.get_control_inf().ready then
|
||||||
|
local plc_s = svsessions.get_reactor_session(i)
|
||||||
|
if plc_s ~= nil then
|
||||||
|
plc_s.in_queue.push_command(plc.PLC_S_CMDS.ENABLE)
|
||||||
|
log.info("FAC: startup resume enabling manually controlled reactor unit #" .. i)
|
||||||
|
|
||||||
|
-- only execute once
|
||||||
|
self.recovery_boot_state.unit_states[i] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if recovered then
|
||||||
|
self.recovery = RCV_STATE.STOPPED
|
||||||
|
self.recovery_boot_state = nil
|
||||||
|
log.info("FAC: startup resume sequence completed")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- automatic control pre-update logic
|
-- automatic control pre-update logic
|
||||||
function update.pre_auto()
|
function update.pre_auto()
|
||||||
-- unlink RTU sessions if they are closed
|
-- unlink RTU sessions if they are closed
|
||||||
@@ -243,6 +297,11 @@ function update.auto_control(ExtChargeIdling)
|
|||||||
|
|
||||||
log.debug(util.c("FAC: state changed from ", PROCESS_NAMES[self.last_mode + 1], " to ", PROCESS_NAMES[self.mode + 1]))
|
log.debug(util.c("FAC: state changed from ", PROCESS_NAMES[self.last_mode + 1], " to ", PROCESS_NAMES[self.mode + 1]))
|
||||||
|
|
||||||
|
settings.set("LastProcessState", self.mode)
|
||||||
|
if not settings.save("/supervisor.settings") then
|
||||||
|
log.warning("facility_update.auto_control(): failed to save supervisor settings file")
|
||||||
|
end
|
||||||
|
|
||||||
if (self.last_mode == PROCESS.INACTIVE) or (self.last_mode == PROCESS.GEN_RATE_FAULT_IDLE) then
|
if (self.last_mode == PROCESS.INACTIVE) or (self.last_mode == PROCESS.GEN_RATE_FAULT_IDLE) then
|
||||||
self.start_fail = START_STATUS.OK
|
self.start_fail = START_STATUS.OK
|
||||||
|
|
||||||
@@ -341,9 +400,17 @@ function update.auto_control(ExtChargeIdling)
|
|||||||
if state_changed then
|
if state_changed then
|
||||||
self.time_start = now
|
self.time_start = now
|
||||||
self.saturated = true
|
self.saturated = true
|
||||||
|
self.waiting_on_ramp = true
|
||||||
|
|
||||||
self.status_text = { "MONITORED MODE", "running reactors at limit" }
|
self.status_text = { "MONITORED MODE", "ramping reactors to limit" }
|
||||||
log.info("FAC: MAX_BURN process mode started")
|
log.info("FAC: MAX_BURN process mode started")
|
||||||
|
elseif self.waiting_on_ramp then
|
||||||
|
if all_units_ramped() then
|
||||||
|
self.waiting_on_ramp = false
|
||||||
|
|
||||||
|
self.status_text = { "MONITORED MODE", "running reactors at limit" }
|
||||||
|
log.info("FAC: MAX_BURN process mode initial ramp completed")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
allocate_burn_rate(self.max_burn_combined, true)
|
allocate_burn_rate(self.max_burn_combined, true)
|
||||||
@@ -351,8 +418,17 @@ function update.auto_control(ExtChargeIdling)
|
|||||||
-- a total aggregate burn rate
|
-- a total aggregate burn rate
|
||||||
if state_changed then
|
if state_changed then
|
||||||
self.time_start = now
|
self.time_start = now
|
||||||
self.status_text = { "BURN RATE MODE", "running" }
|
self.waiting_on_ramp = true
|
||||||
|
|
||||||
|
self.status_text = { "BURN RATE MODE", "ramping to target" }
|
||||||
log.info("FAC: BURN_RATE process mode started")
|
log.info("FAC: BURN_RATE process mode started")
|
||||||
|
elseif self.waiting_on_ramp then
|
||||||
|
if all_units_ramped() then
|
||||||
|
self.waiting_on_ramp = false
|
||||||
|
|
||||||
|
self.status_text = { "BURN RATE MODE", "running" }
|
||||||
|
log.info("FAC: BURN_RATE process mode initial ramp completed")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local unallocated = allocate_burn_rate(self.burn_target, true)
|
local unallocated = allocate_burn_rate(self.burn_target, true)
|
||||||
@@ -511,13 +587,19 @@ function update.auto_safety()
|
|||||||
|
|
||||||
local astatus = self.ascram_status
|
local astatus = self.ascram_status
|
||||||
|
|
||||||
|
-- matrix related checks
|
||||||
if self.induction[1] ~= nil then
|
if self.induction[1] ~= nil then
|
||||||
local db = self.induction[1].get_db()
|
local db = self.induction[1].get_db()
|
||||||
|
|
||||||
-- clear matrix disconnected
|
-- check for unformed or faulted state
|
||||||
if astatus.matrix_dc then
|
local i_ok = db.formed and not self.induction[1].is_faulted()
|
||||||
astatus.matrix_dc = false
|
|
||||||
log.info("FAC: induction matrix reconnected, clearing ASCRAM condition")
|
-- clear matrix fault if ok again
|
||||||
|
if astatus.matrix_fault and i_ok then
|
||||||
|
astatus.matrix_fault = false
|
||||||
|
log.info("FAC: induction matrix OK, clearing ASCRAM condition")
|
||||||
|
else
|
||||||
|
astatus.matrix_fault = not i_ok
|
||||||
end
|
end
|
||||||
|
|
||||||
-- check matrix fill too high
|
-- check matrix fill too high
|
||||||
@@ -528,42 +610,42 @@ function update.auto_safety()
|
|||||||
log.info(util.c("FAC: charge state of induction matrix entered acceptable range <= ", ALARM_LIMS.CHARGE_RE_ENABLE * 100, "%"))
|
log.info(util.c("FAC: charge state of induction matrix entered acceptable range <= ", ALARM_LIMS.CHARGE_RE_ENABLE * 100, "%"))
|
||||||
end
|
end
|
||||||
|
|
||||||
-- check for critical unit alarms
|
|
||||||
astatus.crit_alarm = false
|
|
||||||
for i = 1, #self.units do
|
|
||||||
local u = self.units[i]
|
|
||||||
|
|
||||||
if u.has_alarm_min_prio(PRIO.CRITICAL) then
|
|
||||||
astatus.crit_alarm = true
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- check for facility radiation
|
|
||||||
if #self.envd > 0 then
|
|
||||||
local max_rad = 0
|
|
||||||
|
|
||||||
for i = 1, #self.envd do
|
|
||||||
local envd = self.envd[i]
|
|
||||||
local e_db = envd.get_db()
|
|
||||||
if e_db.radiation_raw > max_rad then max_rad = e_db.radiation_raw end
|
|
||||||
end
|
|
||||||
|
|
||||||
astatus.radiation = max_rad >= ALARM_LIMS.FAC_HIGH_RAD
|
|
||||||
else
|
|
||||||
-- don't clear, if it is true then we lost it with high radiation, so just keep alarming
|
|
||||||
-- operator can restart the system or hit the stop/reset button
|
|
||||||
end
|
|
||||||
|
|
||||||
-- system not ready, will need to restart GEN_RATE mode
|
-- system not ready, will need to restart GEN_RATE mode
|
||||||
-- clears when we enter the fault waiting state
|
-- clears when we enter the fault waiting state
|
||||||
astatus.gen_fault = self.mode == PROCESS.GEN_RATE and not self.units_ready
|
astatus.gen_fault = self.mode == PROCESS.GEN_RATE and not self.units_ready
|
||||||
else
|
else
|
||||||
astatus.matrix_dc = true
|
astatus.matrix_fault = true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- check for critical unit alarms
|
||||||
|
astatus.crit_alarm = false
|
||||||
|
for i = 1, #self.units do
|
||||||
|
local u = self.units[i]
|
||||||
|
|
||||||
|
if u.has_alarm_min_prio(PRIO.CRITICAL) then
|
||||||
|
astatus.crit_alarm = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- check for facility radiation
|
||||||
|
if #self.envd > 0 then
|
||||||
|
local max_rad = 0
|
||||||
|
|
||||||
|
for i = 1, #self.envd do
|
||||||
|
local envd = self.envd[i]
|
||||||
|
local e_db = envd.get_db()
|
||||||
|
if e_db.radiation_raw > max_rad then max_rad = e_db.radiation_raw end
|
||||||
|
end
|
||||||
|
|
||||||
|
astatus.radiation = max_rad >= ALARM_LIMS.FAC_HIGH_RAD
|
||||||
|
else
|
||||||
|
-- don't clear, if it is true then we lost it with high radiation, so just keep alarming
|
||||||
|
-- operator can restart the system or hit the stop/reset button
|
||||||
end
|
end
|
||||||
|
|
||||||
if (self.mode ~= PROCESS.INACTIVE) and (self.mode ~= PROCESS.SYSTEM_ALARM_IDLE) then
|
if (self.mode ~= PROCESS.INACTIVE) and (self.mode ~= PROCESS.SYSTEM_ALARM_IDLE) then
|
||||||
local scram = astatus.matrix_dc or astatus.matrix_fill or astatus.crit_alarm or astatus.gen_fault
|
local scram = astatus.matrix_fault or astatus.matrix_fill or astatus.crit_alarm or astatus.radiation or astatus.gen_fault
|
||||||
|
|
||||||
if scram and not self.ascram then
|
if scram and not self.ascram then
|
||||||
-- SCRAM all units
|
-- SCRAM all units
|
||||||
@@ -587,14 +669,14 @@ function update.auto_safety()
|
|||||||
self.status_text = { "AUTOMATIC SCRAM", "facility radiation high" }
|
self.status_text = { "AUTOMATIC SCRAM", "facility radiation high" }
|
||||||
|
|
||||||
log.info("FAC: automatic SCRAM due to high facility radiation")
|
log.info("FAC: automatic SCRAM due to high facility radiation")
|
||||||
elseif astatus.matrix_dc then
|
elseif astatus.matrix_fault then
|
||||||
next_mode = PROCESS.MATRIX_FAULT_IDLE
|
next_mode = PROCESS.MATRIX_FAULT_IDLE
|
||||||
self.ascram_reason = AUTO_SCRAM.MATRIX_DC
|
self.ascram_reason = AUTO_SCRAM.MATRIX_FAULT
|
||||||
self.status_text = { "AUTOMATIC SCRAM", "induction matrix disconnected" }
|
self.status_text = { "AUTOMATIC SCRAM", "induction matrix fault" }
|
||||||
|
|
||||||
if self.mode ~= PROCESS.MATRIX_FAULT_IDLE then self.return_mode = self.mode end
|
if self.mode ~= PROCESS.MATRIX_FAULT_IDLE then self.return_mode = self.mode end
|
||||||
|
|
||||||
log.info("FAC: automatic SCRAM due to induction matrix disconnection")
|
log.info("FAC: automatic SCRAM due to induction matrix disconnected, unformed, or faulted")
|
||||||
elseif astatus.matrix_fill then
|
elseif astatus.matrix_fill then
|
||||||
next_mode = PROCESS.MATRIX_FAULT_IDLE
|
next_mode = PROCESS.MATRIX_FAULT_IDLE
|
||||||
self.ascram_reason = AUTO_SCRAM.MATRIX_FILL
|
self.ascram_reason = AUTO_SCRAM.MATRIX_FILL
|
||||||
@@ -619,25 +701,32 @@ function update.auto_safety()
|
|||||||
self.ascram_reason = AUTO_SCRAM.NONE
|
self.ascram_reason = AUTO_SCRAM.NONE
|
||||||
|
|
||||||
-- reset PLC RPS trips if we should
|
-- reset PLC RPS trips if we should
|
||||||
for i = 1, #self.units do
|
for i = 1, #self.prio_defs do
|
||||||
local u = self.units[i]
|
for _, u in pairs(self.prio_defs[i]) do
|
||||||
u.auto_cond_rps_reset()
|
u.auto_cond_rps_reset()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- update last mode and set next mode
|
-- update last mode, set next mode, and update saved state as needed
|
||||||
function update.post_auto()
|
function update.post_auto()
|
||||||
self.last_mode = self.mode
|
self.last_mode = self.mode
|
||||||
self.mode = next_mode
|
self.mode = next_mode
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- update facility alarm states
|
||||||
|
function update.update_alarms()
|
||||||
|
-- Facility Radiation
|
||||||
|
alarm_ctl.update_alarm_state("FAC", self.alarm_states, self.ascram_status.radiation, self.alarms.FacilityRadiation, true)
|
||||||
|
end
|
||||||
|
|
||||||
-- update alarm audio control
|
-- update alarm audio control
|
||||||
function update.alarm_audio()
|
function update.alarm_audio()
|
||||||
local allow_test = self.allow_testing and self.test_tone_set
|
local allow_test = self.allow_testing and self.test_tone_set
|
||||||
|
|
||||||
local alarms = { false, false, false, false, false, false, false, false, false, false, false, false }
|
local alarms = { false, false, false, false, false, false, false, false, false, false, false, false, false }
|
||||||
|
|
||||||
-- reset tone states before re-evaluting
|
-- reset tone states before re-evaluting
|
||||||
for i = 1, #self.tone_states do self.tone_states[i] = false end
|
for i = 1, #self.tone_states do self.tone_states[i] = false end
|
||||||
@@ -653,8 +742,11 @@ function update.alarm_audio()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- record facility alarms
|
||||||
|
alarms[ALARM.FacilityRadiation] = self.alarm_states[ALARM.FacilityRadiation] == ALARM_STATE.TRIPPED
|
||||||
|
|
||||||
|
-- clear testing alarms if we aren't using them
|
||||||
if not self.test_tone_reset then
|
if not self.test_tone_reset then
|
||||||
-- clear testing alarms if we aren't using them
|
|
||||||
for i = 1, #self.test_alarm_states do self.test_alarm_states[i] = false end
|
for i = 1, #self.test_alarm_states do self.test_alarm_states[i] = false end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -693,7 +785,7 @@ function update.alarm_audio()
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- radiation is a big concern, always play this CRITICAL level alarm if active
|
-- radiation is a big concern, always play this CRITICAL level alarm if active
|
||||||
if alarms[ALARM.ContainmentRadiation] then
|
if alarms[ALARM.ContainmentRadiation] or alarms[ALARM.FacilityRadiation] then
|
||||||
self.tone_states[TONE.T_800Hz_1000Hz_Alt] = true
|
self.tone_states[TONE.T_800Hz_1000Hz_Alt] = true
|
||||||
-- we are going to disable the RPS trip alarm audio due to conflict, and if it was enabled
|
-- we are going to disable the RPS trip alarm audio due to conflict, and if it was enabled
|
||||||
-- then we can re-enable the reactor lost alarm audio since it doesn't painfully combine with this one
|
-- then we can re-enable the reactor lost alarm audio since it doesn't painfully combine with this one
|
||||||
@@ -769,6 +861,7 @@ end
|
|||||||
function update.unit_mgmt()
|
function update.unit_mgmt()
|
||||||
local insufficent_po_rate = false
|
local insufficent_po_rate = false
|
||||||
local need_emcool = false
|
local need_emcool = false
|
||||||
|
local write_state = false
|
||||||
|
|
||||||
for i = 1, #self.units do
|
for i = 1, #self.units do
|
||||||
local u = self.units[i]
|
local u = self.units[i]
|
||||||
@@ -784,6 +877,21 @@ function update.unit_mgmt()
|
|||||||
if (self.cooling_conf.fac_tank_mode > 0) and u.is_emer_cool_tripped() and (self.cooling_conf.fac_tank_defs[i] == 2) then
|
if (self.cooling_conf.fac_tank_mode > 0) and u.is_emer_cool_tripped() and (self.cooling_conf.fac_tank_defs[i] == 2) then
|
||||||
need_emcool = true
|
need_emcool = true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- check for enabled state changes to save
|
||||||
|
if self.last_unit_states[i] ~= u.is_reactor_enabled() then
|
||||||
|
self.last_unit_states[i] = u.is_reactor_enabled()
|
||||||
|
write_state = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- record unit control states
|
||||||
|
|
||||||
|
if write_state then
|
||||||
|
settings.set("LastUnitStates", self.last_unit_states)
|
||||||
|
if not settings.save("/supervisor.settings") then
|
||||||
|
log.warning("facility_update.unit_mgmt(): failed to save supervisor settings file")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- update waste product
|
-- update waste product
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ local function init(parent, id)
|
|||||||
|
|
||||||
local label_fg = style.fp.label_fg
|
local label_fg = style.fp.label_fg
|
||||||
|
|
||||||
|
local term_w, _ = term.getSize()
|
||||||
|
|
||||||
-- root div
|
-- root div
|
||||||
local root = Div{parent=parent,x=2,y=2,height=4,width=parent.get_width()-2}
|
local root = Div{parent=parent,x=2,y=2,height=4,width=parent.get_width()-2}
|
||||||
local entry = Div{parent=root,x=2,y=1,height=3,fg_bg=style.theme.highlight_box_bright}
|
local entry = Div{parent=root,x=2,y=1,height=3,fg_bg=style.theme.highlight_box_bright}
|
||||||
@@ -40,9 +42,9 @@ local function init(parent, id)
|
|||||||
local pdg_fw_v = TextBox{parent=entry,x=14,y=2,text=" ------- ",width=20,fg_bg=label_fg}
|
local pdg_fw_v = TextBox{parent=entry,x=14,y=2,text=" ------- ",width=20,fg_bg=label_fg}
|
||||||
pdg_fw_v.register(databus.ps, ps_prefix .. "fw", pdg_fw_v.set_value)
|
pdg_fw_v.register(databus.ps, ps_prefix .. "fw", pdg_fw_v.set_value)
|
||||||
|
|
||||||
TextBox{parent=entry,x=35,y=2,text="RTT:",width=4}
|
TextBox{parent=entry,x=term_w-16,y=2,text="RTT:",width=4}
|
||||||
local pdg_rtt = DataIndicator{parent=entry,x=40,y=2,label="",unit="",format="%5d",value=0,width=5,fg_bg=label_fg}
|
local pdg_rtt = DataIndicator{parent=entry,x=term_w-11,y=2,label="",unit="",format="%5d",value=0,width=5,fg_bg=label_fg}
|
||||||
TextBox{parent=entry,x=46,y=2,text="ms",width=4,fg_bg=label_fg}
|
TextBox{parent=entry,x=term_w-5,y=2,text="ms",width=4,fg_bg=label_fg}
|
||||||
pdg_rtt.register(databus.ps, ps_prefix .. "rtt", pdg_rtt.update)
|
pdg_rtt.register(databus.ps, ps_prefix .. "rtt", pdg_rtt.update)
|
||||||
pdg_rtt.register(databus.ps, ps_prefix .. "rtt_color", pdg_rtt.recolor)
|
pdg_rtt.register(databus.ps, ps_prefix .. "rtt_color", pdg_rtt.recolor)
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ local function init(parent, id)
|
|||||||
|
|
||||||
local label_fg = style.fp.label_fg
|
local label_fg = style.fp.label_fg
|
||||||
|
|
||||||
|
local term_w, _ = term.getSize()
|
||||||
|
|
||||||
-- root div
|
-- root div
|
||||||
local root = Div{parent=parent,x=2,y=2,height=4,width=parent.get_width()-2}
|
local root = Div{parent=parent,x=2,y=2,height=4,width=parent.get_width()-2}
|
||||||
local entry = Div{parent=root,x=2,y=1,height=3,fg_bg=style.theme.highlight_box_bright}
|
local entry = Div{parent=root,x=2,y=1,height=3,fg_bg=style.theme.highlight_box_bright}
|
||||||
@@ -40,13 +42,13 @@ local function init(parent, id)
|
|||||||
local unit_count = DataIndicator{parent=entry,x=17,y=2,label="",unit="",format="%2d",value=0,width=2,fg_bg=style.fp.label_d_fg}
|
local unit_count = DataIndicator{parent=entry,x=17,y=2,label="",unit="",format="%2d",value=0,width=2,fg_bg=style.fp.label_d_fg}
|
||||||
unit_count.register(databus.ps, ps_prefix .. "units", unit_count.set_value)
|
unit_count.register(databus.ps, ps_prefix .. "units", unit_count.set_value)
|
||||||
|
|
||||||
TextBox{parent=entry,x=21,y=2,text="FW:",width=3}
|
TextBox{parent=entry,x=term_w-30,y=2,text="FW:",width=3}
|
||||||
local rtu_fw_v = TextBox{parent=entry,x=25,y=2,text=" ------- ",width=9,fg_bg=label_fg}
|
local rtu_fw_v = TextBox{parent=entry,x=term_w-26,y=2,text=" ------- ",width=9,fg_bg=label_fg}
|
||||||
rtu_fw_v.register(databus.ps, ps_prefix .. "fw", rtu_fw_v.set_value)
|
rtu_fw_v.register(databus.ps, ps_prefix .. "fw", rtu_fw_v.set_value)
|
||||||
|
|
||||||
TextBox{parent=entry,x=36,y=2,text="RTT:",width=4}
|
TextBox{parent=entry,x=term_w-15,y=2,text="RTT:",width=4}
|
||||||
local rtu_rtt = DataIndicator{parent=entry,x=40,y=2,label="",unit="",format="%5d",value=0,width=5,fg_bg=label_fg}
|
local rtu_rtt = DataIndicator{parent=entry,x=term_w-11,y=2,label="",unit="",format="%5d",value=0,width=5,fg_bg=label_fg}
|
||||||
TextBox{parent=entry,x=46,y=2,text="ms",width=4,fg_bg=label_fg}
|
TextBox{parent=entry,x=term_w-5,y=2,text="ms",width=4,fg_bg=label_fg}
|
||||||
rtu_rtt.register(databus.ps, ps_prefix .. "rtt", rtu_rtt.update)
|
rtu_rtt.register(databus.ps, ps_prefix .. "rtt", rtu_rtt.update)
|
||||||
rtu_rtt.register(databus.ps, ps_prefix .. "rtt_color", rtu_rtt.recolor)
|
rtu_rtt.register(databus.ps, ps_prefix .. "rtt_color", rtu_rtt.recolor)
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ local function init(panel)
|
|||||||
local label_fg = style.fp.label_fg
|
local label_fg = style.fp.label_fg
|
||||||
local label_d_fg = style.fp.label_d_fg
|
local label_d_fg = style.fp.label_d_fg
|
||||||
|
|
||||||
|
local term_w, term_h = term.getSize()
|
||||||
|
|
||||||
TextBox{parent=panel,y=1,text="SCADA SUPERVISOR",alignment=ALIGN.CENTER,fg_bg=style.theme.header}
|
TextBox{parent=panel,y=1,text="SCADA SUPERVISOR",alignment=ALIGN.CENTER,fg_bg=style.theme.header}
|
||||||
|
|
||||||
local page_div = Div{parent=panel,x=1,y=3}
|
local page_div = Div{parent=panel,x=1,y=3}
|
||||||
@@ -73,9 +75,9 @@ local function init(panel)
|
|||||||
-- about footer
|
-- about footer
|
||||||
--
|
--
|
||||||
|
|
||||||
local about = Div{parent=main_page,width=15,height=3,x=1,y=16,fg_bg=style.fp.disabled_fg}
|
local about = Div{parent=main_page,width=15,height=2,y=term_h-3,fg_bg=style.fp.disabled_fg}
|
||||||
local fw_v = TextBox{parent=about,x=1,y=1,text="FW: v00.00.00"}
|
local fw_v = TextBox{parent=about,text="FW: v00.00.00"}
|
||||||
local comms_v = TextBox{parent=about,x=1,y=2,text="NT: v00.00.00"}
|
local comms_v = TextBox{parent=about,text="NT: v00.00.00"}
|
||||||
|
|
||||||
fw_v.register(databus.ps, "version", function (version) fw_v.set_value(util.c("FW: ", version)) end)
|
fw_v.register(databus.ps, "version", function (version) fw_v.set_value(util.c("FW: ", version)) end)
|
||||||
comms_v.register(databus.ps, "comms_version", function (version) comms_v.set_value(util.c("NT: v", version)) end)
|
comms_v.register(databus.ps, "comms_version", function (version) comms_v.set_value(util.c("NT: v", version)) end)
|
||||||
@@ -87,7 +89,7 @@ local function init(panel)
|
|||||||
-- plc sessions page
|
-- plc sessions page
|
||||||
|
|
||||||
local plc_page = Div{parent=page_div,x=1,y=1,hidden=true}
|
local plc_page = Div{parent=page_div,x=1,y=1,hidden=true}
|
||||||
local plc_list = Div{parent=plc_page,x=2,y=2,width=49}
|
local plc_list = Div{parent=plc_page,x=2,y=2,width=term_w-2}
|
||||||
|
|
||||||
for i = 1, supervisor.config.UnitCount do
|
for i = 1, supervisor.config.UnitCount do
|
||||||
local ps_prefix = "plc_" .. i .. "_"
|
local ps_prefix = "plc_" .. i .. "_"
|
||||||
@@ -103,13 +105,13 @@ local function init(panel)
|
|||||||
local plc_addr = TextBox{parent=plc_entry,x=17,y=2,text=" --- ",width=5,fg_bg=label_d_fg}
|
local plc_addr = TextBox{parent=plc_entry,x=17,y=2,text=" --- ",width=5,fg_bg=label_d_fg}
|
||||||
plc_addr.register(databus.ps, ps_prefix .. "addr", plc_addr.set_value)
|
plc_addr.register(databus.ps, ps_prefix .. "addr", plc_addr.set_value)
|
||||||
|
|
||||||
TextBox{parent=plc_entry,x=23,y=2,text="FW:",width=3}
|
TextBox{parent=plc_entry,x=term_w-28,y=2,text="FW:",width=3}
|
||||||
local plc_fw_v = TextBox{parent=plc_entry,x=27,y=2,text=" ------- ",width=9,fg_bg=label_fg}
|
local plc_fw_v = TextBox{parent=plc_entry,x=term_w-24,y=2,text=" ------- ",width=9,fg_bg=label_fg}
|
||||||
plc_fw_v.register(databus.ps, ps_prefix .. "fw", plc_fw_v.set_value)
|
plc_fw_v.register(databus.ps, ps_prefix .. "fw", plc_fw_v.set_value)
|
||||||
|
|
||||||
TextBox{parent=plc_entry,x=37,y=2,text="RTT:",width=4}
|
TextBox{parent=plc_entry,x=term_w-14,y=2,text="RTT:",width=4}
|
||||||
local plc_rtt = DataIndicator{parent=plc_entry,x=42,y=2,label="",unit="",format="%4d",value=0,width=4,fg_bg=label_fg}
|
local plc_rtt = DataIndicator{parent=plc_entry,x=term_w-9,y=2,label="",unit="",format="%4d",value=0,width=4,fg_bg=label_fg}
|
||||||
TextBox{parent=plc_entry,x=47,y=2,text="ms",width=4,fg_bg=label_fg}
|
TextBox{parent=plc_entry,x=term_w-4,y=2,text="ms",width=4,fg_bg=label_fg}
|
||||||
plc_rtt.register(databus.ps, ps_prefix .. "rtt", plc_rtt.update)
|
plc_rtt.register(databus.ps, ps_prefix .. "rtt", plc_rtt.update)
|
||||||
plc_rtt.register(databus.ps, ps_prefix .. "rtt_color", plc_rtt.recolor)
|
plc_rtt.register(databus.ps, ps_prefix .. "rtt_color", plc_rtt.recolor)
|
||||||
|
|
||||||
@@ -119,13 +121,13 @@ local function init(panel)
|
|||||||
-- rtu sessions page
|
-- rtu sessions page
|
||||||
|
|
||||||
local rtu_page = Div{parent=page_div,x=1,y=1,hidden=true}
|
local rtu_page = Div{parent=page_div,x=1,y=1,hidden=true}
|
||||||
local rtu_list = ListBox{parent=rtu_page,x=1,y=1,height=17,width=51,scroll_height=1000,fg_bg=cpair(colors.black,colors.ivory),nav_fg_bg=cpair(colors.gray,colors.lightGray),nav_active=cpair(colors.black,colors.gray)}
|
local rtu_list = ListBox{parent=rtu_page,y=1,height=term_h-2,width=term_w,scroll_height=1000,fg_bg=cpair(colors.black,colors.ivory),nav_fg_bg=cpair(colors.gray,colors.lightGray),nav_active=cpair(colors.black,colors.gray)}
|
||||||
local _ = Div{parent=rtu_list,height=1} -- padding
|
local _ = Div{parent=rtu_list,height=1} -- padding
|
||||||
|
|
||||||
-- coordinator session page
|
-- coordinator session page
|
||||||
|
|
||||||
local crd_page = Div{parent=page_div,x=1,y=1,hidden=true}
|
local crd_page = Div{parent=page_div,x=1,y=1,hidden=true}
|
||||||
local crd_box = Div{parent=crd_page,x=2,y=2,width=49,height=4,fg_bg=s_hi_bright}
|
local crd_box = Div{parent=crd_page,x=2,y=2,width=term_w-2,height=4,fg_bg=s_hi_bright}
|
||||||
|
|
||||||
local crd_conn = LED{parent=crd_box,x=2,y=2,label="CONNECTION",colors=cpair(colors.green_hc,colors.green_off)}
|
local crd_conn = LED{parent=crd_box,x=2,y=2,label="CONNECTION",colors=cpair(colors.green_hc,colors.green_off)}
|
||||||
crd_conn.register(databus.ps, "crd_conn", crd_conn.update)
|
crd_conn.register(databus.ps, "crd_conn", crd_conn.update)
|
||||||
@@ -138,27 +140,27 @@ local function init(panel)
|
|||||||
local crd_fw_v = TextBox{parent=crd_box,x=26,y=2,text=" ------- ",width=9,fg_bg=label_fg}
|
local crd_fw_v = TextBox{parent=crd_box,x=26,y=2,text=" ------- ",width=9,fg_bg=label_fg}
|
||||||
crd_fw_v.register(databus.ps, "crd_fw", crd_fw_v.set_value)
|
crd_fw_v.register(databus.ps, "crd_fw", crd_fw_v.set_value)
|
||||||
|
|
||||||
TextBox{parent=crd_box,x=36,y=2,text="RTT:",width=4}
|
TextBox{parent=crd_box,x=term_w-15,y=2,text="RTT:",width=4}
|
||||||
local crd_rtt = DataIndicator{parent=crd_box,x=41,y=2,label="",unit="",format="%5d",value=0,width=5,fg_bg=label_fg}
|
local crd_rtt = DataIndicator{parent=crd_box,x=term_w-10,y=2,label="",unit="",format="%5d",value=0,width=5,fg_bg=label_fg}
|
||||||
TextBox{parent=crd_box,x=47,y=2,text="ms",width=4,fg_bg=label_fg}
|
TextBox{parent=crd_box,x=term_w-4,y=2,text="ms",width=4,fg_bg=label_fg}
|
||||||
crd_rtt.register(databus.ps, "crd_rtt", crd_rtt.update)
|
crd_rtt.register(databus.ps, "crd_rtt", crd_rtt.update)
|
||||||
crd_rtt.register(databus.ps, "crd_rtt_color", crd_rtt.recolor)
|
crd_rtt.register(databus.ps, "crd_rtt_color", crd_rtt.recolor)
|
||||||
|
|
||||||
-- pocket sessions page
|
-- pocket sessions page
|
||||||
|
|
||||||
local pkt_page = Div{parent=page_div,x=1,y=1,hidden=true}
|
local pkt_page = Div{parent=page_div,y=1,hidden=true}
|
||||||
local pdg_list = ListBox{parent=pkt_page,x=1,y=1,height=17,width=51,scroll_height=1000,fg_bg=style.fp.text_fg,nav_fg_bg=cpair(colors.gray,colors.lightGray),nav_active=cpair(colors.black,colors.gray)}
|
local pdg_list = ListBox{parent=pkt_page,y=1,height=term_h-2,width=term_w,scroll_height=1000,fg_bg=style.fp.text_fg,nav_fg_bg=cpair(colors.gray,colors.lightGray),nav_active=cpair(colors.black,colors.gray)}
|
||||||
local _ = Div{parent=pdg_list,height=1} -- padding
|
local _ = Div{parent=pdg_list,height=1} -- padding
|
||||||
|
|
||||||
-- RTU device ID check/diagnostics page
|
-- RTU device ID check/diagnostics page
|
||||||
|
|
||||||
local chk_page = Div{parent=page_div,x=1,y=1,hidden=true}
|
local chk_page = Div{parent=page_div,y=1,hidden=true}
|
||||||
local chk_list = ListBox{parent=chk_page,x=1,y=1,height=17,width=51,scroll_height=1000,fg_bg=style.fp.text_fg,nav_fg_bg=cpair(colors.gray,colors.lightGray),nav_active=cpair(colors.black,colors.gray)}
|
local chk_list = ListBox{parent=chk_page,y=1,height=term_h-2,width=term_w,scroll_height=1000,fg_bg=style.fp.text_fg,nav_fg_bg=cpair(colors.gray,colors.lightGray),nav_active=cpair(colors.black,colors.gray)}
|
||||||
local _ = Div{parent=chk_list,height=1} -- padding
|
local _ = Div{parent=chk_list,height=1} -- padding
|
||||||
|
|
||||||
-- info page
|
-- info page
|
||||||
|
|
||||||
local info_page = Div{parent=page_div,x=1,y=1,hidden=true}
|
local info_page = Div{parent=page_div,y=1,hidden=true}
|
||||||
local info = Div{parent=info_page,height=6,x=2,y=2}
|
local info = Div{parent=info_page,height=6,x=2,y=2}
|
||||||
|
|
||||||
TextBox{parent=info,text="SVR \x1a Supervisor Status"}
|
TextBox{parent=info,text="SVR \x1a Supervisor Status"}
|
||||||
@@ -168,7 +170,7 @@ local function init(panel)
|
|||||||
TextBox{parent=info,text="PKT \x1a Pocket Connections"}
|
TextBox{parent=info,text="PKT \x1a Pocket Connections"}
|
||||||
TextBox{parent=info,text="DEV \x1a RTU Device/Configuration Alerts"}
|
TextBox{parent=info,text="DEV \x1a RTU Device/Configuration Alerts"}
|
||||||
|
|
||||||
local notes = Div{parent=info_page,width=49,height=8,x=2,y=9,fg_bg=style.fp.disabled_fg}
|
local notes = Div{parent=info_page,width=term_w-2,height=8,x=2,y=9,fg_bg=style.fp.disabled_fg}
|
||||||
|
|
||||||
TextBox{parent=notes,text="The DEV tab will show missing devices and devices that connected with incorrect information. Missing entries will indicate how the configuration should be, duplicate entries will indicate what is a duplicate, and out-of-range entries will indicate the invalid entry. An out-of-range example is a #2 turbine when you should only have 1 turbine for that unit."}
|
TextBox{parent=notes,text="The DEV tab will show missing devices and devices that connected with incorrect information. Missing entries will indicate how the configuration should be, duplicate entries will indicate what is a duplicate, and out-of-range entries will indicate the invalid entry. An out-of-range example is a #2 turbine when you should only have 1 turbine for that unit."}
|
||||||
|
|
||||||
|
|||||||
@@ -234,6 +234,23 @@ function coordinator.new_session(id, s_addr, i_seq_num, in_queue, out_queue, tim
|
|||||||
if pkt.type == CRDN_TYPE.INITIAL_BUILDS then
|
if pkt.type == CRDN_TYPE.INITIAL_BUILDS then
|
||||||
-- acknowledgement to coordinator receiving builds
|
-- acknowledgement to coordinator receiving builds
|
||||||
self.acks.builds = true
|
self.acks.builds = true
|
||||||
|
elseif pkt.type == CRDN_TYPE.PROCESS_READY then
|
||||||
|
if pkt.length == 5 then
|
||||||
|
-- coordinator has sent all initial process data, power-on recovery is now possible
|
||||||
|
|
||||||
|
---@type start_auto_config
|
||||||
|
local config = {
|
||||||
|
mode = pkt.data[1],
|
||||||
|
burn_target = pkt.data[2],
|
||||||
|
charge_target = pkt.data[3],
|
||||||
|
gen_target = pkt.data[4],
|
||||||
|
limits = pkt.data[5]
|
||||||
|
}
|
||||||
|
|
||||||
|
facility.boot_recovery_start(config)
|
||||||
|
else
|
||||||
|
log.debug(log_tag .. "CRDN process ready packet length mismatch")
|
||||||
|
end
|
||||||
elseif pkt.type == CRDN_TYPE.FAC_BUILDS then
|
elseif pkt.type == CRDN_TYPE.FAC_BUILDS then
|
||||||
-- acknowledgement to coordinator receiving builds
|
-- acknowledgement to coordinator receiving builds
|
||||||
self.acks.fac_builds = true
|
self.acks.fac_builds = true
|
||||||
@@ -243,8 +260,11 @@ function coordinator.new_session(id, s_addr, i_seq_num, in_queue, out_queue, tim
|
|||||||
|
|
||||||
if cmd == FAC_COMMAND.SCRAM_ALL then
|
if cmd == FAC_COMMAND.SCRAM_ALL then
|
||||||
facility.scram_all()
|
facility.scram_all()
|
||||||
|
facility.cancel_recovery()
|
||||||
_send(CRDN_TYPE.FAC_CMD, { cmd, true })
|
_send(CRDN_TYPE.FAC_CMD, { cmd, true })
|
||||||
elseif cmd == FAC_COMMAND.STOP then
|
elseif cmd == FAC_COMMAND.STOP then
|
||||||
|
facility.cancel_recovery()
|
||||||
|
|
||||||
local was_active = facility.auto_is_active()
|
local was_active = facility.auto_is_active()
|
||||||
|
|
||||||
if was_active then
|
if was_active then
|
||||||
@@ -253,14 +273,16 @@ function coordinator.new_session(id, s_addr, i_seq_num, in_queue, out_queue, tim
|
|||||||
|
|
||||||
_send(CRDN_TYPE.FAC_CMD, { cmd, was_active })
|
_send(CRDN_TYPE.FAC_CMD, { cmd, was_active })
|
||||||
elseif cmd == FAC_COMMAND.START then
|
elseif cmd == FAC_COMMAND.START then
|
||||||
|
facility.cancel_recovery()
|
||||||
|
|
||||||
if pkt.length == 6 then
|
if pkt.length == 6 then
|
||||||
---@type sys_auto_config
|
---@class start_auto_config
|
||||||
local config = {
|
local config = {
|
||||||
mode = pkt.data[2],
|
mode = pkt.data[2], ---@type PROCESS
|
||||||
burn_target = pkt.data[3],
|
burn_target = pkt.data[3], ---@type number
|
||||||
charge_target = pkt.data[4],
|
charge_target = pkt.data[4], ---@type number
|
||||||
gen_target = pkt.data[5],
|
gen_target = pkt.data[5], ---@type number
|
||||||
limits = pkt.data[6]
|
limits = pkt.data[6] ---@type number[]
|
||||||
}
|
}
|
||||||
|
|
||||||
_send(CRDN_TYPE.FAC_CMD, { cmd, table.unpack(facility.auto_start(config)) })
|
_send(CRDN_TYPE.FAC_CMD, { cmd, table.unpack(facility.auto_start(config)) })
|
||||||
@@ -278,13 +300,13 @@ function coordinator.new_session(id, s_addr, i_seq_num, in_queue, out_queue, tim
|
|||||||
end
|
end
|
||||||
elseif cmd == FAC_COMMAND.SET_PU_FB then
|
elseif cmd == FAC_COMMAND.SET_PU_FB then
|
||||||
if pkt.length == 2 then
|
if pkt.length == 2 then
|
||||||
_send(CRDN_TYPE.FAC_CMD, { cmd, facility.set_pu_fallback(pkt.data[2]) })
|
_send(CRDN_TYPE.FAC_CMD, { cmd, facility.set_pu_fallback(pkt.data[2] == true) })
|
||||||
else
|
else
|
||||||
log.debug(log_tag .. "CRDN set pu fallback packet length mismatch")
|
log.debug(log_tag .. "CRDN set pu fallback packet length mismatch")
|
||||||
end
|
end
|
||||||
elseif cmd == FAC_COMMAND.SET_SPS_LP then
|
elseif cmd == FAC_COMMAND.SET_SPS_LP then
|
||||||
if pkt.length == 2 then
|
if pkt.length == 2 then
|
||||||
_send(CRDN_TYPE.FAC_CMD, { cmd, facility.set_sps_low_power(pkt.data[2]) })
|
_send(CRDN_TYPE.FAC_CMD, { cmd, facility.set_sps_low_power(pkt.data[2] == true) })
|
||||||
else
|
else
|
||||||
log.debug(log_tag .. "CRDN set sps low power packet length mismatch")
|
log.debug(log_tag .. "CRDN set sps low power packet length mismatch")
|
||||||
end
|
end
|
||||||
@@ -312,8 +334,11 @@ function coordinator.new_session(id, s_addr, i_seq_num, in_queue, out_queue, tim
|
|||||||
local manual = facility.get_group(uid) == AUTO_GROUP.MANUAL
|
local manual = facility.get_group(uid) == AUTO_GROUP.MANUAL
|
||||||
|
|
||||||
if cmd == UNIT_COMMAND.SCRAM then
|
if cmd == UNIT_COMMAND.SCRAM then
|
||||||
|
facility.cancel_recovery()
|
||||||
out_queue.push_data(SV_Q_DATA.SCRAM, data)
|
out_queue.push_data(SV_Q_DATA.SCRAM, data)
|
||||||
elseif cmd == UNIT_COMMAND.START then
|
elseif cmd == UNIT_COMMAND.START then
|
||||||
|
facility.cancel_recovery()
|
||||||
|
|
||||||
if manual then
|
if manual then
|
||||||
out_queue.push_data(SV_Q_DATA.START, data)
|
out_queue.push_data(SV_Q_DATA.START, data)
|
||||||
else
|
else
|
||||||
@@ -323,6 +348,8 @@ function coordinator.new_session(id, s_addr, i_seq_num, in_queue, out_queue, tim
|
|||||||
elseif cmd == UNIT_COMMAND.RESET_RPS then
|
elseif cmd == UNIT_COMMAND.RESET_RPS then
|
||||||
out_queue.push_data(SV_Q_DATA.RESET_RPS, data)
|
out_queue.push_data(SV_Q_DATA.RESET_RPS, data)
|
||||||
elseif cmd == UNIT_COMMAND.SET_BURN then
|
elseif cmd == UNIT_COMMAND.SET_BURN then
|
||||||
|
facility.cancel_recovery()
|
||||||
|
|
||||||
if pkt.length == 3 then
|
if pkt.length == 3 then
|
||||||
if manual then
|
if manual then
|
||||||
out_queue.push_data(SV_Q_DATA.SET_BURN, data)
|
out_queue.push_data(SV_Q_DATA.SET_BURN, data)
|
||||||
@@ -353,6 +380,8 @@ function coordinator.new_session(id, s_addr, i_seq_num, in_queue, out_queue, tim
|
|||||||
log.debug(log_tag .. "CRDN unit command reset alarm missing alarm id")
|
log.debug(log_tag .. "CRDN unit command reset alarm missing alarm id")
|
||||||
end
|
end
|
||||||
elseif cmd == UNIT_COMMAND.SET_GROUP then
|
elseif cmd == UNIT_COMMAND.SET_GROUP then
|
||||||
|
facility.cancel_recovery()
|
||||||
|
|
||||||
if (pkt.length == 3) and (type(pkt.data[3]) == "number") and
|
if (pkt.length == 3) and (type(pkt.data[3]) == "number") and
|
||||||
(pkt.data[3] >= AUTO_GROUP.MANUAL) and (pkt.data[3] <= AUTO_GROUP.BACKUP) then
|
(pkt.data[3] >= AUTO_GROUP.MANUAL) and (pkt.data[3] <= AUTO_GROUP.BACKUP) then
|
||||||
facility.set_group(unit.get_id(), pkt.data[3])
|
facility.set_group(unit.get_id(), pkt.data[3])
|
||||||
|
|||||||
@@ -53,15 +53,15 @@ local PERIODICS = {
|
|||||||
---@param in_queue mqueue in message queue
|
---@param in_queue mqueue in message queue
|
||||||
---@param out_queue mqueue out message queue
|
---@param out_queue mqueue out message queue
|
||||||
---@param timeout number communications timeout
|
---@param timeout number communications timeout
|
||||||
|
---@param initial_reset boolean[] initial PLC reset on timeout flags, indexed by reactor_id
|
||||||
---@param fp_ok boolean if the front panel UI is running
|
---@param fp_ok boolean if the front panel UI is running
|
||||||
function plc.new_session(id, s_addr, i_seq_num, reactor_id, in_queue, out_queue, timeout, fp_ok)
|
function plc.new_session(id, s_addr, i_seq_num, reactor_id, in_queue, out_queue, timeout, initial_reset, fp_ok)
|
||||||
-- print a log message to the terminal as long as the UI isn't running
|
-- print a log message to the terminal as long as the UI isn't running
|
||||||
local function println(message) if not fp_ok then util.println_ts(message) end end
|
local function println(message) if not fp_ok then util.println_ts(message) end end
|
||||||
|
|
||||||
local log_tag = "plc_session(" .. id .. "): "
|
local log_tag = "plc_session(" .. id .. "): "
|
||||||
|
|
||||||
local self = {
|
local self = {
|
||||||
commanded_state = false,
|
|
||||||
commanded_burn_rate = 0.0,
|
commanded_burn_rate = 0.0,
|
||||||
auto_cmd_token = 0,
|
auto_cmd_token = 0,
|
||||||
ramping_rate = false,
|
ramping_rate = false,
|
||||||
@@ -72,6 +72,7 @@ function plc.new_session(id, s_addr, i_seq_num, reactor_id, in_queue, out_queue,
|
|||||||
connected = true,
|
connected = true,
|
||||||
received_struct = false,
|
received_struct = false,
|
||||||
received_status_cache = false,
|
received_status_cache = false,
|
||||||
|
received_rps_status = false,
|
||||||
conn_watchdog = util.new_watchdog(timeout),
|
conn_watchdog = util.new_watchdog(timeout),
|
||||||
last_rtt = 0,
|
last_rtt = 0,
|
||||||
-- periodic messages
|
-- periodic messages
|
||||||
@@ -381,6 +382,16 @@ function plc.new_session(id, s_addr, i_seq_num, reactor_id, in_queue, out_queue,
|
|||||||
local status = pcall(_copy_rps_status, pkt.data)
|
local status = pcall(_copy_rps_status, pkt.data)
|
||||||
if status then
|
if status then
|
||||||
-- copied in RPS status data OK
|
-- copied in RPS status data OK
|
||||||
|
self.received_rps_status = true
|
||||||
|
|
||||||
|
-- try initial reset if needed
|
||||||
|
if initial_reset[reactor_id] then
|
||||||
|
initial_reset[reactor_id] = false
|
||||||
|
if self.sDB.rps_trip_cause == "timeout" then
|
||||||
|
_send(RPLC_TYPE.RPS_AUTO_RESET, {})
|
||||||
|
log.debug(log_tag .. "initial RPS reset on timeout status sent")
|
||||||
|
end
|
||||||
|
end
|
||||||
else
|
else
|
||||||
-- error copying RPS status data
|
-- error copying RPS status data
|
||||||
log.error(log_tag .. "failed to parse RPS status packet data")
|
log.error(log_tag .. "failed to parse RPS status packet data")
|
||||||
@@ -394,6 +405,16 @@ function plc.new_session(id, s_addr, i_seq_num, reactor_id, in_queue, out_queue,
|
|||||||
local status = pcall(_copy_rps_status, { true, table.unpack(pkt.data) })
|
local status = pcall(_copy_rps_status, { true, table.unpack(pkt.data) })
|
||||||
if status then
|
if status then
|
||||||
-- copied in RPS status data OK
|
-- copied in RPS status data OK
|
||||||
|
self.received_rps_status = true
|
||||||
|
|
||||||
|
-- try initial reset if needed
|
||||||
|
if initial_reset[reactor_id] then
|
||||||
|
initial_reset[reactor_id] = false
|
||||||
|
if self.sDB.rps_trip_cause == "timeout" then
|
||||||
|
_send(RPLC_TYPE.RPS_AUTO_RESET, {})
|
||||||
|
log.debug(log_tag .. "initial RPS reset on timeout alarm sent")
|
||||||
|
end
|
||||||
|
end
|
||||||
else
|
else
|
||||||
-- error copying RPS status data
|
-- error copying RPS status data
|
||||||
log.error(log_tag .. "failed to parse RPS alarm status data")
|
log.error(log_tag .. "failed to parse RPS alarm status data")
|
||||||
@@ -487,6 +508,10 @@ function plc.new_session(id, s_addr, i_seq_num, reactor_id, in_queue, out_queue,
|
|||||||
---@nodiscard
|
---@nodiscard
|
||||||
function public.get_db() return self.sDB end
|
function public.get_db() return self.sDB end
|
||||||
|
|
||||||
|
-- check if the reactor structure, status, and RPS status have been received
|
||||||
|
---@nodiscard
|
||||||
|
function public.check_received_all_data() return self.received_struct and self.received_status_cache and self.received_rps_status end
|
||||||
|
|
||||||
-- check if ramping is completed by first verifying auto command token ack
|
-- check if ramping is completed by first verifying auto command token ack
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
function public.is_ramp_complete()
|
function public.is_ramp_complete()
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ local rsctl = {}
|
|||||||
-- create a new redstone RTU I/O controller
|
-- create a new redstone RTU I/O controller
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
---@param redstone_rtus redstone_session[] redstone RTU sessions
|
---@param redstone_rtus redstone_session[] redstone RTU sessions
|
||||||
function rsctl.new(redstone_rtus)
|
---@param bank integer I/O bank (unit/facility assignment) to interface with
|
||||||
|
function rsctl.new(redstone_rtus, bank)
|
||||||
---@class rs_controller
|
---@class rs_controller
|
||||||
local public = {}
|
local public = {}
|
||||||
|
|
||||||
@@ -18,7 +19,7 @@ function rsctl.new(redstone_rtus)
|
|||||||
---@return boolean
|
---@return boolean
|
||||||
function public.is_connected(port)
|
function public.is_connected(port)
|
||||||
for i = 1, #redstone_rtus do
|
for i = 1, #redstone_rtus do
|
||||||
if redstone_rtus[i].get_db().io[port] ~= nil then return true end
|
if redstone_rtus[i].get_db().io[bank][port] ~= nil then return true end
|
||||||
end
|
end
|
||||||
|
|
||||||
return false
|
return false
|
||||||
@@ -29,7 +30,7 @@ function rsctl.new(redstone_rtus)
|
|||||||
---@param value boolean
|
---@param value boolean
|
||||||
function public.digital_write(port, value)
|
function public.digital_write(port, value)
|
||||||
for i = 1, #redstone_rtus do
|
for i = 1, #redstone_rtus do
|
||||||
local io = redstone_rtus[i].get_db().io[port]
|
local io = redstone_rtus[i].get_db().io[bank][port]
|
||||||
if io ~= nil then io.write(value) end
|
if io ~= nil then io.write(value) end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -40,7 +41,7 @@ function rsctl.new(redstone_rtus)
|
|||||||
---@return boolean|nil
|
---@return boolean|nil
|
||||||
function public.digital_read(port)
|
function public.digital_read(port)
|
||||||
for i = 1, #redstone_rtus do
|
for i = 1, #redstone_rtus do
|
||||||
local io = redstone_rtus[i].get_db().io[port]
|
local io = redstone_rtus[i].get_db().io[bank][port]
|
||||||
if io ~= nil then return io.read() --[[@as boolean|nil]] end
|
if io ~= nil then return io.read() --[[@as boolean|nil]] end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -52,7 +53,7 @@ function rsctl.new(redstone_rtus)
|
|||||||
---@param max number maximum value for scaling 0 to 15
|
---@param max number maximum value for scaling 0 to 15
|
||||||
function public.analog_write(port, value, min, max)
|
function public.analog_write(port, value, min, max)
|
||||||
for i = 1, #redstone_rtus do
|
for i = 1, #redstone_rtus do
|
||||||
local io = redstone_rtus[i].get_db().io[port]
|
local io = redstone_rtus[i].get_db().io[bank][port]
|
||||||
if io ~= nil then io.write(rsio.analog_write(value, min, max)) end
|
if io ~= nil then io.write(rsio.analog_write(value, min, max)) end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ function rtu.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout, ad
|
|||||||
type = self.advert[i][1],
|
type = self.advert[i][1],
|
||||||
index = self.advert[i][2],
|
index = self.advert[i][2],
|
||||||
reactor = self.advert[i][3],
|
reactor = self.advert[i][3],
|
||||||
rsio = self.advert[i][4]
|
rs_conns = self.advert[i][4]
|
||||||
}
|
}
|
||||||
|
|
||||||
local u_type = unit_advert.type ---@type RTU_UNIT_TYPE|boolean
|
local u_type = unit_advert.type ---@type RTU_UNIT_TYPE|boolean
|
||||||
@@ -104,14 +104,17 @@ function rtu.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout, ad
|
|||||||
advert_validator.assert(util.is_int(unit_advert.index) or (unit_advert.index == false))
|
advert_validator.assert(util.is_int(unit_advert.index) or (unit_advert.index == false))
|
||||||
advert_validator.assert_type_int(unit_advert.reactor)
|
advert_validator.assert_type_int(unit_advert.reactor)
|
||||||
|
|
||||||
if u_type == RTU_UNIT_TYPE.REDSTONE then
|
|
||||||
advert_validator.assert_type_table(unit_advert.rsio)
|
|
||||||
end
|
|
||||||
|
|
||||||
if advert_validator.valid() then
|
if advert_validator.valid() then
|
||||||
if util.is_int(unit_advert.index) then advert_validator.assert_min(unit_advert.index, 1) end
|
if util.is_int(unit_advert.index) then advert_validator.assert_min(unit_advert.index, 1) end
|
||||||
advert_validator.assert_min(unit_advert.reactor, 0)
|
|
||||||
advert_validator.assert_max(unit_advert.reactor, #self.fac_units)
|
if (unit_advert.reactor == -1) or (u_type == RTU_UNIT_TYPE.REDSTONE) then
|
||||||
|
advert_validator.assert((unit_advert.reactor == -1) and (u_type == RTU_UNIT_TYPE.REDSTONE))
|
||||||
|
advert_validator.assert_type_table(unit_advert.rs_conns)
|
||||||
|
else
|
||||||
|
advert_validator.assert_min(unit_advert.reactor, 0)
|
||||||
|
advert_validator.assert_max(unit_advert.reactor, #self.fac_units)
|
||||||
|
end
|
||||||
|
|
||||||
if not advert_validator.valid() then u_type = false end
|
if not advert_validator.valid() then u_type = false end
|
||||||
else
|
else
|
||||||
u_type = false
|
u_type = false
|
||||||
@@ -126,15 +129,34 @@ function rtu.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout, ad
|
|||||||
-- validation fail
|
-- validation fail
|
||||||
log.debug(log_tag .. "_handle_advertisement(): advertisement unit validation failure")
|
log.debug(log_tag .. "_handle_advertisement(): advertisement unit validation failure")
|
||||||
else
|
else
|
||||||
if unit_advert.reactor > 0 then
|
if unit_advert.reactor == -1 then
|
||||||
local target_unit = self.fac_units[unit_advert.reactor]
|
-- redstone RTUs can be used in multiple different assignments
|
||||||
|
|
||||||
-- unit RTUs
|
|
||||||
if u_type == RTU_UNIT_TYPE.REDSTONE then
|
if u_type == RTU_UNIT_TYPE.REDSTONE then
|
||||||
-- redstone
|
-- redstone
|
||||||
unit = svrs_redstone.new(id, i, unit_advert, self.modbus_q)
|
unit = svrs_redstone.new(id, i, unit_advert, self.modbus_q)
|
||||||
if type(unit) ~= "nil" then target_unit.add_redstone(unit) end
|
|
||||||
elseif u_type == RTU_UNIT_TYPE.BOILER_VALVE then
|
-- link this to any subsystems this RTU provides connections for
|
||||||
|
if type(unit) ~= "nil" then
|
||||||
|
for assignment, conns in pairs(unit_advert.rs_conns) do
|
||||||
|
if #conns > 0 then
|
||||||
|
if assignment == 0 then
|
||||||
|
facility.add_redstone(unit)
|
||||||
|
elseif assignment > 0 and assignment <= #self.fac_units then
|
||||||
|
self.fac_units[assignment].add_redstone(unit)
|
||||||
|
else
|
||||||
|
log.warning(util.c(log_tag, "_handle_advertisement(): invalid redstone RTU assignment ", assignment))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
log.warning(util.c(log_tag, "_handle_advertisement(): encountered unsupported multi-assignment RTU type ", type_string))
|
||||||
|
end
|
||||||
|
elseif unit_advert.reactor > 0 then
|
||||||
|
local target_unit = self.fac_units[unit_advert.reactor]
|
||||||
|
|
||||||
|
-- unit RTUs
|
||||||
|
if u_type == RTU_UNIT_TYPE.BOILER_VALVE then
|
||||||
-- boiler
|
-- boiler
|
||||||
unit = svrs_boilerv.new(id, i, unit_advert, self.modbus_q)
|
unit = svrs_boilerv.new(id, i, unit_advert, self.modbus_q)
|
||||||
if type(unit) ~= "nil" then target_unit.add_boiler(unit) end
|
if type(unit) ~= "nil" then target_unit.add_boiler(unit) end
|
||||||
|
|||||||
@@ -105,27 +105,39 @@ function boilerv.new(session_id, unit_id, advert, out_queue)
|
|||||||
-- PRIVATE FUNCTIONS --
|
-- PRIVATE FUNCTIONS --
|
||||||
|
|
||||||
-- query if the multiblock is formed
|
-- query if the multiblock is formed
|
||||||
local function _request_formed()
|
---@param time_now integer
|
||||||
|
local function _request_formed(time_now)
|
||||||
-- read discrete input 1 (start = 1, count = 1)
|
-- read discrete input 1 (start = 1, count = 1)
|
||||||
self.session.send_request(TXN_TYPES.FORMED, MODBUS_FCODE.READ_DISCRETE_INPUTS, { 1, 1 })
|
if self.session.send_request(TXN_TYPES.FORMED, MODBUS_FCODE.READ_DISCRETE_INPUTS, { 1, 1 }) ~= false then
|
||||||
|
self.periodics.next_formed_req = time_now + PERIODICS.FORMED
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- query the build of the device
|
-- query the build of the device
|
||||||
local function _request_build()
|
---@param time_now integer
|
||||||
|
local function _request_build(time_now)
|
||||||
-- read input registers 1 through 12 (start = 1, count = 12)
|
-- read input registers 1 through 12 (start = 1, count = 12)
|
||||||
self.session.send_request(TXN_TYPES.BUILD, MODBUS_FCODE.READ_INPUT_REGS, { 1, 12 })
|
if self.session.send_request(TXN_TYPES.BUILD, MODBUS_FCODE.READ_INPUT_REGS, { 1, 12 }) ~= false then
|
||||||
|
self.periodics.next_build_req = time_now + PERIODICS.BUILD
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- query the state of the device
|
-- query the state of the device
|
||||||
local function _request_state()
|
---@param time_now integer
|
||||||
|
local function _request_state(time_now)
|
||||||
-- read input registers 13 through 15 (start = 13, count = 3)
|
-- read input registers 13 through 15 (start = 13, count = 3)
|
||||||
self.session.send_request(TXN_TYPES.STATE, MODBUS_FCODE.READ_INPUT_REGS, { 13, 3 })
|
if self.session.send_request(TXN_TYPES.STATE, MODBUS_FCODE.READ_INPUT_REGS, { 13, 3 }) ~= false then
|
||||||
|
self.periodics.next_state_req = time_now + PERIODICS.STATE
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- query the tanks of the device
|
-- query the tanks of the device
|
||||||
local function _request_tanks()
|
---@param time_now integer
|
||||||
|
local function _request_tanks(time_now)
|
||||||
-- read input registers 16 through 27 (start = 16, count = 12)
|
-- read input registers 16 through 27 (start = 16, count = 12)
|
||||||
self.session.send_request(TXN_TYPES.TANKS, MODBUS_FCODE.READ_INPUT_REGS, { 16, 12 })
|
if self.session.send_request(TXN_TYPES.TANKS, MODBUS_FCODE.READ_INPUT_REGS, { 16, 12 }) ~= false then
|
||||||
|
self.periodics.next_tanks_req = time_now + PERIODICS.TANKS
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- PUBLIC FUNCTIONS --
|
-- PUBLIC FUNCTIONS --
|
||||||
@@ -210,26 +222,12 @@ function boilerv.new(session_id, unit_id, advert, out_queue)
|
|||||||
-- update this runner
|
-- update this runner
|
||||||
---@param time_now integer milliseconds
|
---@param time_now integer milliseconds
|
||||||
function public.update(time_now)
|
function public.update(time_now)
|
||||||
if self.periodics.next_formed_req <= time_now then
|
if self.periodics.next_formed_req <= time_now then _request_formed(time_now) end
|
||||||
_request_formed()
|
|
||||||
self.periodics.next_formed_req = time_now + PERIODICS.FORMED
|
|
||||||
end
|
|
||||||
|
|
||||||
if self.db.formed then
|
if self.db.formed then
|
||||||
if not self.has_build and self.periodics.next_build_req <= time_now then
|
if not self.has_build and self.periodics.next_build_req <= time_now then _request_build(time_now) end
|
||||||
_request_build()
|
if self.periodics.next_state_req <= time_now then _request_state(time_now) end
|
||||||
self.periodics.next_build_req = time_now + PERIODICS.BUILD
|
if self.periodics.next_tanks_req <= time_now then _request_tanks(time_now) end
|
||||||
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
|
end
|
||||||
|
|
||||||
self.session.post_update()
|
self.session.post_update()
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ local PERIODICS = {
|
|||||||
TANKS = 500
|
TANKS = 500
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local WRITE_BUSY_WAIT = 1000
|
||||||
|
|
||||||
-- create a new dynamicv rtu session runner
|
-- create a new dynamicv rtu session runner
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
---@param session_id integer RTU gateway session ID
|
---@param session_id integer RTU gateway session ID
|
||||||
@@ -63,6 +65,8 @@ function dynamicv.new(session_id, unit_id, advert, out_queue)
|
|||||||
local self = {
|
local self = {
|
||||||
session = unit_session.new(session_id, unit_id, advert, out_queue, log_tag, TXN_TAGS),
|
session = unit_session.new(session_id, unit_id, advert, out_queue, log_tag, TXN_TAGS),
|
||||||
has_build = false,
|
has_build = false,
|
||||||
|
mode_cmd = nil, ---@type container_mode|nil
|
||||||
|
resend_mode = false,
|
||||||
periodics = {
|
periodics = {
|
||||||
next_formed_req = 0,
|
next_formed_req = 0,
|
||||||
next_build_req = 0,
|
next_build_req = 0,
|
||||||
@@ -101,45 +105,77 @@ function dynamicv.new(session_id, unit_id, advert, out_queue)
|
|||||||
|
|
||||||
-- increment the container mode
|
-- increment the container mode
|
||||||
local function _inc_cont_mode()
|
local function _inc_cont_mode()
|
||||||
|
-- set mode command
|
||||||
|
if self.mode_cmd == "BOTH" then self.mode_cmd = "FILL"
|
||||||
|
elseif self.mode_cmd == "FILL" then self.mode_cmd = "EMPTY"
|
||||||
|
elseif self.mode_cmd == "EMPTY" then self.mode_cmd = "BOTH"
|
||||||
|
end
|
||||||
|
|
||||||
-- write coil 1 with unused value 0
|
-- write coil 1 with unused value 0
|
||||||
self.session.send_request(TXN_TYPES.INC_CONT, MODBUS_FCODE.WRITE_SINGLE_COIL, { 1, 0 })
|
if self.session.send_request(TXN_TYPES.INC_CONT, MODBUS_FCODE.WRITE_SINGLE_COIL, { 1, 0 }, WRITE_BUSY_WAIT) == false then
|
||||||
|
self.resend_mode = true
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- decrement the container mode
|
-- decrement the container mode
|
||||||
local function _dec_cont_mode()
|
local function _dec_cont_mode()
|
||||||
|
-- set mode command
|
||||||
|
if self.mode_cmd == "BOTH" then self.mode_cmd = "EMPTY"
|
||||||
|
elseif self.mode_cmd == "EMPTY" then self.mode_cmd = "FILL"
|
||||||
|
elseif self.mode_cmd == "FILL" then self.mode_cmd = "BOTH"
|
||||||
|
end
|
||||||
|
|
||||||
-- write coil 2 with unused value 0
|
-- write coil 2 with unused value 0
|
||||||
self.session.send_request(TXN_TYPES.DEC_CONT, MODBUS_FCODE.WRITE_SINGLE_COIL, { 2, 0 })
|
if self.session.send_request(TXN_TYPES.DEC_CONT, MODBUS_FCODE.WRITE_SINGLE_COIL, { 2, 0 , WRITE_BUSY_WAIT}) == false then
|
||||||
|
self.resend_mode = false
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- set the container mode
|
-- set the container mode
|
||||||
---@param mode container_mode
|
---@param mode container_mode
|
||||||
local function _set_cont_mode(mode)
|
local function _set_cont_mode(mode)
|
||||||
|
self.mode_cmd = mode
|
||||||
|
|
||||||
-- write holding register 1
|
-- write holding register 1
|
||||||
self.session.send_request(TXN_TYPES.SET_CONT, MODBUS_FCODE.WRITE_SINGLE_HOLD_REG, { 1, mode })
|
if self.session.send_request(TXN_TYPES.SET_CONT, MODBUS_FCODE.WRITE_SINGLE_HOLD_REG, { 1, mode }, WRITE_BUSY_WAIT) == false then
|
||||||
|
self.resend_mode = false
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- query if the multiblock is formed
|
-- query if the multiblock is formed
|
||||||
local function _request_formed()
|
---@param time_now integer
|
||||||
|
local function _request_formed(time_now)
|
||||||
-- read discrete input 1 (start = 1, count = 1)
|
-- read discrete input 1 (start = 1, count = 1)
|
||||||
self.session.send_request(TXN_TYPES.FORMED, MODBUS_FCODE.READ_DISCRETE_INPUTS, { 1, 1 })
|
if self.session.send_request(TXN_TYPES.FORMED, MODBUS_FCODE.READ_DISCRETE_INPUTS, { 1, 1 }) ~= false then
|
||||||
|
self.periodics.next_formed_req = time_now + PERIODICS.FORMED
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- query the build of the device
|
-- query the build of the device
|
||||||
local function _request_build()
|
---@param time_now integer
|
||||||
|
local function _request_build(time_now)
|
||||||
-- read input registers 1 through 7 (start = 1, count = 7)
|
-- read input registers 1 through 7 (start = 1, count = 7)
|
||||||
self.session.send_request(TXN_TYPES.BUILD, MODBUS_FCODE.READ_INPUT_REGS, { 1, 7 })
|
if self.session.send_request(TXN_TYPES.BUILD, MODBUS_FCODE.READ_INPUT_REGS, { 1, 7 }) ~= false then
|
||||||
|
self.periodics.next_build_req = time_now + PERIODICS.BUILD
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- query the state of the device
|
-- query the state of the device
|
||||||
local function _request_state()
|
---@param time_now integer
|
||||||
|
local function _request_state(time_now)
|
||||||
-- read holding register 1 (start = 1, count = 1)
|
-- read holding register 1 (start = 1, count = 1)
|
||||||
self.session.send_request(TXN_TYPES.STATE, MODBUS_FCODE.READ_MUL_HOLD_REGS, { 1, 1 })
|
if self.session.send_request(TXN_TYPES.STATE, MODBUS_FCODE.READ_MUL_HOLD_REGS, { 1, 1 }) ~= false then
|
||||||
|
self.periodics.next_state_req = time_now + PERIODICS.STATE
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- query the tanks of the device
|
-- query the tanks of the device
|
||||||
local function _request_tanks()
|
---@param time_now integer
|
||||||
|
local function _request_tanks(time_now)
|
||||||
-- read input registers 8 through 9 (start = 8, count = 2)
|
-- read input registers 8 through 9 (start = 8, count = 2)
|
||||||
self.session.send_request(TXN_TYPES.TANKS, MODBUS_FCODE.READ_INPUT_REGS, { 8, 2 })
|
if self.session.send_request(TXN_TYPES.TANKS, MODBUS_FCODE.READ_INPUT_REGS, { 8, 2 }) ~= false then
|
||||||
|
self.periodics.next_tanks_req = time_now + PERIODICS.TANKS
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- PUBLIC FUNCTIONS --
|
-- PUBLIC FUNCTIONS --
|
||||||
@@ -182,6 +218,10 @@ function dynamicv.new(session_id, unit_id, advert, out_queue)
|
|||||||
if m_pkt.length == 1 then
|
if m_pkt.length == 1 then
|
||||||
self.db.state.last_update = util.time_ms()
|
self.db.state.last_update = util.time_ms()
|
||||||
self.db.state.container_mode = m_pkt.data[1]
|
self.db.state.container_mode = m_pkt.data[1]
|
||||||
|
|
||||||
|
if self.mode_cmd == nil then
|
||||||
|
self.mode_cmd = self.db.state.container_mode
|
||||||
|
end
|
||||||
else
|
else
|
||||||
log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")")
|
log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")")
|
||||||
end
|
end
|
||||||
@@ -247,30 +287,22 @@ function dynamicv.new(session_id, unit_id, advert, out_queue)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- try to resend mode if needed
|
||||||
|
if self.resend_mode then
|
||||||
|
self.resend_mode = false
|
||||||
|
_set_cont_mode(self.mode_cmd)
|
||||||
|
end
|
||||||
|
|
||||||
time_now = util.time()
|
time_now = util.time()
|
||||||
|
|
||||||
-- handle periodics
|
-- handle periodics
|
||||||
|
|
||||||
if self.periodics.next_formed_req <= time_now then
|
if self.periodics.next_formed_req <= time_now then _request_formed(time_now) end
|
||||||
_request_formed()
|
|
||||||
self.periodics.next_formed_req = time_now + PERIODICS.FORMED
|
|
||||||
end
|
|
||||||
|
|
||||||
if self.db.formed then
|
if self.db.formed then
|
||||||
if not self.has_build and self.periodics.next_build_req <= time_now then
|
if not self.has_build and self.periodics.next_build_req <= time_now then _request_build(time_now) end
|
||||||
_request_build()
|
if self.periodics.next_state_req <= time_now then _request_state(time_now) end
|
||||||
self.periodics.next_build_req = time_now + PERIODICS.BUILD
|
if self.periodics.next_tanks_req <= time_now then _request_tanks(time_now) end
|
||||||
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
|
end
|
||||||
|
|
||||||
self.session.post_update()
|
self.session.post_update()
|
||||||
|
|||||||
@@ -58,9 +58,12 @@ function envd.new(session_id, unit_id, advert, out_queue)
|
|||||||
-- PRIVATE FUNCTIONS --
|
-- PRIVATE FUNCTIONS --
|
||||||
|
|
||||||
-- query the radiation readings of the device
|
-- query the radiation readings of the device
|
||||||
local function _request_radiation()
|
---@param time_now integer
|
||||||
|
local function _request_radiation(time_now)
|
||||||
-- read input registers 1 and 2 (start = 1, count = 2)
|
-- read input registers 1 and 2 (start = 1, count = 2)
|
||||||
self.session.send_request(TXN_TYPES.RAD, MODBUS_FCODE.READ_INPUT_REGS, { 1, 2 })
|
if self.session.send_request(TXN_TYPES.RAD, MODBUS_FCODE.READ_INPUT_REGS, { 1, 2 }) ~= false then
|
||||||
|
self.periodics.next_rad_req = time_now + PERIODICS.RAD
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- PUBLIC FUNCTIONS --
|
-- PUBLIC FUNCTIONS --
|
||||||
@@ -90,10 +93,7 @@ function envd.new(session_id, unit_id, advert, out_queue)
|
|||||||
-- update this runner
|
-- update this runner
|
||||||
---@param time_now integer milliseconds
|
---@param time_now integer milliseconds
|
||||||
function public.update(time_now)
|
function public.update(time_now)
|
||||||
if self.periodics.next_rad_req <= time_now then
|
if self.periodics.next_rad_req <= time_now then _request_radiation(time_now) end
|
||||||
_request_radiation()
|
|
||||||
self.periodics.next_rad_req = time_now + PERIODICS.RAD
|
|
||||||
end
|
|
||||||
|
|
||||||
self.session.post_update()
|
self.session.post_update()
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -89,27 +89,39 @@ function imatrix.new(session_id, unit_id, advert, out_queue)
|
|||||||
-- PRIVATE FUNCTIONS --
|
-- PRIVATE FUNCTIONS --
|
||||||
|
|
||||||
-- query if the multiblock is formed
|
-- query if the multiblock is formed
|
||||||
local function _request_formed()
|
---@param time_now integer
|
||||||
|
local function _request_formed(time_now)
|
||||||
-- read discrete input 1 (start = 1, count = 1)
|
-- read discrete input 1 (start = 1, count = 1)
|
||||||
self.session.send_request(TXN_TYPES.FORMED, MODBUS_FCODE.READ_DISCRETE_INPUTS, { 1, 1 })
|
if self.session.send_request(TXN_TYPES.FORMED, MODBUS_FCODE.READ_DISCRETE_INPUTS, { 1, 1 }) ~= false then
|
||||||
|
self.periodics.next_formed_req = time_now + PERIODICS.FORMED
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- query the build of the device
|
-- query the build of the device
|
||||||
local function _request_build()
|
---@param time_now integer
|
||||||
|
local function _request_build(time_now)
|
||||||
-- read input registers 1 through 9 (start = 1, count = 9)
|
-- read input registers 1 through 9 (start = 1, count = 9)
|
||||||
self.session.send_request(TXN_TYPES.BUILD, MODBUS_FCODE.READ_INPUT_REGS, { 1, 9 })
|
if self.session.send_request(TXN_TYPES.BUILD, MODBUS_FCODE.READ_INPUT_REGS, { 1, 9 }) ~= false then
|
||||||
|
self.periodics.next_build_req = time_now + PERIODICS.BUILD
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- query the state of the device
|
-- query the state of the device
|
||||||
local function _request_state()
|
---@param time_now integer
|
||||||
|
local function _request_state(time_now)
|
||||||
-- read input register 10 through 11 (start = 10, count = 2)
|
-- read input register 10 through 11 (start = 10, count = 2)
|
||||||
self.session.send_request(TXN_TYPES.STATE, MODBUS_FCODE.READ_INPUT_REGS, { 10, 2 })
|
if self.session.send_request(TXN_TYPES.STATE, MODBUS_FCODE.READ_INPUT_REGS, { 10, 2 }) ~= false then
|
||||||
|
self.periodics.next_state_req = time_now + PERIODICS.STATE
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- query the tanks of the device
|
-- query the tanks of the device
|
||||||
local function _request_tanks()
|
---@param time_now integer
|
||||||
|
local function _request_tanks(time_now)
|
||||||
-- read input registers 12 through 15 (start = 12, count = 3)
|
-- read input registers 12 through 15 (start = 12, count = 3)
|
||||||
self.session.send_request(TXN_TYPES.TANKS, MODBUS_FCODE.READ_INPUT_REGS, { 12, 3 })
|
if self.session.send_request(TXN_TYPES.TANKS, MODBUS_FCODE.READ_INPUT_REGS, { 12, 3 }) ~= false then
|
||||||
|
self.periodics.next_tanks_req = time_now + PERIODICS.TANKS
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- PUBLIC FUNCTIONS --
|
-- PUBLIC FUNCTIONS --
|
||||||
@@ -181,26 +193,12 @@ function imatrix.new(session_id, unit_id, advert, out_queue)
|
|||||||
-- update this runner
|
-- update this runner
|
||||||
---@param time_now integer milliseconds
|
---@param time_now integer milliseconds
|
||||||
function public.update(time_now)
|
function public.update(time_now)
|
||||||
if self.periodics.next_formed_req <= time_now then
|
if self.periodics.next_formed_req <= time_now then _request_formed(time_now) end
|
||||||
_request_formed()
|
|
||||||
self.periodics.next_formed_req = time_now + PERIODICS.FORMED
|
|
||||||
end
|
|
||||||
|
|
||||||
if self.db.formed then
|
if self.db.formed then
|
||||||
if not self.has_build and self.periodics.next_build_req <= time_now then
|
if not self.has_build and self.periodics.next_build_req <= time_now then _request_build(time_now) end
|
||||||
_request_build()
|
if self.periodics.next_state_req <= time_now then _request_state(time_now) end
|
||||||
self.periodics.next_build_req = time_now + PERIODICS.BUILD
|
if self.periodics.next_tanks_req <= time_now then _request_tanks(time_now) end
|
||||||
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
|
end
|
||||||
|
|
||||||
self.session.post_update()
|
self.session.post_update()
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ local redstone = {}
|
|||||||
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
|
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
|
||||||
local MODBUS_FCODE = types.MODBUS_FCODE
|
local MODBUS_FCODE = types.MODBUS_FCODE
|
||||||
|
|
||||||
local IO_PORT = rsio.IO
|
|
||||||
local IO_LVL = rsio.IO_LVL
|
local IO_LVL = rsio.IO_LVL
|
||||||
local IO_MODE = rsio.IO_MODE
|
local IO_MODE = rsio.IO_MODE
|
||||||
|
|
||||||
@@ -39,6 +38,9 @@ local PERIODICS = {
|
|||||||
OUTPUT_SYNC = 200
|
OUTPUT_SYNC = 200
|
||||||
}
|
}
|
||||||
|
|
||||||
|
-- create a new block of IO banks (facility, then each unit)
|
||||||
|
local function new_io_block() return { [0] = {}, {}, {}, {}, {} } end
|
||||||
|
|
||||||
---@class dig_phy_entry
|
---@class dig_phy_entry
|
||||||
---@field phy IO_LVL actual value
|
---@field phy IO_LVL actual value
|
||||||
---@field req IO_LVL commanded value
|
---@field req IO_LVL commanded value
|
||||||
@@ -74,27 +76,27 @@ function redstone.new(session_id, unit_id, advert, out_queue)
|
|||||||
next_ir_req = 0,
|
next_ir_req = 0,
|
||||||
next_hr_sync = 0
|
next_hr_sync = 0
|
||||||
},
|
},
|
||||||
---@class rs_io_list
|
---@class rs_io_map
|
||||||
io_list = {
|
io_map = {
|
||||||
digital_in = {}, ---@type IO_PORT[] discrete inputs
|
digital_in = {}, ---@type { bank: integer, port: IO_PORT }[] discrete inputs
|
||||||
digital_out = {}, ---@type IO_PORT[] coils
|
digital_out = {}, ---@type { bank: integer, port: IO_PORT }[] coils
|
||||||
analog_in = {}, ---@type IO_PORT[] input registers
|
analog_in = {}, ---@type { bank: integer, port: IO_PORT }[] input registers
|
||||||
analog_out = {} ---@type IO_PORT[] holding registers
|
analog_out = {} ---@type { bank: integer, port: IO_PORT }[] holding registers
|
||||||
},
|
},
|
||||||
phy_trans = { coils = -1, hold_regs = -1 },
|
phy_trans = { coils = -1, hold_regs = -1 },
|
||||||
-- last set/read ports (reflecting the current state of the RTU)
|
-- last set/read ports (reflecting the current state of the RTU)
|
||||||
---@class rs_io_states
|
---@class rs_io_states
|
||||||
phy_io = {
|
phy_io = {
|
||||||
digital_in = {}, ---@type dig_phy_entry[] discrete inputs
|
digital_in = new_io_block(), ---@type dig_phy_entry[][] discrete inputs
|
||||||
digital_out = {}, ---@type dig_phy_entry[] coils
|
digital_out = new_io_block(), ---@type dig_phy_entry[][] coils
|
||||||
analog_in = {}, ---@type ana_phy_entry[] input registers
|
analog_in = new_io_block(), ---@type ana_phy_entry[][] input registers
|
||||||
analog_out = {} ---@type ana_phy_entry[] holding registers
|
analog_out = new_io_block() ---@type ana_phy_entry[][] holding registers
|
||||||
},
|
},
|
||||||
---@class redstone_session_db
|
---@class redstone_session_db
|
||||||
db = {
|
db = {
|
||||||
-- read/write functions for connected I/O
|
-- read/write functions for connected I/O
|
||||||
---@type (rs_db_dig_io|rs_db_ana_io)[]
|
---@type (rs_db_dig_io|rs_db_ana_io)[][]
|
||||||
io = {}
|
io = new_io_block()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,93 +105,91 @@ function redstone.new(session_id, unit_id, advert, out_queue)
|
|||||||
|
|
||||||
-- INITIALIZE --
|
-- INITIALIZE --
|
||||||
|
|
||||||
-- create all ports as disconnected
|
|
||||||
for _ = 1, #IO_PORT do
|
|
||||||
table.insert(self.db, IO_LVL.DISCONNECT)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- setup I/O
|
-- setup I/O
|
||||||
for i = 1, #advert.rsio do
|
for bank = 0, 4 do
|
||||||
local port = advert.rsio[i]
|
for i = 1, #advert.rs_conns[bank] do
|
||||||
|
local port = advert.rs_conns[bank][i]
|
||||||
|
|
||||||
if rsio.is_valid_port(port) then
|
if rsio.is_valid_port(port) then
|
||||||
local mode = rsio.get_io_mode(port)
|
local mode = rsio.get_io_mode(port)
|
||||||
|
local io_entry = { bank = bank, port = port }
|
||||||
|
|
||||||
if mode == IO_MODE.DIGITAL_IN then
|
if mode == IO_MODE.DIGITAL_IN then
|
||||||
self.has_di = true
|
self.has_di = true
|
||||||
table.insert(self.io_list.digital_in, port)
|
table.insert(self.io_map.digital_in, io_entry)
|
||||||
|
|
||||||
self.phy_io.digital_in[port] = { phy = IO_LVL.FLOATING, req = IO_LVL.FLOATING }
|
self.phy_io.digital_in[bank][port] = { phy = IO_LVL.FLOATING, req = IO_LVL.FLOATING }
|
||||||
|
|
||||||
---@class rs_db_dig_io
|
---@class rs_db_dig_io
|
||||||
local io_f = {
|
local io_f = {
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
read = function () return rsio.digital_is_active(port, self.phy_io.digital_in[port].phy) end,
|
read = function () return rsio.digital_is_active(port, self.phy_io.digital_in[bank][port].phy) end,
|
||||||
write = function () end
|
write = function () end
|
||||||
}
|
}
|
||||||
|
|
||||||
self.db.io[port] = io_f
|
self.db.io[bank][port] = io_f
|
||||||
elseif mode == IO_MODE.DIGITAL_OUT then
|
elseif mode == IO_MODE.DIGITAL_OUT then
|
||||||
self.has_do = true
|
self.has_do = true
|
||||||
table.insert(self.io_list.digital_out, port)
|
table.insert(self.io_map.digital_out, io_entry)
|
||||||
|
|
||||||
self.phy_io.digital_out[port] = { phy = IO_LVL.FLOATING, req = IO_LVL.FLOATING }
|
self.phy_io.digital_out[bank][port] = { phy = IO_LVL.FLOATING, req = IO_LVL.FLOATING }
|
||||||
|
|
||||||
---@class rs_db_dig_io
|
---@class rs_db_dig_io
|
||||||
local io_f = {
|
local io_f = {
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
read = function () return rsio.digital_is_active(port, self.phy_io.digital_out[port].phy) end,
|
read = function () return rsio.digital_is_active(port, self.phy_io.digital_out[bank][port].phy) end,
|
||||||
---@param active boolean
|
---@param active boolean
|
||||||
write = function (active)
|
write = function (active)
|
||||||
local level = rsio.digital_write_active(port, active)
|
local level = rsio.digital_write_active(port, active)
|
||||||
if level ~= nil then self.phy_io.digital_out[port].req = level end
|
if level ~= nil then self.phy_io.digital_out[bank][port].req = level end
|
||||||
end
|
|
||||||
}
|
|
||||||
|
|
||||||
self.db.io[port] = io_f
|
|
||||||
elseif mode == IO_MODE.ANALOG_IN then
|
|
||||||
self.has_ai = true
|
|
||||||
table.insert(self.io_list.analog_in, port)
|
|
||||||
|
|
||||||
self.phy_io.analog_in[port] = { phy = 0, req = 0 }
|
|
||||||
|
|
||||||
---@class rs_db_ana_io
|
|
||||||
local io_f = {
|
|
||||||
---@nodiscard
|
|
||||||
---@return integer
|
|
||||||
read = function () return self.phy_io.analog_in[port].phy end,
|
|
||||||
write = function () end
|
|
||||||
}
|
|
||||||
|
|
||||||
self.db.io[port] = io_f
|
|
||||||
elseif mode == IO_MODE.ANALOG_OUT then
|
|
||||||
self.has_ao = true
|
|
||||||
table.insert(self.io_list.analog_out, port)
|
|
||||||
|
|
||||||
self.phy_io.analog_out[port] = { phy = 0, req = 0 }
|
|
||||||
|
|
||||||
---@class rs_db_ana_io
|
|
||||||
local io_f = {
|
|
||||||
---@nodiscard
|
|
||||||
---@return integer
|
|
||||||
read = function () return self.phy_io.analog_out[port].phy end,
|
|
||||||
---@param value integer
|
|
||||||
write = function (value)
|
|
||||||
if value >= 0 and value <= 15 then
|
|
||||||
self.phy_io.analog_out[port].req = value
|
|
||||||
end
|
end
|
||||||
end
|
}
|
||||||
}
|
|
||||||
|
|
||||||
self.db.io[port] = io_f
|
self.db.io[bank][port] = io_f
|
||||||
|
elseif mode == IO_MODE.ANALOG_IN then
|
||||||
|
self.has_ai = true
|
||||||
|
table.insert(self.io_map.analog_in, io_entry)
|
||||||
|
|
||||||
|
self.phy_io.analog_in[bank][port] = { phy = 0, req = 0 }
|
||||||
|
|
||||||
|
---@class rs_db_ana_io
|
||||||
|
local io_f = {
|
||||||
|
---@nodiscard
|
||||||
|
---@return integer
|
||||||
|
read = function () return self.phy_io.analog_in[bank][port].phy end,
|
||||||
|
write = function () end
|
||||||
|
}
|
||||||
|
|
||||||
|
self.db.io[bank][port] = io_f
|
||||||
|
elseif mode == IO_MODE.ANALOG_OUT then
|
||||||
|
self.has_ao = true
|
||||||
|
table.insert(self.io_map.analog_out, io_entry)
|
||||||
|
|
||||||
|
self.phy_io.analog_out[bank][port] = { phy = 0, req = 0 }
|
||||||
|
|
||||||
|
---@class rs_db_ana_io
|
||||||
|
local io_f = {
|
||||||
|
---@nodiscard
|
||||||
|
---@return integer
|
||||||
|
read = function () return self.phy_io.analog_out[bank][port].phy end,
|
||||||
|
---@param value integer
|
||||||
|
write = function (value)
|
||||||
|
if value >= 0 and value <= 15 then
|
||||||
|
self.phy_io.analog_out[bank][port].req = value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
self.db.io[bank][port] = io_f
|
||||||
|
else
|
||||||
|
-- should be unreachable code, we already validated ports
|
||||||
|
log.error(util.c(log_tag, "failed to identify advertisement port IO mode (", bank, ":", port, ")"), true)
|
||||||
|
return nil
|
||||||
|
end
|
||||||
else
|
else
|
||||||
-- should be unreachable code, we already validated ports
|
log.error(util.c(log_tag, "invalid advertisement port (", bank, ":", port, ")"), true)
|
||||||
log.error(util.c(log_tag, "failed to identify advertisement port IO mode (", port, ")"), true)
|
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
else
|
|
||||||
log.error(util.c(log_tag, "invalid advertisement port (", port, ")"), true)
|
|
||||||
return nil
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -197,12 +197,12 @@ function redstone.new(session_id, unit_id, advert, out_queue)
|
|||||||
|
|
||||||
-- query discrete inputs
|
-- query discrete inputs
|
||||||
local function _request_discrete_inputs()
|
local function _request_discrete_inputs()
|
||||||
self.session.send_request(TXN_TYPES.DI_READ, MODBUS_FCODE.READ_DISCRETE_INPUTS, { 1, #self.io_list.digital_in })
|
self.session.send_request(TXN_TYPES.DI_READ, MODBUS_FCODE.READ_DISCRETE_INPUTS, { 1, #self.io_map.digital_in })
|
||||||
end
|
end
|
||||||
|
|
||||||
-- query input registers
|
-- query input registers
|
||||||
local function _request_input_registers()
|
local function _request_input_registers()
|
||||||
self.session.send_request(TXN_TYPES.INPUT_REG_READ, MODBUS_FCODE.READ_INPUT_REGS, { 1, #self.io_list.analog_in })
|
self.session.send_request(TXN_TYPES.INPUT_REG_READ, MODBUS_FCODE.READ_INPUT_REGS, { 1, #self.io_map.analog_in })
|
||||||
end
|
end
|
||||||
|
|
||||||
-- write all coil outputs
|
-- write all coil outputs
|
||||||
@@ -210,9 +210,9 @@ function redstone.new(session_id, unit_id, advert, out_queue)
|
|||||||
local params = { 1 }
|
local params = { 1 }
|
||||||
|
|
||||||
local outputs = self.phy_io.digital_out
|
local outputs = self.phy_io.digital_out
|
||||||
for i = 1, #self.io_list.digital_out do
|
for i = 1, #self.io_map.digital_out do
|
||||||
local port = self.io_list.digital_out[i]
|
local entry = self.io_map.digital_out[i]
|
||||||
table.insert(params, outputs[port].req)
|
table.insert(params, outputs[entry.bank][entry.port].req)
|
||||||
end
|
end
|
||||||
|
|
||||||
self.phy_trans.coils = self.session.send_request(TXN_TYPES.COIL_WRITE, MODBUS_FCODE.WRITE_MUL_COILS, params)
|
self.phy_trans.coils = self.session.send_request(TXN_TYPES.COIL_WRITE, MODBUS_FCODE.WRITE_MUL_COILS, params)
|
||||||
@@ -220,7 +220,7 @@ function redstone.new(session_id, unit_id, advert, out_queue)
|
|||||||
|
|
||||||
-- read all coil outputs
|
-- read all coil outputs
|
||||||
local function _read_coils()
|
local function _read_coils()
|
||||||
self.session.send_request(TXN_TYPES.COIL_READ, MODBUS_FCODE.READ_COILS, { 1, #self.io_list.digital_out })
|
self.session.send_request(TXN_TYPES.COIL_READ, MODBUS_FCODE.READ_COILS, { 1, #self.io_map.digital_out })
|
||||||
end
|
end
|
||||||
|
|
||||||
-- write all holding register outputs
|
-- write all holding register outputs
|
||||||
@@ -228,9 +228,9 @@ function redstone.new(session_id, unit_id, advert, out_queue)
|
|||||||
local params = { 1 }
|
local params = { 1 }
|
||||||
|
|
||||||
local outputs = self.phy_io.analog_out
|
local outputs = self.phy_io.analog_out
|
||||||
for i = 1, #self.io_list.analog_out do
|
for i = 1, #self.io_map.analog_out do
|
||||||
local port = self.io_list.analog_out[i]
|
local entry = self.io_map.analog_out[i]
|
||||||
table.insert(params, outputs[port].req)
|
table.insert(params, outputs[entry.bank][entry.port].req)
|
||||||
end
|
end
|
||||||
|
|
||||||
self.phy_trans.hold_regs = self.session.send_request(TXN_TYPES.HOLD_REG_WRITE, MODBUS_FCODE.WRITE_MUL_HOLD_REGS, params)
|
self.phy_trans.hold_regs = self.session.send_request(TXN_TYPES.HOLD_REG_WRITE, MODBUS_FCODE.WRITE_MUL_HOLD_REGS, params)
|
||||||
@@ -238,7 +238,7 @@ function redstone.new(session_id, unit_id, advert, out_queue)
|
|||||||
|
|
||||||
-- read all holding register outputs
|
-- read all holding register outputs
|
||||||
local function _read_holding_registers()
|
local function _read_holding_registers()
|
||||||
self.session.send_request(TXN_TYPES.HOLD_REG_READ, MODBUS_FCODE.READ_MUL_HOLD_REGS, { 1, #self.io_list.analog_out })
|
self.session.send_request(TXN_TYPES.HOLD_REG_READ, MODBUS_FCODE.READ_MUL_HOLD_REGS, { 1, #self.io_map.analog_out })
|
||||||
end
|
end
|
||||||
|
|
||||||
-- PUBLIC FUNCTIONS --
|
-- PUBLIC FUNCTIONS --
|
||||||
@@ -259,24 +259,24 @@ function redstone.new(session_id, unit_id, advert, out_queue)
|
|||||||
end
|
end
|
||||||
elseif txn_type == TXN_TYPES.DI_READ then
|
elseif txn_type == TXN_TYPES.DI_READ then
|
||||||
-- discrete input read response
|
-- discrete input read response
|
||||||
if m_pkt.length == #self.io_list.digital_in then
|
if m_pkt.length == #self.io_map.digital_in then
|
||||||
for i = 1, m_pkt.length do
|
for i = 1, m_pkt.length do
|
||||||
local port = self.io_list.digital_in[i]
|
local entry = self.io_map.digital_in[i]
|
||||||
local value = m_pkt.data[i]
|
local value = m_pkt.data[i]
|
||||||
|
|
||||||
self.phy_io.digital_in[port].phy = value
|
self.phy_io.digital_in[entry.bank][entry.port].phy = value
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")")
|
log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")")
|
||||||
end
|
end
|
||||||
elseif txn_type == TXN_TYPES.INPUT_REG_READ then
|
elseif txn_type == TXN_TYPES.INPUT_REG_READ then
|
||||||
-- input register read response
|
-- input register read response
|
||||||
if m_pkt.length == #self.io_list.analog_in then
|
if m_pkt.length == #self.io_map.analog_in then
|
||||||
for i = 1, m_pkt.length do
|
for i = 1, m_pkt.length do
|
||||||
local port = self.io_list.analog_in[i]
|
local entry = self.io_map.analog_in[i]
|
||||||
local value = m_pkt.data[i]
|
local value = m_pkt.data[i]
|
||||||
|
|
||||||
self.phy_io.analog_in[port].phy = value
|
self.phy_io.analog_in[entry.bank][entry.port].phy = value
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")")
|
log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")")
|
||||||
@@ -288,15 +288,14 @@ function redstone.new(session_id, unit_id, advert, out_queue)
|
|||||||
-- update phy I/O table
|
-- update phy I/O table
|
||||||
-- if there are multiple outputs for the same port, they will overwrite eachother (but *should* be identical)
|
-- if there are multiple outputs for the same port, they will overwrite eachother (but *should* be identical)
|
||||||
-- given these are redstone outputs, if one worked they all should have, so no additional verification will be done
|
-- given these are redstone outputs, if one worked they all should have, so no additional verification will be done
|
||||||
if m_pkt.length == #self.io_list.digital_out then
|
if m_pkt.length == #self.io_map.digital_out then
|
||||||
for i = 1, m_pkt.length do
|
for i = 1, m_pkt.length do
|
||||||
local port = self.io_list.digital_out[i]
|
local entry = self.io_map.digital_out[i]
|
||||||
|
local state = self.phy_io.digital_out[entry.bank][entry.port]
|
||||||
local value = m_pkt.data[i]
|
local value = m_pkt.data[i]
|
||||||
|
|
||||||
self.phy_io.digital_out[port].phy = value
|
state.phy = value
|
||||||
if self.phy_io.digital_out[port].req == IO_LVL.FLOATING then
|
if state.req == IO_LVL.FLOATING then state.req = value end
|
||||||
self.phy_io.digital_out[port].req = value
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
self.phy_trans.coils = TXN_READY
|
self.phy_trans.coils = TXN_READY
|
||||||
@@ -310,12 +309,12 @@ function redstone.new(session_id, unit_id, advert, out_queue)
|
|||||||
-- update phy I/O table
|
-- update phy I/O table
|
||||||
-- if there are multiple outputs for the same port, they will overwrite eachother (but *should* be identical)
|
-- if there are multiple outputs for the same port, they will overwrite eachother (but *should* be identical)
|
||||||
-- given these are redstone outputs, if one worked they all should have, so no additional verification will be done
|
-- given these are redstone outputs, if one worked they all should have, so no additional verification will be done
|
||||||
if m_pkt.length == #self.io_list.analog_out then
|
if m_pkt.length == #self.io_map.analog_out then
|
||||||
for i = 1, m_pkt.length do
|
for i = 1, m_pkt.length do
|
||||||
local port = self.io_list.analog_out[i]
|
local entry = self.io_map.analog_out[i]
|
||||||
local value = m_pkt.data[i]
|
local value = m_pkt.data[i]
|
||||||
|
|
||||||
self.phy_io.analog_out[port].phy = value
|
self.phy_io.analog_out[entry.bank][entry.port].phy = value
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")")
|
log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")")
|
||||||
@@ -343,8 +342,17 @@ function redstone.new(session_id, unit_id, advert, out_queue)
|
|||||||
-- sync digital outputs
|
-- sync digital outputs
|
||||||
if self.has_do then
|
if self.has_do then
|
||||||
if (self.periodics.next_cl_sync <= time_now) and (self.phy_trans.coils == TXN_READY) then
|
if (self.periodics.next_cl_sync <= time_now) and (self.phy_trans.coils == TXN_READY) then
|
||||||
for _, entry in pairs(self.phy_io.digital_out) do
|
for bank = 0, 4 do
|
||||||
if entry.phy ~= entry.req then
|
local changed = false
|
||||||
|
|
||||||
|
for _, entry in pairs(self.phy_io.digital_out[bank]) do
|
||||||
|
if entry.phy ~= entry.req then
|
||||||
|
changed = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if changed then
|
||||||
_write_coils()
|
_write_coils()
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
@@ -365,8 +373,17 @@ function redstone.new(session_id, unit_id, advert, out_queue)
|
|||||||
-- sync analog outputs
|
-- sync analog outputs
|
||||||
if self.has_ao then
|
if self.has_ao then
|
||||||
if (self.periodics.next_hr_sync <= time_now) and (self.phy_trans.hold_regs == TXN_READY) then
|
if (self.periodics.next_hr_sync <= time_now) and (self.phy_trans.hold_regs == TXN_READY) then
|
||||||
for _, entry in pairs(self.phy_io.analog_out) do
|
for bank = 0, 4 do
|
||||||
if entry.phy ~= entry.req then
|
local changed = false
|
||||||
|
|
||||||
|
for _, entry in pairs(self.phy_io.analog_out[bank]) do
|
||||||
|
if entry.phy ~= entry.req then
|
||||||
|
changed = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if changed then
|
||||||
_write_holding_registers()
|
_write_holding_registers()
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
@@ -379,9 +396,10 @@ function redstone.new(session_id, unit_id, advert, out_queue)
|
|||||||
self.session.post_update()
|
self.session.post_update()
|
||||||
end
|
end
|
||||||
|
|
||||||
-- invalidate build cache
|
-- force a re-read of cached outputs
|
||||||
function public.invalidate_cache()
|
function public.invalidate_cache()
|
||||||
-- no build cache for this device
|
_read_coils()
|
||||||
|
_read_holding_registers()
|
||||||
end
|
end
|
||||||
|
|
||||||
-- get the unit session database
|
-- get the unit session database
|
||||||
|
|||||||
@@ -80,21 +80,30 @@ function sna.new(session_id, unit_id, advert, out_queue)
|
|||||||
-- PRIVATE FUNCTIONS --
|
-- PRIVATE FUNCTIONS --
|
||||||
|
|
||||||
-- query the build of the device
|
-- query the build of the device
|
||||||
local function _request_build()
|
---@param time_now integer
|
||||||
|
local function _request_build(time_now)
|
||||||
-- read input registers 1 through 2 (start = 1, count = 2)
|
-- read input registers 1 through 2 (start = 1, count = 2)
|
||||||
self.session.send_request(TXN_TYPES.BUILD, MODBUS_FCODE.READ_INPUT_REGS, { 1, 2 })
|
if self.session.send_request(TXN_TYPES.BUILD, MODBUS_FCODE.READ_INPUT_REGS, { 1, 2 }) ~= false then
|
||||||
|
self.periodics.next_build_req = time_now + PERIODICS.BUILD
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- query the state of the device
|
-- query the state of the device
|
||||||
local function _request_state()
|
---@param time_now integer
|
||||||
|
local function _request_state(time_now)
|
||||||
-- read input registers 3 through 4 (start = 3, count = 2)
|
-- read input registers 3 through 4 (start = 3, count = 2)
|
||||||
self.session.send_request(TXN_TYPES.STATE, MODBUS_FCODE.READ_INPUT_REGS, { 3, 2 })
|
if self.session.send_request(TXN_TYPES.STATE, MODBUS_FCODE.READ_INPUT_REGS, { 3, 2 }) ~= false then
|
||||||
|
self.periodics.next_state_req = time_now + PERIODICS.STATE
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- query the tanks of the device
|
-- query the tanks of the device
|
||||||
local function _request_tanks()
|
---@param time_now integer
|
||||||
|
local function _request_tanks(time_now)
|
||||||
-- read input registers 5 through 10 (start = 5, count = 6)
|
-- read input registers 5 through 10 (start = 5, count = 6)
|
||||||
self.session.send_request(TXN_TYPES.TANKS, MODBUS_FCODE.READ_INPUT_REGS, { 5, 6 })
|
if self.session.send_request(TXN_TYPES.TANKS, MODBUS_FCODE.READ_INPUT_REGS, { 5, 6 }) ~= false then
|
||||||
|
self.periodics.next_tanks_req = time_now + PERIODICS.TANKS
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- PUBLIC FUNCTIONS --
|
-- PUBLIC FUNCTIONS --
|
||||||
@@ -152,20 +161,9 @@ function sna.new(session_id, unit_id, advert, out_queue)
|
|||||||
-- update this runner
|
-- update this runner
|
||||||
---@param time_now integer milliseconds
|
---@param time_now integer milliseconds
|
||||||
function public.update(time_now)
|
function public.update(time_now)
|
||||||
if not self.has_build and self.periodics.next_build_req <= time_now then
|
if not self.has_build and self.periodics.next_build_req <= time_now then _request_build(time_now) end
|
||||||
_request_build()
|
if self.periodics.next_state_req <= time_now then _request_state(time_now) end
|
||||||
self.periodics.next_build_req = time_now + PERIODICS.BUILD
|
if self.periodics.next_tanks_req <= time_now then _request_tanks(time_now) end
|
||||||
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
|
|
||||||
|
|
||||||
self.session.post_update()
|
self.session.post_update()
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -94,27 +94,39 @@ function sps.new(session_id, unit_id, advert, out_queue)
|
|||||||
-- PRIVATE FUNCTIONS --
|
-- PRIVATE FUNCTIONS --
|
||||||
|
|
||||||
-- query if the multiblock is formed
|
-- query if the multiblock is formed
|
||||||
local function _request_formed()
|
---@param time_now integer
|
||||||
|
local function _request_formed(time_now)
|
||||||
-- read discrete input 1 (start = 1, count = 1)
|
-- read discrete input 1 (start = 1, count = 1)
|
||||||
self.session.send_request(TXN_TYPES.FORMED, MODBUS_FCODE.READ_DISCRETE_INPUTS, { 1, 1 })
|
if self.session.send_request(TXN_TYPES.FORMED, MODBUS_FCODE.READ_DISCRETE_INPUTS, { 1, 1 }) ~= false then
|
||||||
|
self.periodics.next_formed_req = time_now + PERIODICS.FORMED
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- query the build of the device
|
-- query the build of the device
|
||||||
local function _request_build()
|
---@param time_now integer
|
||||||
|
local function _request_build(time_now)
|
||||||
-- read input registers 1 through 9 (start = 1, count = 9)
|
-- read input registers 1 through 9 (start = 1, count = 9)
|
||||||
self.session.send_request(TXN_TYPES.BUILD, MODBUS_FCODE.READ_INPUT_REGS, { 1, 9 })
|
if self.session.send_request(TXN_TYPES.BUILD, MODBUS_FCODE.READ_INPUT_REGS, { 1, 9 }) ~= false then
|
||||||
|
self.periodics.next_build_req = time_now + PERIODICS.BUILD
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- query the state of the device
|
-- query the state of the device
|
||||||
local function _request_state()
|
---@param time_now integer
|
||||||
|
local function _request_state(time_now)
|
||||||
-- read input register 10 (start = 10, count = 1)
|
-- read input register 10 (start = 10, count = 1)
|
||||||
self.session.send_request(TXN_TYPES.STATE, MODBUS_FCODE.READ_INPUT_REGS, { 10, 1 })
|
if self.session.send_request(TXN_TYPES.STATE, MODBUS_FCODE.READ_INPUT_REGS, { 10, 1 }) ~= false then
|
||||||
|
self.periodics.next_state_req = time_now + PERIODICS.STATE
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- query the tanks of the device
|
-- query the tanks of the device
|
||||||
local function _request_tanks()
|
---@param time_now integer
|
||||||
|
local function _request_tanks(time_now)
|
||||||
-- read input registers 11 through 19 (start = 11, count = 9)
|
-- read input registers 11 through 19 (start = 11, count = 9)
|
||||||
self.session.send_request(TXN_TYPES.TANKS, MODBUS_FCODE.READ_INPUT_REGS, { 11, 9 })
|
if self.session.send_request(TXN_TYPES.TANKS, MODBUS_FCODE.READ_INPUT_REGS, { 11, 9 }) ~= false then
|
||||||
|
self.periodics.next_tanks_req = time_now + PERIODICS.TANKS
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- PUBLIC FUNCTIONS --
|
-- PUBLIC FUNCTIONS --
|
||||||
@@ -191,26 +203,12 @@ function sps.new(session_id, unit_id, advert, out_queue)
|
|||||||
-- update this runner
|
-- update this runner
|
||||||
---@param time_now integer milliseconds
|
---@param time_now integer milliseconds
|
||||||
function public.update(time_now)
|
function public.update(time_now)
|
||||||
if self.periodics.next_formed_req <= time_now then
|
if self.periodics.next_formed_req <= time_now then _request_formed(time_now) end
|
||||||
_request_formed()
|
|
||||||
self.periodics.next_formed_req = time_now + PERIODICS.FORMED
|
|
||||||
end
|
|
||||||
|
|
||||||
if self.db.formed then
|
if self.db.formed then
|
||||||
if not self.has_build and self.periodics.next_build_req <= time_now then
|
if not self.has_build and self.periodics.next_build_req <= time_now then _request_build(time_now) end
|
||||||
_request_build()
|
if self.periodics.next_state_req <= time_now then _request_state(time_now) end
|
||||||
self.periodics.next_build_req = time_now + PERIODICS.BUILD
|
if self.periodics.next_tanks_req <= time_now then _request_tanks(time_now) end
|
||||||
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
|
end
|
||||||
|
|
||||||
self.session.post_update()
|
self.session.post_update()
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ local PERIODICS = {
|
|||||||
TANKS = 1000
|
TANKS = 1000
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local WRITE_BUSY_WAIT = 1000
|
||||||
|
|
||||||
-- create a new turbinev rtu session runner
|
-- create a new turbinev rtu session runner
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
---@param session_id integer RTU gateway session ID
|
---@param session_id integer RTU gateway session ID
|
||||||
@@ -63,6 +65,8 @@ function turbinev.new(session_id, unit_id, advert, out_queue)
|
|||||||
local self = {
|
local self = {
|
||||||
session = unit_session.new(session_id, unit_id, advert, out_queue, log_tag, TXN_TAGS),
|
session = unit_session.new(session_id, unit_id, advert, out_queue, log_tag, TXN_TAGS),
|
||||||
has_build = false,
|
has_build = false,
|
||||||
|
mode_cmd = nil, ---@type dumping_mode|nil
|
||||||
|
resend_mode = false,
|
||||||
periodics = {
|
periodics = {
|
||||||
next_formed_req = 0,
|
next_formed_req = 0,
|
||||||
next_build_req = 0,
|
next_build_req = 0,
|
||||||
@@ -116,45 +120,77 @@ function turbinev.new(session_id, unit_id, advert, out_queue)
|
|||||||
|
|
||||||
-- increment the dumping mode
|
-- increment the dumping mode
|
||||||
local function _inc_dump_mode()
|
local function _inc_dump_mode()
|
||||||
|
-- set mode command
|
||||||
|
if self.mode_cmd == "IDLE" then self.mode_cmd = "DUMPING_EXCESS"
|
||||||
|
elseif self.mode_cmd == "DUMPING_EXCESS" then self.mode_cmd = "DUMPING"
|
||||||
|
elseif self.mode_cmd == "DUMPING" then self.mode_cmd = "IDLE"
|
||||||
|
end
|
||||||
|
|
||||||
-- write coil 1 with unused value 0
|
-- write coil 1 with unused value 0
|
||||||
self.session.send_request(TXN_TYPES.INC_DUMP, MODBUS_FCODE.WRITE_SINGLE_COIL, { 1, 0 })
|
if self.session.send_request(TXN_TYPES.INC_DUMP, MODBUS_FCODE.WRITE_SINGLE_COIL, { 1, 0 }, WRITE_BUSY_WAIT) == false then
|
||||||
|
self.resend_mode = true
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- decrement the dumping mode
|
-- decrement the dumping mode
|
||||||
local function _dec_dump_mode()
|
local function _dec_dump_mode()
|
||||||
|
-- set mode command
|
||||||
|
if self.mode_cmd == "IDLE" then self.mode_cmd = "DUMPING"
|
||||||
|
elseif self.mode_cmd == "DUMPING_EXCESS" then self.mode_cmd = "IDLE"
|
||||||
|
elseif self.mode_cmd == "DUMPING" then self.mode_cmd = "DUMPING_EXCESS"
|
||||||
|
end
|
||||||
|
|
||||||
-- write coil 2 with unused value 0
|
-- write coil 2 with unused value 0
|
||||||
self.session.send_request(TXN_TYPES.DEC_DUMP, MODBUS_FCODE.WRITE_SINGLE_COIL, { 2, 0 })
|
if self.session.send_request(TXN_TYPES.DEC_DUMP, MODBUS_FCODE.WRITE_SINGLE_COIL, { 2, 0 }, WRITE_BUSY_WAIT) == false then
|
||||||
|
self.resend_mode = true
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- set the dumping mode
|
-- set the dumping mode
|
||||||
---@param mode dumping_mode
|
---@param mode dumping_mode
|
||||||
local function _set_dump_mode(mode)
|
local function _set_dump_mode(mode)
|
||||||
|
self.mode_cmd = mode
|
||||||
|
|
||||||
-- write holding register 1
|
-- write holding register 1
|
||||||
self.session.send_request(TXN_TYPES.SET_DUMP, MODBUS_FCODE.WRITE_SINGLE_HOLD_REG, { 1, mode })
|
if self.session.send_request(TXN_TYPES.SET_DUMP, MODBUS_FCODE.WRITE_SINGLE_HOLD_REG, { 1, mode }, WRITE_BUSY_WAIT) == false then
|
||||||
|
self.resend_mode = true
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- query if the multiblock is formed
|
-- query if the multiblock is formed
|
||||||
local function _request_formed()
|
---@param time_now integer
|
||||||
|
local function _request_formed(time_now)
|
||||||
-- read discrete input 1 (start = 1, count = 1)
|
-- read discrete input 1 (start = 1, count = 1)
|
||||||
self.session.send_request(TXN_TYPES.FORMED, MODBUS_FCODE.READ_DISCRETE_INPUTS, { 1, 1 })
|
if self.session.send_request(TXN_TYPES.FORMED, MODBUS_FCODE.READ_DISCRETE_INPUTS, { 1, 1 }) ~= false then
|
||||||
|
self.periodics.next_formed_req = time_now + PERIODICS.FORMED
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- query the build of the device
|
-- query the build of the device
|
||||||
local function _request_build()
|
---@param time_now integer
|
||||||
|
local function _request_build(time_now)
|
||||||
-- read input registers 1 through 15 (start = 1, count = 15)
|
-- read input registers 1 through 15 (start = 1, count = 15)
|
||||||
self.session.send_request(TXN_TYPES.BUILD, MODBUS_FCODE.READ_INPUT_REGS, { 1, 15 })
|
if self.session.send_request(TXN_TYPES.BUILD, MODBUS_FCODE.READ_INPUT_REGS, { 1, 15 }) ~= false then
|
||||||
|
self.periodics.next_build_req = time_now + PERIODICS.BUILD
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- query the state of the device
|
-- query the state of the device
|
||||||
local function _request_state()
|
---@param time_now integer
|
||||||
|
local function _request_state(time_now)
|
||||||
-- read input registers 16 through 19 (start = 16, count = 4)
|
-- read input registers 16 through 19 (start = 16, count = 4)
|
||||||
self.session.send_request(TXN_TYPES.STATE, MODBUS_FCODE.READ_INPUT_REGS, { 16, 4 })
|
if self.session.send_request(TXN_TYPES.STATE, MODBUS_FCODE.READ_INPUT_REGS, { 16, 4 }) ~= false then
|
||||||
|
self.periodics.next_state_req = time_now + PERIODICS.STATE
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- query the tanks of the device
|
-- query the tanks of the device
|
||||||
local function _request_tanks()
|
---@param time_now integer
|
||||||
|
local function _request_tanks(time_now)
|
||||||
-- read input registers 20 through 25 (start = 20, count = 6)
|
-- read input registers 20 through 25 (start = 20, count = 6)
|
||||||
self.session.send_request(TXN_TYPES.TANKS, MODBUS_FCODE.READ_INPUT_REGS, { 20, 6 })
|
if self.session.send_request(TXN_TYPES.TANKS, MODBUS_FCODE.READ_INPUT_REGS, { 20, 6 }) ~= false then
|
||||||
|
self.periodics.next_tanks_req = time_now + PERIODICS.TANKS
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- PUBLIC FUNCTIONS --
|
-- PUBLIC FUNCTIONS --
|
||||||
@@ -208,6 +244,10 @@ function turbinev.new(session_id, unit_id, advert, out_queue)
|
|||||||
self.db.state.prod_rate = m_pkt.data[2]
|
self.db.state.prod_rate = m_pkt.data[2]
|
||||||
self.db.state.steam_input_rate = m_pkt.data[3]
|
self.db.state.steam_input_rate = m_pkt.data[3]
|
||||||
self.db.state.dumping_mode = m_pkt.data[4]
|
self.db.state.dumping_mode = m_pkt.data[4]
|
||||||
|
|
||||||
|
if self.mode_cmd == nil then
|
||||||
|
self.mode_cmd = self.db.state.dumping_mode
|
||||||
|
end
|
||||||
else
|
else
|
||||||
log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")")
|
log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")")
|
||||||
end
|
end
|
||||||
@@ -277,30 +317,22 @@ function turbinev.new(session_id, unit_id, advert, out_queue)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- try to resend mode if needed
|
||||||
|
if self.resend_mode then
|
||||||
|
self.resend_mode = false
|
||||||
|
_set_dump_mode(self.mode_cmd)
|
||||||
|
end
|
||||||
|
|
||||||
time_now = util.time()
|
time_now = util.time()
|
||||||
|
|
||||||
-- handle periodics
|
-- handle periodics
|
||||||
|
|
||||||
if self.periodics.next_formed_req <= time_now then
|
if self.periodics.next_formed_req <= time_now then _request_formed(time_now) end
|
||||||
_request_formed()
|
|
||||||
self.periodics.next_formed_req = time_now + PERIODICS.FORMED
|
|
||||||
end
|
|
||||||
|
|
||||||
if self.db.formed then
|
if self.db.formed then
|
||||||
if not self.has_build and self.periodics.next_build_req <= time_now then
|
if not self.has_build and self.periodics.next_build_req <= time_now then _request_build(time_now) end
|
||||||
_request_build()
|
if self.periodics.next_state_req <= time_now then _request_state(time_now) end
|
||||||
self.periodics.next_build_req = time_now + PERIODICS.BUILD
|
if self.periodics.next_tanks_req <= time_now then _request_tanks(time_now) end
|
||||||
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
|
end
|
||||||
|
|
||||||
self.session.post_update()
|
self.session.post_update()
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ local RTU_US_DATA = {
|
|||||||
unit_session.RTU_US_CMDS = RTU_US_CMDS
|
unit_session.RTU_US_CMDS = RTU_US_CMDS
|
||||||
unit_session.RTU_US_DATA = RTU_US_DATA
|
unit_session.RTU_US_DATA = RTU_US_DATA
|
||||||
|
|
||||||
|
local DEFAULT_BUSY_WAIT = 3000
|
||||||
|
|
||||||
-- create a new unit session runner
|
-- create a new unit session runner
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
---@param session_id integer RTU gateway session ID
|
---@param session_id integer RTU gateway session ID
|
||||||
@@ -36,7 +38,8 @@ function unit_session.new(session_id, unit_id, advert, out_queue, log_tag, txn_t
|
|||||||
reactor = advert.reactor,
|
reactor = advert.reactor,
|
||||||
transaction_controller = txnctrl.new(),
|
transaction_controller = txnctrl.new(),
|
||||||
connected = true,
|
connected = true,
|
||||||
device_fail = false
|
device_fail = false,
|
||||||
|
last_busy = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
---@class _unit_session
|
---@class _unit_session
|
||||||
@@ -53,14 +56,21 @@ function unit_session.new(session_id, unit_id, advert, out_queue, log_tag, txn_t
|
|||||||
---@param txn_type integer transaction type
|
---@param txn_type integer transaction type
|
||||||
---@param f_code MODBUS_FCODE function code
|
---@param f_code MODBUS_FCODE function code
|
||||||
---@param register_param (number|string)[] register range or register and values
|
---@param register_param (number|string)[] register range or register and values
|
||||||
---@return integer txn_id transaction ID of this transaction
|
---@param busy_wait integer|nil milliseconds to wait (>0), or uses the default
|
||||||
function protected.send_request(txn_type, f_code, register_param)
|
---@return integer|false txn_id transaction ID of this transaction or false if not sent due to being busy
|
||||||
local m_pkt = comms.modbus_packet()
|
function protected.send_request(txn_type, f_code, register_param, busy_wait)
|
||||||
local txn_id = self.transaction_controller.create(txn_type)
|
local txn_id = false ---@type integer|false
|
||||||
|
|
||||||
m_pkt.make(txn_id, unit_id, f_code, register_param)
|
busy_wait = busy_wait or DEFAULT_BUSY_WAIT
|
||||||
|
|
||||||
out_queue.push_packet(m_pkt)
|
if (util.time_ms() - self.last_busy) >= busy_wait then
|
||||||
|
local m_pkt = comms.modbus_packet()
|
||||||
|
txn_id = self.transaction_controller.create(txn_type)
|
||||||
|
|
||||||
|
m_pkt.make(txn_id, unit_id, f_code, register_param)
|
||||||
|
|
||||||
|
out_queue.push_packet(m_pkt)
|
||||||
|
end
|
||||||
|
|
||||||
return txn_id
|
return txn_id
|
||||||
end
|
end
|
||||||
@@ -99,9 +109,9 @@ function unit_session.new(session_id, unit_id, advert, out_queue, log_tag, txn_t
|
|||||||
-- will have to wait on reply, renew the transaction
|
-- will have to wait on reply, renew the transaction
|
||||||
self.transaction_controller.renew(m_pkt.txn_id, txn_type)
|
self.transaction_controller.renew(m_pkt.txn_id, txn_type)
|
||||||
elseif ex == MODBUS_EXCODE.SERVER_DEVICE_BUSY then
|
elseif ex == MODBUS_EXCODE.SERVER_DEVICE_BUSY then
|
||||||
-- will have to wait on reply, renew the transaction
|
-- will have to try again later
|
||||||
self.transaction_controller.renew(m_pkt.txn_id, txn_type)
|
self.last_busy = util.time_ms()
|
||||||
log.debug(log_tag .. "MODBUS: device busy" .. txn_tag)
|
log.warning(log_tag .. "MODBUS: device busy" .. txn_tag)
|
||||||
elseif ex == MODBUS_EXCODE.NEG_ACKNOWLEDGE then
|
elseif ex == MODBUS_EXCODE.NEG_ACKNOWLEDGE then
|
||||||
-- general failure
|
-- general failure
|
||||||
log.error(log_tag .. "MODBUS: negative acknowledge (bad request)" .. txn_tag)
|
log.error(log_tag .. "MODBUS: negative acknowledge (bad request)" .. txn_tag)
|
||||||
|
|||||||
@@ -45,13 +45,16 @@ local self = {
|
|||||||
fp_ok = false,
|
fp_ok = false,
|
||||||
config = nil, ---@type svr_config
|
config = nil, ---@type svr_config
|
||||||
facility = nil, ---@type facility|nil
|
facility = nil, ---@type facility|nil
|
||||||
|
plc_ini_reset = {},
|
||||||
-- lists of connected sessions
|
-- lists of connected sessions
|
||||||
|
---@diagnostic disable: missing-fields
|
||||||
sessions = {
|
sessions = {
|
||||||
rtu = {}, ---@type rtu_session_struct
|
rtu = {}, ---@type rtu_session_struct
|
||||||
plc = {}, ---@type plc_session_struct
|
plc = {}, ---@type plc_session_struct
|
||||||
crd = {}, ---@type crd_session_struct
|
crd = {}, ---@type crd_session_struct
|
||||||
pdg = {} ---@type pdg_session_struct
|
pdg = {} ---@type pdg_session_struct
|
||||||
},
|
},
|
||||||
|
---@diagnostic enable: missing-fields
|
||||||
-- next session IDs
|
-- next session IDs
|
||||||
next_ids = { rtu = 0, plc = 0, crd = 0, pdg = 0 },
|
next_ids = { rtu = 0, plc = 0, crd = 0, pdg = 0 },
|
||||||
-- rtu device tracking and invalid assignment detection
|
-- rtu device tracking and invalid assignment detection
|
||||||
@@ -389,6 +392,7 @@ function svsessions.init(nic, fp_ok, config, facility)
|
|||||||
conns.tanks[1] = true
|
conns.tanks[1] = true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
self.plc_ini_reset[i] = true
|
||||||
self.dev_dbg.connected.units[i] = conns
|
self.dev_dbg.connected.units[i] = conns
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -484,7 +488,7 @@ function svsessions.establish_plc_session(source_addr, i_seq_num, for_reactor, v
|
|||||||
|
|
||||||
local id = self.next_ids.plc
|
local id = self.next_ids.plc
|
||||||
|
|
||||||
plc_s.instance = plc.new_session(id, source_addr, i_seq_num, for_reactor, plc_s.in_queue, plc_s.out_queue, self.config.PLC_Timeout, self.fp_ok)
|
plc_s.instance = plc.new_session(id, source_addr, i_seq_num, for_reactor, plc_s.in_queue, plc_s.out_queue, self.config.PLC_Timeout, self.plc_ini_reset, self.fp_ok)
|
||||||
table.insert(self.sessions.plc, plc_s)
|
table.insert(self.sessions.plc, plc_s)
|
||||||
|
|
||||||
local units = self.facility.get_units()
|
local units = self.facility.get_units()
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ local log = require("scada-common.log")
|
|||||||
local network = require("scada-common.network")
|
local network = require("scada-common.network")
|
||||||
local ppm = require("scada-common.ppm")
|
local ppm = require("scada-common.ppm")
|
||||||
local tcd = require("scada-common.tcd")
|
local tcd = require("scada-common.tcd")
|
||||||
|
local types = require("scada-common.types")
|
||||||
local util = require("scada-common.util")
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
local core = require("graphics.core")
|
local core = require("graphics.core")
|
||||||
@@ -22,7 +23,7 @@ local supervisor = require("supervisor.supervisor")
|
|||||||
|
|
||||||
local svsessions = require("supervisor.session.svsessions")
|
local svsessions = require("supervisor.session.svsessions")
|
||||||
|
|
||||||
local SUPERVISOR_VERSION = "v1.5.10"
|
local SUPERVISOR_VERSION = "v1.7.0"
|
||||||
|
|
||||||
local println = util.println
|
local println = util.println
|
||||||
local println_ts = util.println_ts
|
local println_ts = util.println_ts
|
||||||
@@ -72,6 +73,21 @@ if config.FacilityTankMode > 0 then
|
|||||||
cfv.assert_type_int(def)
|
cfv.assert_type_int(def)
|
||||||
cfv.assert_range(def, 0, 2)
|
cfv.assert_range(def, 0, 2)
|
||||||
assert(cfv.valid(), "startup> invalid facility tank definition for reactor unit " .. i)
|
assert(cfv.valid(), "startup> invalid facility tank definition for reactor unit " .. i)
|
||||||
|
|
||||||
|
local entry = config.FacilityTankList[i]
|
||||||
|
cfv.assert_type_int(entry)
|
||||||
|
cfv.assert_range(entry, 0, 2)
|
||||||
|
assert(cfv.valid(), "startup> invalid facility tank list entry for tank " .. i)
|
||||||
|
|
||||||
|
local conn = config.FacilityTankConns[i]
|
||||||
|
cfv.assert_type_int(conn)
|
||||||
|
cfv.assert_range(conn, 0, #config.FacilityTankDefs)
|
||||||
|
assert(cfv.valid(), "startup> invalid facility tank connection for reactor unit " .. i)
|
||||||
|
|
||||||
|
local type = config.TankFluidTypes[i]
|
||||||
|
cfv.assert_type_int(type)
|
||||||
|
cfv.assert_range(type, 0, types.COOLANT_TYPE.SODIUM)
|
||||||
|
assert(cfv.valid(), "startup> invalid tank fluid type for tank " .. i)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -147,6 +163,9 @@ local function main()
|
|||||||
-- halve the rate heartbeat LED flash
|
-- halve the rate heartbeat LED flash
|
||||||
local heartbeat_toggle = true
|
local heartbeat_toggle = true
|
||||||
|
|
||||||
|
-- init startup recovery
|
||||||
|
sv_facility.boot_recovery_init(supervisor.boot_state)
|
||||||
|
|
||||||
-- event loop
|
-- event loop
|
||||||
while true do
|
while true do
|
||||||
local event, param1, param2, param3, param4, param5 = util.pull_event()
|
local event, param1, param2, param3, param4, param5 = util.pull_event()
|
||||||
@@ -237,6 +256,8 @@ local function main()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
sv_facility.clear_boot_state()
|
||||||
|
|
||||||
renderer.close_ui()
|
renderer.close_ui()
|
||||||
|
|
||||||
util.println_ts("exited")
|
util.println_ts("exited")
|
||||||
|
|||||||
@@ -14,18 +14,37 @@ local ESTABLISH_ACK = comms.ESTABLISH_ACK
|
|||||||
local MGMT_TYPE = comms.MGMT_TYPE
|
local MGMT_TYPE = comms.MGMT_TYPE
|
||||||
|
|
||||||
---@type svr_config
|
---@type svr_config
|
||||||
|
---@diagnostic disable-next-line: missing-fields
|
||||||
local config = {}
|
local config = {}
|
||||||
|
|
||||||
supervisor.config = config
|
supervisor.config = config
|
||||||
|
|
||||||
-- load the supervisor configuration
|
-- control state from last unexpected shutdown
|
||||||
|
supervisor.boot_state = nil ---@type sv_boot_state|nil
|
||||||
|
|
||||||
|
-- load the supervisor configuration and startup state
|
||||||
function supervisor.load_config()
|
function supervisor.load_config()
|
||||||
if not settings.load("/supervisor.settings") then return false end
|
if not settings.load("/supervisor.settings") then return false end
|
||||||
|
|
||||||
|
---@class sv_boot_state
|
||||||
|
local boot_state = {
|
||||||
|
mode = settings.get("LastProcessState"), ---@type PROCESS
|
||||||
|
unit_states = settings.get("LastUnitStates") ---@type boolean[]
|
||||||
|
}
|
||||||
|
|
||||||
|
-- only record boot state if likely valid
|
||||||
|
if type(boot_state.mode) == "number" and type(boot_state.unit_states) == "table" then
|
||||||
|
supervisor.boot_state = boot_state
|
||||||
|
end
|
||||||
|
|
||||||
config.UnitCount = settings.get("UnitCount")
|
config.UnitCount = settings.get("UnitCount")
|
||||||
config.CoolingConfig = settings.get("CoolingConfig")
|
config.CoolingConfig = settings.get("CoolingConfig")
|
||||||
config.FacilityTankMode = settings.get("FacilityTankMode")
|
config.FacilityTankMode = settings.get("FacilityTankMode")
|
||||||
config.FacilityTankDefs = settings.get("FacilityTankDefs")
|
config.FacilityTankDefs = settings.get("FacilityTankDefs")
|
||||||
|
config.FacilityTankList = settings.get("FacilityTankList")
|
||||||
|
config.FacilityTankConns = settings.get("FacilityTankConns")
|
||||||
|
config.TankFluidTypes = settings.get("TankFluidTypes")
|
||||||
|
config.AuxiliaryCoolant = settings.get("AuxiliaryCoolant")
|
||||||
config.ExtChargeIdling = settings.get("ExtChargeIdling")
|
config.ExtChargeIdling = settings.get("ExtChargeIdling")
|
||||||
|
|
||||||
config.SVR_Channel = settings.get("SVR_Channel")
|
config.SVR_Channel = settings.get("SVR_Channel")
|
||||||
@@ -55,8 +74,12 @@ function supervisor.load_config()
|
|||||||
cfv.assert_range(config.UnitCount, 1, 4)
|
cfv.assert_range(config.UnitCount, 1, 4)
|
||||||
|
|
||||||
cfv.assert_type_table(config.CoolingConfig)
|
cfv.assert_type_table(config.CoolingConfig)
|
||||||
cfv.assert_type_table(config.FacilityTankDefs)
|
|
||||||
cfv.assert_type_int(config.FacilityTankMode)
|
cfv.assert_type_int(config.FacilityTankMode)
|
||||||
|
cfv.assert_type_table(config.FacilityTankDefs)
|
||||||
|
cfv.assert_type_table(config.FacilityTankList)
|
||||||
|
cfv.assert_type_table(config.FacilityTankConns)
|
||||||
|
cfv.assert_type_table(config.TankFluidTypes)
|
||||||
|
cfv.assert_type_table(config.AuxiliaryCoolant)
|
||||||
cfv.assert_range(config.FacilityTankMode, 0, 8)
|
cfv.assert_range(config.FacilityTankMode, 0, 8)
|
||||||
|
|
||||||
cfv.assert_type_bool(config.ExtChargeIdling)
|
cfv.assert_type_bool(config.ExtChargeIdling)
|
||||||
@@ -239,20 +262,32 @@ function supervisor.comms(_version, nic, fp_ok, facility)
|
|||||||
-- PLC linking request
|
-- PLC linking request
|
||||||
if packet.length == 4 and type(packet.data[4]) == "number" then
|
if packet.length == 4 and type(packet.data[4]) == "number" then
|
||||||
local reactor_id = packet.data[4]
|
local reactor_id = packet.data[4]
|
||||||
local plc_id = svsessions.establish_plc_session(src_addr, i_seq_num, reactor_id, firmware_v)
|
|
||||||
|
|
||||||
if plc_id == false then
|
-- check ID validity
|
||||||
-- reactor already has a PLC assigned
|
if reactor_id < 1 or reactor_id > config.UnitCount then
|
||||||
if last_ack ~= ESTABLISH_ACK.COLLISION then
|
-- reactor index out of range
|
||||||
log.warning(util.c("PLC_ESTABLISH: assignment collision with reactor ", reactor_id))
|
if last_ack ~= ESTABLISH_ACK.DENY then
|
||||||
|
log.warning(util.c("PLC_ESTABLISH: denied assignment ", reactor_id, " outside of configured unit count ", config.UnitCount))
|
||||||
end
|
end
|
||||||
|
|
||||||
_send_establish(packet.scada_frame, ESTABLISH_ACK.COLLISION)
|
_send_establish(packet.scada_frame, ESTABLISH_ACK.DENY)
|
||||||
else
|
else
|
||||||
-- got an ID; assigned to a reactor successfully
|
-- try to establish the session
|
||||||
println(util.c("PLC (", firmware_v, ") [@", src_addr, "] \xbb reactor ", reactor_id, " connected"))
|
local plc_id = svsessions.establish_plc_session(src_addr, i_seq_num, reactor_id, firmware_v)
|
||||||
log.info(util.c("PLC_ESTABLISH: PLC (", firmware_v, ") [@", src_addr, "] reactor unit ", reactor_id, " PLC connected with session ID ", plc_id))
|
|
||||||
_send_establish(packet.scada_frame, ESTABLISH_ACK.ALLOW)
|
if plc_id == false then
|
||||||
|
-- reactor already has a PLC assigned
|
||||||
|
if last_ack ~= ESTABLISH_ACK.COLLISION then
|
||||||
|
log.warning(util.c("PLC_ESTABLISH: assignment collision with reactor ", reactor_id))
|
||||||
|
end
|
||||||
|
|
||||||
|
_send_establish(packet.scada_frame, ESTABLISH_ACK.COLLISION)
|
||||||
|
else
|
||||||
|
-- got an ID; assigned to a reactor successfully
|
||||||
|
println(util.c("PLC (", firmware_v, ") [@", src_addr, "] \xbb reactor ", reactor_id, " connected"))
|
||||||
|
log.info(util.c("PLC_ESTABLISH: PLC (", firmware_v, ") [@", src_addr, "] reactor unit ", reactor_id, " PLC connected with session ID ", plc_id))
|
||||||
|
_send_establish(packet.scada_frame, ESTABLISH_ACK.ALLOW)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
log.debug("PLC_ESTABLISH: packet length mismatch/bad parameter type")
|
log.debug("PLC_ESTABLISH: packet length mismatch/bad parameter type")
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user