mirror of
https://github.com/MikaylaFischler/cc-mek-scada.git
synced 2024-08-30 18:22:34 +00:00
Compare commits
275 Commits
v1.8.11-be
...
main
Author | SHA1 | Date | |
---|---|---|---|
|
07406ca5fc | ||
|
6b20445446 | ||
|
f93db02793 | ||
|
fe1b916b1f | ||
|
ebeeecc5ab | ||
|
dbabcd13b0 | ||
|
acc8e1c058 | ||
|
5a38acf2a7 | ||
|
b3be2d4bfc | ||
|
0ab2d57b66 | ||
|
183af8a5ca | ||
|
6f63092d4b | ||
|
a087eda0ee | ||
|
a1b6ff4bcc | ||
|
8c6b264f6b | ||
|
8a5c468606 | ||
|
12f187f596 | ||
|
01a1c374ab | ||
|
465875b287 | ||
|
45d4b4e653 | ||
|
fc7441b2f6 | ||
|
6917697290 | ||
|
c323967b6a | ||
|
4775639245 | ||
|
072613959c | ||
|
f259f85a99 | ||
|
e076e327d8 | ||
|
f34747372f | ||
|
affe2d6c6d | ||
|
5597ea2097 | ||
|
0f4a8b6dfc | ||
|
ab97f8935d | ||
|
b0342654e7 | ||
|
bee96ed12e | ||
|
50bd59781e | ||
|
196e0b1daf | ||
|
f725eb0eef | ||
|
bcc55628cf | ||
|
9bffd6feee | ||
|
1500004481 | ||
|
2904621e81 | ||
|
08eee198c8 | ||
|
e750ffe69d | ||
|
de6d8a89ca | ||
|
f00751edeb | ||
|
d58a6a3369 | ||
|
340c6689a9 | ||
|
7cc088ca95 | ||
|
01f6b1e190 | ||
|
3ffc79b181 | ||
|
8e4bb583a8 | ||
|
ec107929bc | ||
|
3406d12681 | ||
|
03bbf8a891 | ||
|
b61867be3c | ||
|
1358d95269 | ||
|
fd06730e46 | ||
|
fb5f3b9474 | ||
|
3afc1e6cfa | ||
|
715765d442 | ||
|
3762e9dced | ||
|
022d1f9f49 | ||
|
b1da76c2f6 | ||
|
0364b4df7b | ||
|
e04bd032fe | ||
|
34fe5dc382 | ||
|
1f8ea56095 | ||
|
f2cd98c57a | ||
|
1e341af8a5 | ||
|
2fb3d9b515 | ||
|
220f9d152f | ||
|
604b4a1927 | ||
|
0ecaa42a7f | ||
|
9614407c37 | ||
|
f1c4f8c00a | ||
|
8a409f0313 | ||
|
da68398fa4 | ||
|
375e969161 | ||
|
c93cd4d0bd | ||
|
e69cbc8633 | ||
|
ea1bcbf81c | ||
|
f13f03fddc | ||
|
72a480e475 | ||
|
ec1b56b853 | ||
|
c5fb299f55 | ||
|
63c990a3cf | ||
|
89e84f9711 | ||
|
270aeb13ca | ||
|
df8c71f12e | ||
|
347f67c8ee | ||
|
8de2d7071e | ||
|
d424cf74d3 | ||
|
a1b571d7c0 | ||
|
8e0e4df3eb | ||
|
b025958173 | ||
|
f83eecf2e2 | ||
|
0b2f7b13a1 | ||
|
3ad3cbb4eb | ||
|
e868fd3397 | ||
|
a4add9370c | ||
|
4f48ba8abc | ||
|
3cc6781844 | ||
|
c05a45c29a | ||
|
f2937b47e9 | ||
|
8e14fa1591 | ||
|
aebb9f42be | ||
|
2de30ef064 | ||
|
8dedb092e7 | ||
|
807e575580 | ||
|
55dc203cdd | ||
|
a15cbadd32 | ||
|
bc76c01aa5 | ||
|
d2bc4f6bc0 | ||
|
4cdbe3b07f | ||
|
897a3ed22d | ||
|
2bc20ec312 | ||
|
fc42049aa0 | ||
|
4a7028f401 | ||
|
006c5e6adf | ||
|
f64db66448 | ||
|
a8e6bc0e35 | ||
|
4a39ed9d38 | ||
|
9fe0669fda | ||
|
219f02b188 | ||
|
ea8f62dea6 | ||
|
1c719ad67b | ||
|
87a91e309d | ||
|
c66ad44adb | ||
|
14736e414f | ||
|
fb1f85a626 | ||
|
697a3d6f6b | ||
|
00cacd6d0a | ||
|
0b97d4d4b0 | ||
|
def5b49327 | ||
|
38457cfbbc | ||
|
e851a5275f | ||
|
5848c2ac1a | ||
|
f8a5dd9c32 | ||
|
ff8ae5e609 | ||
|
95b93eb795 | ||
|
356caf7b4d | ||
|
09c44a6969 | ||
|
83ba6e3961 | ||
|
e88e1afcc4 | ||
|
b457edbc71 | ||
|
5e70e4131e | ||
|
375b7f680e | ||
|
e37b8758cd | ||
|
9404b50a8c | ||
|
58fb35e85b | ||
|
b9030d6bed | ||
|
25ebf2c8c7 | ||
|
4d87887709 | ||
|
b63a17bda0 | ||
|
39233dae8a | ||
|
a2af0d3829 | ||
|
db901129f9 | ||
|
c1c49ea3fb | ||
|
be6c3755a4 | ||
|
ac2d189c1a | ||
|
0fa0324940 | ||
|
3181ab96f1 | ||
|
30c9215658 | ||
|
946c28c929 | ||
|
e6d6353d05 | ||
|
0e81391144 | ||
|
b18cadb53e | ||
|
9a500d8f96 | ||
|
a268a770f2 | ||
|
b96eb7d89d | ||
|
9b8947fba2 | ||
|
afc38e7e7a | ||
|
41b7a68f3e | ||
|
8968ebede3 | ||
|
50a9168b06 | ||
|
76e85da9d5 | ||
|
be560cd532 | ||
|
2b0a536292 | ||
|
afed6f514d | ||
|
3e6a0a8869 | ||
|
b99cf19be0 | ||
|
7f009f9c86 | ||
|
6a8ed311f3 | ||
|
c181142f75 | ||
|
0cb964a177 | ||
|
3cd832ca20 | ||
|
0c6c7bf9a5 | ||
|
3bfcb1d83c | ||
|
4fe6792804 | ||
|
25dc47d520 | ||
|
f958b0e3b7 | ||
|
f621ff2482 | ||
|
eb45ff899b | ||
|
e91fd2fcaa | ||
|
d35b824458 | ||
|
165d1497f8 | ||
|
50bf057ca6 | ||
|
6f768ef6b3 | ||
|
826086951e | ||
|
35bf56663f | ||
|
43b44cc425 | ||
|
7b8cea4a5c | ||
|
51d4a22532 | ||
|
fb85c2f05b | ||
|
712d018806 | ||
|
00a8d64a88 | ||
|
d9efd5b8d2 | ||
|
a786404092 | ||
|
1bd03c0b1a | ||
|
0d8d7aeb15 | ||
|
0b60dc9fa4 | ||
|
48b91ac808 | ||
|
4c9efc78b1 | ||
|
3c5857cd68 | ||
|
473b0dd10d | ||
|
d2df7db18b | ||
|
0ca002316f | ||
|
9fe34648c2 | ||
|
57dc20692b | ||
|
f22d699baf | ||
|
23b31e0049 | ||
|
2b4309afa7 | ||
|
89cd5a07f2 | ||
|
99213da760 | ||
|
f23f69d441 | ||
|
a99b7912f8 | ||
|
23ca5fb69e | ||
|
c243d064ef | ||
|
878c3b92e1 | ||
|
2aa52d2e2c | ||
|
dfc1ee6497 | ||
|
a6a1a61954 | ||
|
d0b50c834c | ||
|
612a06ba98 | ||
|
65d43d55c7 | ||
|
4fcd375ee2 | ||
|
a22f5562cf | ||
|
0365ea5e8a | ||
|
da300607f1 | ||
|
cc3174ee76 | ||
|
98c37caecd | ||
|
cc50e4a020 | ||
|
f734c4307b | ||
|
45573be8c7 | ||
|
eab1ffbe03 | ||
|
1504247b33 | ||
|
92a4277f73 | ||
|
31a663e86b | ||
|
5c0e2c32ee | ||
|
3537c59365 | ||
|
1d7104ae74 | ||
|
4629e1ba2a | ||
|
659644865a | ||
|
03d2b3f087 | ||
|
bea7b91ff1 | ||
|
b94c89f4ec | ||
|
55e4e5a68b | ||
|
e1ad76a00d | ||
|
93e4590947 | ||
|
2442e7f972 | ||
|
bb2c07963b | ||
|
44c6352a8c | ||
|
968b0a9122 | ||
|
19869416af | ||
|
598f6f08af | ||
|
f0f2aadf53 | ||
|
ce37a672a3 | ||
|
526a54903e | ||
|
e1a632dcc7 | ||
|
d5d818e625 | ||
|
03de90c3d8 | ||
|
99096e0fc9 | ||
|
0395aa95b6 | ||
|
c624baed2c | ||
|
1a40321c0f |
2
.github/workflows/check.yml
vendored
2
.github/workflows/check.yml
vendored
@ -26,4 +26,4 @@ jobs:
|
|||||||
# --no-max-line-length = Disable warnings for long line lengths
|
# --no-max-line-length = Disable warnings for long line lengths
|
||||||
# --exclude-files ... = Exclude lockbox library (external) and config files
|
# --exclude-files ... = Exclude lockbox library (external) and config files
|
||||||
# --globals ... = Override all globals overridden in .vscode/settings.json AND 'os' since CraftOS 'os' differs from Lua's 'os'
|
# --globals ... = Override all globals overridden in .vscode/settings.json AND 'os' since CraftOS 'os' differs from Lua's 'os'
|
||||||
args: . --no-max-line-length -i 121 512 542 --exclude-files ./lockbox/* ./*/config.lua --globals os _HOST bit colors fs http keys parallel periphemu peripheral read rs settings shell term textutils window
|
args: . --no-max-line-length -i 121 512 542 --exclude-files ./lockbox/* --globals os _HOST bit colors fs http keys parallel periphemu peripheral read rs settings shell term textutils window
|
||||||
|
4
.github/workflows/manifest.yml
vendored
4
.github/workflows/manifest.yml
vendored
@ -46,7 +46,7 @@ jobs:
|
|||||||
- name: Generate manifest and shields for main branch
|
- name: Generate manifest and shields for main branch
|
||||||
id: manifest-main
|
id: manifest-main
|
||||||
if: ${{ (success() || failure()) && steps.checkout-main.outcome == 'success' }}
|
if: ${{ (success() || failure()) && steps.checkout-main.outcome == 'success' }}
|
||||||
run: python imgen.py shields
|
run: python build/imgen.py shields
|
||||||
- name: Save main's manifest
|
- name: Save main's manifest
|
||||||
if: ${{ (success() || failure()) && steps.manifest-main.outcome == 'success' }}
|
if: ${{ (success() || failure()) && steps.manifest-main.outcome == 'success' }}
|
||||||
run: mv install_manifest.json deploy/manifests/main
|
run: mv install_manifest.json deploy/manifests/main
|
||||||
@ -61,7 +61,7 @@ jobs:
|
|||||||
- name: Generate manifest for devel
|
- name: Generate manifest for devel
|
||||||
id: manifest-devel
|
id: manifest-devel
|
||||||
if: ${{ (success() || failure()) && steps.checkout-devel.outcome == 'success' }}
|
if: ${{ (success() || failure()) && steps.checkout-devel.outcome == 'success' }}
|
||||||
run: python imgen.py
|
run: python build/imgen.py
|
||||||
- name: Save devel's manifest
|
- name: Save devel's manifest
|
||||||
if: ${{ (success() || failure()) && steps.manifest-devel.outcome == 'success' }}
|
if: ${{ (success() || failure()) && steps.manifest-devel.outcome == 'success' }}
|
||||||
run: mv install_manifest.json deploy/manifests/devel
|
run: mv install_manifest.json deploy/manifests/devel
|
||||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,2 +1,2 @@
|
|||||||
_notes/
|
_*/
|
||||||
/*program.sh
|
/*program.sh
|
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright © 2022 - 2024 Mikayla Fischler
|
Copyright 2022 - 2024 Mikayla Fischler
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
11
README.md
11
README.md
@ -6,9 +6,9 @@ Configurable ComputerCraft SCADA system for multi-reactor control of Mekanism fi
|
|||||||

|

|
||||||

|

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

|

|
||||||
|
|
||||||
## Released Component Versions
|
## Released Component Versions
|
||||||
|
|
||||||
@ -45,6 +45,13 @@ v10.1+ is required due to the complete support of CC:Tweaked added in Mekanism v
|
|||||||
You can install this on a ComputerCraft computer using either:
|
You can install this on a ComputerCraft computer using either:
|
||||||
* `wget https://raw.githubusercontent.com/MikaylaFischler/cc-mek-scada/main/ccmsi.lua`
|
* `wget https://raw.githubusercontent.com/MikaylaFischler/cc-mek-scada/main/ccmsi.lua`
|
||||||
* `pastebin get sqUN6VUb ccmsi.lua`
|
* `pastebin get sqUN6VUb ccmsi.lua`
|
||||||
|
* Off-line (when HTTP is disabled) installation via [release bundles](https://github.com/MikaylaFischler/cc-mek-scada/wiki/Alternative-Installation-Strategies#release-bundles)
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Please reach out to me via Discord or email (or GitHub in some way) if you are thinking of making any contributions at this time. I started this project as a challenge for myself and have been enjoying having something I can work on in my own way.
|
||||||
|
|
||||||
|
Once this is out of beta I will be more open to contributions, but for now I am hoping to keep them to a minimum as the remaining challenges are ones I am looking forward to solving.
|
||||||
|
|
||||||
## [SCADA](https://en.wikipedia.org/wiki/SCADA)
|
## [SCADA](https://en.wikipedia.org/wiki/SCADA)
|
||||||
> Supervisory control and data acquisition (SCADA) is a control system architecture comprising computers, networked data communications and graphical user interfaces for high-level supervision of machines and processes. It also covers sensors and other devices, such as programmable logic controllers, which interface with process plant or machinery.
|
> Supervisory control and data acquisition (SCADA) is a control system architecture comprising computers, networked data communications and graphical user interfaces for high-level supervision of machines and processes. It also covers sensors and other devices, such as programmable logic controllers, which interface with process plant or machinery.
|
||||||
|
118
build/_offline.lua
Normal file
118
build/_offline.lua
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
---@diagnostic disable: undefined-global
|
||||||
|
-- luacheck: push ignore install_manifest ccmsi_offline app_files dep_files lgray green white
|
||||||
|
|
||||||
|
local b64_lookup = {
|
||||||
|
['A'] = 0, ['B'] = 1, ['C'] = 2, ['D'] = 3, ['E'] = 4, ['F'] = 5, ['G'] = 6, ['H'] = 7, ['I'] = 8, ['J'] = 9, ['K'] = 10, ['L'] = 11, ['M'] = 12, ['N'] = 13, ['O'] = 14, ['P'] = 15, ['Q'] = 16, ['R'] = 17, ['S'] = 18, ['T'] = 19, ['U'] = 20, ['V'] = 21, ['W'] = 22, ['X'] = 23, ['Y'] = 24, ['Z'] = 25,
|
||||||
|
['a'] = 26, ['b'] = 27, ['c'] = 28, ['d'] = 29, ['e'] = 30, ['f'] = 31, ['g'] = 32, ['h'] = 33, ['i'] = 34, ['j'] = 35, ['k'] = 36, ['l'] = 37, ['m'] = 38, ['n'] = 39, ['o'] = 40, ['p'] = 41, ['q'] = 42, ['r'] = 43, ['s'] = 44, ['t'] = 45, ['u'] = 46, ['v'] = 47, ['w'] = 48, ['x'] = 49, ['y'] = 50, ['z'] = 51,
|
||||||
|
['0'] = 52, ['1'] = 53, ['2'] = 54, ['3'] = 55, ['4'] = 56, ['5'] = 57, ['6'] = 58, ['7'] = 59, ['8'] = 60, ['9'] = 61, ['+'] = 62, ['/'] = 63
|
||||||
|
}
|
||||||
|
|
||||||
|
local BYTE = 0xFF
|
||||||
|
local CHAR = string.char
|
||||||
|
local BOR = bit.bor ---@type function
|
||||||
|
local BAND = bit.band ---@type function
|
||||||
|
local LSHFT = bit.blshift ---@type function
|
||||||
|
local RSHFT = bit.blogic_rshift ---@type function
|
||||||
|
|
||||||
|
-- decode a base64 string
|
||||||
|
---@param input string
|
||||||
|
local function b64_decode(input)
|
||||||
|
---@diagnostic disable-next-line: undefined-field
|
||||||
|
local t_start = os.epoch("local")
|
||||||
|
|
||||||
|
local decoded = {}
|
||||||
|
|
||||||
|
local c_idx, idx = 1, 1
|
||||||
|
|
||||||
|
for _ = 1, input:len() / 4 do
|
||||||
|
local block = input:sub(idx, idx + 4)
|
||||||
|
local word = 0x0
|
||||||
|
|
||||||
|
-- build the 24-bit sequence from the 4 characters
|
||||||
|
for i = 1, 4 do
|
||||||
|
local num = b64_lookup[block:sub(i, i)]
|
||||||
|
|
||||||
|
if num then
|
||||||
|
word = BOR(word, LSHFT(b64_lookup[block:sub(i, i)], (4 - i) * 6))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- decode the 24-bit sequence as 8 bytes
|
||||||
|
for i = 1, 3 do
|
||||||
|
local char = BAND(BYTE, RSHFT(word, (3 - i) * 8))
|
||||||
|
|
||||||
|
if char ~= 0 then
|
||||||
|
decoded[c_idx] = CHAR(char)
|
||||||
|
c_idx = c_idx + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
idx = idx + 4
|
||||||
|
end
|
||||||
|
|
||||||
|
---@diagnostic disable-next-line: undefined-field
|
||||||
|
local elapsed = (os.epoch("local") - t_start)
|
||||||
|
local decoded_str = table.concat(decoded)
|
||||||
|
|
||||||
|
return decoded_str, elapsed
|
||||||
|
end
|
||||||
|
|
||||||
|
-- write files recursively from base64 encodings in a table
|
||||||
|
---@param files table
|
||||||
|
---@param path string
|
||||||
|
local function write_files(files, path)
|
||||||
|
fs.makeDir(path)
|
||||||
|
|
||||||
|
for k, v in pairs(files) do
|
||||||
|
if type(v) == "table" then
|
||||||
|
if k == "system" then
|
||||||
|
-- write system files to root
|
||||||
|
write_files(v, "/")
|
||||||
|
else
|
||||||
|
-- descend into directories
|
||||||
|
write_files(v, path .. "/" .. k .. "/")
|
||||||
|
end
|
||||||
|
|
||||||
|
---@diagnostic disable-next-line: undefined-field
|
||||||
|
os.sleep(0.05)
|
||||||
|
else
|
||||||
|
local handle = fs.open(path .. k, "w")
|
||||||
|
local text, time = b64_decode(v)
|
||||||
|
|
||||||
|
print("decoded '" .. k .. "' in " .. time .. "ms")
|
||||||
|
|
||||||
|
handle.write(text)
|
||||||
|
handle.close()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- write installation manifiest and offline install manager
|
||||||
|
local function write_install()
|
||||||
|
local handle = fs.open("install_manifest.json", "w")
|
||||||
|
handle.write(b64_decode(install_manifest))
|
||||||
|
handle.close()
|
||||||
|
|
||||||
|
handle = fs.open("ccmsim.lua", "w")
|
||||||
|
handle.write(b64_decode(ccmsi_offline))
|
||||||
|
handle.close()
|
||||||
|
end
|
||||||
|
|
||||||
|
lgray()
|
||||||
|
|
||||||
|
-- write both app and dependency files
|
||||||
|
write_files(app_files, "/")
|
||||||
|
write_files(dep_files, "/")
|
||||||
|
|
||||||
|
-- write an install manifest and the offline installer
|
||||||
|
write_install()
|
||||||
|
|
||||||
|
green()
|
||||||
|
print("Done!")
|
||||||
|
white()
|
||||||
|
print("All files have been installed. The app can be started with 'startup' and configured with 'configure'.")
|
||||||
|
lgray()
|
||||||
|
print("Hint: You can use 'ccmsim' to manage your off-line installation.")
|
||||||
|
white()
|
||||||
|
|
||||||
|
--luacheck: pop
|
214
build/bundle.py
Normal file
214
build/bundle.py
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
import base64
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
path_prefix = "./_minified/"
|
||||||
|
|
||||||
|
# get git build info
|
||||||
|
build = subprocess.check_output(["git", "describe", "--tags"]).strip().decode('UTF-8')
|
||||||
|
|
||||||
|
# list files in a directory
|
||||||
|
def list_files(path):
|
||||||
|
list = []
|
||||||
|
|
||||||
|
for (root, dirs, files) in os.walk(path):
|
||||||
|
for f in files:
|
||||||
|
list.append((root[2:] + "/" + f).replace('\\','/'))
|
||||||
|
|
||||||
|
return list
|
||||||
|
|
||||||
|
# recursively encode files with base64
|
||||||
|
def encode_recursive(path):
|
||||||
|
list = {}
|
||||||
|
|
||||||
|
for item in os.listdir(path):
|
||||||
|
item_path = path + '/' + item
|
||||||
|
|
||||||
|
if os.path.isfile(item_path):
|
||||||
|
handle = open(item_path, 'r')
|
||||||
|
list[item] = base64.b64encode(bytes(handle.read(), 'UTF-8')).decode('ASCII')
|
||||||
|
handle.close()
|
||||||
|
else:
|
||||||
|
list[item] = encode_recursive(item_path)
|
||||||
|
|
||||||
|
return list
|
||||||
|
|
||||||
|
# encode listed files with base64
|
||||||
|
def encode_files(files):
|
||||||
|
list = {}
|
||||||
|
|
||||||
|
for item in files:
|
||||||
|
item_path = path_prefix + './' + item
|
||||||
|
|
||||||
|
handle = open(item_path, 'r')
|
||||||
|
list[item] = base64.b64encode(bytes(handle.read(), 'UTF-8')).decode('ASCII')
|
||||||
|
handle.close()
|
||||||
|
|
||||||
|
return list
|
||||||
|
|
||||||
|
# get the version of an application at the provided path
|
||||||
|
def get_version(path, is_lib = False):
|
||||||
|
ver = ""
|
||||||
|
string = ".version = \""
|
||||||
|
|
||||||
|
if not is_lib:
|
||||||
|
string = "_VERSION = \""
|
||||||
|
|
||||||
|
f = open(path, "r")
|
||||||
|
|
||||||
|
for line in f:
|
||||||
|
pos = line.find(string)
|
||||||
|
if pos >= 0:
|
||||||
|
ver = line[(pos + len(string)):(len(line) - 2)]
|
||||||
|
break
|
||||||
|
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
return ver
|
||||||
|
|
||||||
|
# file manifest (reflects imgen.py)
|
||||||
|
manifest = {
|
||||||
|
"common_versions" : {
|
||||||
|
"bootloader" : get_version("./startup.lua"),
|
||||||
|
"common" : get_version("./scada-common/util.lua", True),
|
||||||
|
"comms" : get_version("./scada-common/comms.lua", True),
|
||||||
|
"graphics" : get_version("./graphics/core.lua", True),
|
||||||
|
"lockbox" : get_version("./lockbox/init.lua", True),
|
||||||
|
},
|
||||||
|
"app_versions" : {
|
||||||
|
"reactor-plc" : get_version("./reactor-plc/startup.lua"),
|
||||||
|
"rtu" : get_version("./rtu/startup.lua"),
|
||||||
|
"supervisor" : get_version("./supervisor/startup.lua"),
|
||||||
|
"coordinator" : get_version("./coordinator/startup.lua"),
|
||||||
|
"pocket" : get_version("./pocket/startup.lua")
|
||||||
|
},
|
||||||
|
"files" : {
|
||||||
|
# common files
|
||||||
|
"system" : encode_files([ "initenv.lua", "startup.lua", "configure.lua", "LICENSE" ]),
|
||||||
|
"scada-common" : encode_recursive(path_prefix + "./scada-common"),
|
||||||
|
"graphics" : encode_recursive(path_prefix + "./graphics"),
|
||||||
|
"lockbox" : encode_recursive(path_prefix + "./lockbox"),
|
||||||
|
# platform files
|
||||||
|
"reactor-plc" : encode_recursive(path_prefix + "./reactor-plc"),
|
||||||
|
"rtu" : encode_recursive(path_prefix + "./rtu"),
|
||||||
|
"supervisor" : encode_recursive(path_prefix + "./supervisor"),
|
||||||
|
"coordinator" : encode_recursive(path_prefix + "./coordinator"),
|
||||||
|
"pocket" : encode_recursive(path_prefix + "./pocket"),
|
||||||
|
},
|
||||||
|
"install_files" : {
|
||||||
|
# common files
|
||||||
|
"system" : [ "initenv.lua", "startup.lua", "configure.lua", "LICENSE" ],
|
||||||
|
"scada-common" : list_files("./scada-common"),
|
||||||
|
"graphics" : list_files("./graphics"),
|
||||||
|
"lockbox" : list_files("./lockbox"),
|
||||||
|
# platform files
|
||||||
|
"reactor-plc" : list_files("./reactor-plc"),
|
||||||
|
"rtu" : list_files("./rtu"),
|
||||||
|
"supervisor" : list_files("./supervisor"),
|
||||||
|
"coordinator" : list_files("./coordinator"),
|
||||||
|
"pocket" : list_files("./pocket"),
|
||||||
|
},
|
||||||
|
"depends" : [ "system", "scada-common", "graphics", "lockbox" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
# write the application installation items as Lua tables
|
||||||
|
def write_items(body, items, indent):
|
||||||
|
indent_str = " " * indent
|
||||||
|
for key, value in items.items():
|
||||||
|
if isinstance(value, str):
|
||||||
|
body = body + f"{indent_str}['{key}'] = \"{value}\",\n"
|
||||||
|
else:
|
||||||
|
body = body + f"{indent_str}['{key}'] = {{\n"
|
||||||
|
body = write_items(body, value, indent + 4)
|
||||||
|
body = body + f"{indent_str}}},\n"
|
||||||
|
|
||||||
|
return body
|
||||||
|
|
||||||
|
# create output directory
|
||||||
|
if not os.path.exists("./BUNDLE"):
|
||||||
|
os.makedirs("./BUNDLE")
|
||||||
|
|
||||||
|
# get offline installer
|
||||||
|
ccmsim_file = open("./build/ccmsim.lua", "r")
|
||||||
|
ccmsim_script = ccmsim_file.read()
|
||||||
|
ccmsim_file.close()
|
||||||
|
|
||||||
|
# create dependency bundled file
|
||||||
|
dep_file = "common_" + build + ".lua"
|
||||||
|
f_d = open("./BUNDLE/" + dep_file, "w")
|
||||||
|
|
||||||
|
body_b = "local dep_files = {\n"
|
||||||
|
|
||||||
|
for depend in manifest["depends"]:
|
||||||
|
body_b = body_b + write_items("", { f"{depend}": manifest["files"][depend] }, 4)
|
||||||
|
body_b = body_b + "}\n"
|
||||||
|
|
||||||
|
body_b = body_b + f"""
|
||||||
|
if select("#", ...) == 0 then
|
||||||
|
term.setTextColor(colors.red)
|
||||||
|
print("You must run the other file you should have uploaded (it has the app in its name).")
|
||||||
|
term.setTextColor(colors.white)
|
||||||
|
end
|
||||||
|
|
||||||
|
return dep_files
|
||||||
|
"""
|
||||||
|
|
||||||
|
f_d.write(body_b)
|
||||||
|
f_d.close()
|
||||||
|
|
||||||
|
# application bundled files
|
||||||
|
for app in [ "reactor-plc", "rtu", "supervisor", "coordinator", "pocket" ]:
|
||||||
|
app_file = app + "_" + build + ".lua"
|
||||||
|
|
||||||
|
f_script = open("./build/_offline.lua", "r")
|
||||||
|
script = f_script.read()
|
||||||
|
f_script.close()
|
||||||
|
|
||||||
|
f_a = open("./BUNDLE/" + app_file, "w")
|
||||||
|
|
||||||
|
body_a = "local app_files = {\n"
|
||||||
|
|
||||||
|
body_a = body_a + write_items("", { f"{app}": manifest["files"][app] }, 4) + "}\n"
|
||||||
|
|
||||||
|
versions = manifest["common_versions"].copy()
|
||||||
|
versions[app] = manifest["app_versions"][app]
|
||||||
|
|
||||||
|
depends = manifest["depends"].copy()
|
||||||
|
depends.append(app)
|
||||||
|
|
||||||
|
install_manifest = json.dumps({ "versions" : versions, "files" : manifest["install_files"], "depends" : depends })
|
||||||
|
|
||||||
|
body_a = body_a + f"""
|
||||||
|
-- install manifest JSON and offline installer
|
||||||
|
local install_manifest = "{base64.b64encode(bytes(install_manifest, 'UTF-8')).decode('ASCII')}"
|
||||||
|
local ccmsi_offline = "{base64.b64encode(bytes(ccmsim_script, 'UTF-8')).decode('ASCII')}"
|
||||||
|
|
||||||
|
local function red() term.setTextColor(colors.red) end
|
||||||
|
local function green() term.setTextColor(colors.green) end
|
||||||
|
local function white() term.setTextColor(colors.white) end
|
||||||
|
local function lgray() term.setTextColor(colors.lightGray) end
|
||||||
|
|
||||||
|
if not fs.exists("{dep_file}") then
|
||||||
|
red()
|
||||||
|
print("Missing '{dep_file}'! Please upload it, then run this file again.")
|
||||||
|
white()
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- rename the dependency file
|
||||||
|
fs.move("{dep_file}", "install_depends.lua")
|
||||||
|
|
||||||
|
-- load the other file
|
||||||
|
local dep_files = require("install_depends")
|
||||||
|
|
||||||
|
-- delete the uploaded files to free up space to actually install
|
||||||
|
fs.delete("{app_file}")
|
||||||
|
fs.delete("install_depends.lua")
|
||||||
|
|
||||||
|
-- get started installing
|
||||||
|
{script}"""
|
||||||
|
|
||||||
|
f_a.write(body_a)
|
||||||
|
f_a.close()
|
237
build/ccmsim.lua
Normal file
237
build/ccmsim.lua
Normal file
@ -0,0 +1,237 @@
|
|||||||
|
local function println(message) print(tostring(message)) end
|
||||||
|
local function print(message) term.write(tostring(message)) end
|
||||||
|
|
||||||
|
local opts = { ... }
|
||||||
|
local mode, app
|
||||||
|
|
||||||
|
local function red() term.setTextColor(colors.red) end
|
||||||
|
local function orange() term.setTextColor(colors.orange) end
|
||||||
|
local function yellow() term.setTextColor(colors.yellow) end
|
||||||
|
local function green() term.setTextColor(colors.green) end
|
||||||
|
local function blue() term.setTextColor(colors.blue) end
|
||||||
|
local function white() term.setTextColor(colors.white) end
|
||||||
|
local function lgray() term.setTextColor(colors.lightGray) end
|
||||||
|
|
||||||
|
-- get command line option in list
|
||||||
|
local function get_opt(opt, options)
|
||||||
|
for _, v in pairs(options) do if opt == v then return v end end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- wait for any key to be pressed
|
||||||
|
---@diagnostic disable-next-line: undefined-field
|
||||||
|
local function any_key() os.pullEvent("key_up") end
|
||||||
|
|
||||||
|
-- ask the user yes or no
|
||||||
|
local function ask_y_n(question, default)
|
||||||
|
print(question)
|
||||||
|
if default == true then print(" (Y/n)? ") else print(" (y/N)? ") end
|
||||||
|
local response = read();any_key()
|
||||||
|
if response == "" then return default
|
||||||
|
elseif response == "Y" or response == "y" then return true
|
||||||
|
elseif response == "N" or response == "n" then return false
|
||||||
|
else return nil end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- read the local manifest file
|
||||||
|
local function read_local_manifest()
|
||||||
|
local local_ok = false
|
||||||
|
local local_manifest = {}
|
||||||
|
local imfile = fs.open("install_manifest.json", "r")
|
||||||
|
if imfile ~= nil then
|
||||||
|
local_ok, local_manifest = pcall(function () return textutils.unserializeJSON(imfile.readAll()) end)
|
||||||
|
imfile.close()
|
||||||
|
end
|
||||||
|
return local_ok, local_manifest
|
||||||
|
end
|
||||||
|
|
||||||
|
-- recursively build a tree out of the file manifest
|
||||||
|
local function gen_tree(manifest, log)
|
||||||
|
local function _tree_add(tree, split)
|
||||||
|
if #split > 1 then
|
||||||
|
local name = table.remove(split, 1)
|
||||||
|
if tree[name] == nil then tree[name] = {} end
|
||||||
|
table.insert(tree[name], _tree_add(tree[name], split))
|
||||||
|
else return split[1] end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local list, tree = { log }, {}
|
||||||
|
|
||||||
|
-- make a list of each and every file
|
||||||
|
for _, files in pairs(manifest.files) do for i = 1, #files do table.insert(list, files[i]) end end
|
||||||
|
|
||||||
|
for i = 1, #list do
|
||||||
|
local split = {}
|
||||||
|
---@diagnostic disable-next-line: discard-returns
|
||||||
|
string.gsub(list[i], "([^/]+)", function(c) split[#split + 1] = c end)
|
||||||
|
if #split == 1 then table.insert(tree, list[i])
|
||||||
|
else table.insert(tree, _tree_add(tree, split)) end
|
||||||
|
end
|
||||||
|
|
||||||
|
return tree
|
||||||
|
end
|
||||||
|
|
||||||
|
local function _in_array(val, array)
|
||||||
|
for _, v in pairs(array) do if v == val then return true end end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local function _clean_dir(dir, tree)
|
||||||
|
if tree == nil then tree = {} end
|
||||||
|
local ls = fs.list(dir)
|
||||||
|
for _, val in pairs(ls) do
|
||||||
|
local path = dir.."/"..val
|
||||||
|
if fs.isDir(path) then
|
||||||
|
_clean_dir(path, tree[val])
|
||||||
|
if #fs.list(path) == 0 then fs.delete(path);println("deleted "..path) end
|
||||||
|
elseif (not _in_array(val, tree)) and (val ~= "config.lua" ) then
|
||||||
|
fs.delete(path)
|
||||||
|
println("deleted "..path)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- go through app/common directories to delete unused files
|
||||||
|
local function clean(manifest)
|
||||||
|
local log = nil
|
||||||
|
if fs.exists(app..".settings") and settings.load(app..".settings") then
|
||||||
|
log = settings.get("LogPath")
|
||||||
|
if log:sub(1, 1) == "/" then log = log:sub(2) end
|
||||||
|
end
|
||||||
|
|
||||||
|
local tree = gen_tree(manifest, log)
|
||||||
|
|
||||||
|
table.insert(tree, "install_manifest.json")
|
||||||
|
table.insert(tree, "ccmsim.lua")
|
||||||
|
|
||||||
|
local ls = fs.list("/")
|
||||||
|
for _, val in pairs(ls) do
|
||||||
|
if fs.isDriveRoot(val) then
|
||||||
|
yellow();println("skipped mount '"..val.."'")
|
||||||
|
elseif fs.isDir(val) then
|
||||||
|
if tree[val] ~= nil then lgray();_clean_dir("/"..val, tree[val])
|
||||||
|
else white(); if ask_y_n("delete the unused directory '"..val.."'") then lgray();_clean_dir("/"..val) end end
|
||||||
|
if #fs.list(val) == 0 then fs.delete(val);lgray();println("deleted empty directory '"..val.."'") end
|
||||||
|
elseif not _in_array(val, tree) and (string.find(val, ".settings") == nil) then
|
||||||
|
white();if ask_y_n("delete the unused file '"..val.."'") then fs.delete(val);lgray();println("deleted "..val) end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
white()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- get and validate command line options
|
||||||
|
|
||||||
|
println("-- CC Mekanism SCADA Install Manager (Off-Line) --")
|
||||||
|
|
||||||
|
if #opts == 0 or opts[1] == "help" then
|
||||||
|
println("usage: ccmsim <mode>")
|
||||||
|
println("<mode>")
|
||||||
|
lgray()
|
||||||
|
println(" check - check your installed versions")
|
||||||
|
println(" update-rm - delete everything except the config,")
|
||||||
|
println(" so that you can upload files for a")
|
||||||
|
println(" new two-file off-line update")
|
||||||
|
println(" uninstall - delete all app files and config")
|
||||||
|
return
|
||||||
|
else
|
||||||
|
mode = get_opt(opts[1], { "check", "update-rm", "uninstall" })
|
||||||
|
if mode == nil then
|
||||||
|
red();println("Unrecognized mode.");white()
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- run selected mode
|
||||||
|
if mode == "check" then
|
||||||
|
local local_ok, manifest = read_local_manifest()
|
||||||
|
if not local_ok then
|
||||||
|
yellow();println("failed to load local installation information");white()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- list all versions
|
||||||
|
for key, value in pairs(manifest.versions) do
|
||||||
|
term.setTextColor(colors.purple)
|
||||||
|
print(string.format("%-14s", "["..key.."]"))
|
||||||
|
blue();println(value);white()
|
||||||
|
end
|
||||||
|
elseif mode == "update-rm" or mode == "uninstall" then
|
||||||
|
local ok, manifest = read_local_manifest()
|
||||||
|
if not ok then
|
||||||
|
red();println("Error parsing local installation manifest.");white()
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
app = manifest.depends[#manifest.depends]
|
||||||
|
|
||||||
|
if mode == "uninstall" then
|
||||||
|
orange();println("Uninstalling all app files...")
|
||||||
|
else
|
||||||
|
orange();println("Deleting all app files except for configuration...")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ask for confirmation
|
||||||
|
if not ask_y_n("Continue", false) then return end
|
||||||
|
|
||||||
|
-- delete unused files first
|
||||||
|
clean(manifest)
|
||||||
|
|
||||||
|
local file_list = manifest.files
|
||||||
|
local dependencies = manifest.depends
|
||||||
|
|
||||||
|
-- delete all installed files
|
||||||
|
lgray()
|
||||||
|
for _, dependency in pairs(dependencies) do
|
||||||
|
local files = file_list[dependency]
|
||||||
|
for _, file in pairs(files) do
|
||||||
|
if fs.exists(file) then fs.delete(file);println("deleted "..file) end
|
||||||
|
end
|
||||||
|
|
||||||
|
local folder = files[1]
|
||||||
|
while true do
|
||||||
|
local dir = fs.getDir(folder)
|
||||||
|
if dir == "" or dir == ".." then break else folder = dir end
|
||||||
|
end
|
||||||
|
|
||||||
|
if fs.isDir(folder) then
|
||||||
|
fs.delete(folder)
|
||||||
|
println("deleted directory "..folder)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- delete log file
|
||||||
|
local log_deleted = false
|
||||||
|
local settings_file = app..".settings"
|
||||||
|
|
||||||
|
if fs.exists(settings_file) and settings.load(settings_file) then
|
||||||
|
local log = settings.get("LogPath")
|
||||||
|
if log ~= nil then
|
||||||
|
log_deleted = true
|
||||||
|
if fs.exists(log) then
|
||||||
|
fs.delete(log)
|
||||||
|
println("deleted log file "..log)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not log_deleted then
|
||||||
|
red();println("Failed to delete log file (it may not exist).");lgray()
|
||||||
|
end
|
||||||
|
|
||||||
|
if mode == "uninstall" then
|
||||||
|
if fs.exists(settings_file) then
|
||||||
|
fs.delete(settings_file);println("deleted "..settings_file)
|
||||||
|
end
|
||||||
|
|
||||||
|
fs.delete("install_manifest.json")
|
||||||
|
println("deleted install_manifest.json")
|
||||||
|
|
||||||
|
fs.delete("ccmsim.lua")
|
||||||
|
println("deleted ccmsim.lua")
|
||||||
|
end
|
||||||
|
|
||||||
|
green();println("Done!")
|
||||||
|
end
|
||||||
|
|
||||||
|
white()
|
14
build/package.sh
Executable file
14
build/package.sh
Executable file
@ -0,0 +1,14 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Create zips to attach to GitHub releases.
|
||||||
|
# These can be extracted onto a computer and will include all files CCMSI would otherwise install.
|
||||||
|
|
||||||
|
tag=$(git describe --tags)
|
||||||
|
apps=(coordinator pocket reactor-plc rtu supervisor)
|
||||||
|
|
||||||
|
for app in "${apps[@]}" do
|
||||||
|
mkdir ${tag}_${app}
|
||||||
|
cp -R $app scada-common graphics lockbox configure.lua initenv.lua startup.lua LICENSE ${tag}_${app}
|
||||||
|
zip -r ${tag}_${app}.zip ${tag}_${app}
|
||||||
|
rm -R ${tag}_${app}
|
||||||
|
done
|
80
build/safemin.py
Normal file
80
build/safemin.py
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
|
# minify files in a directory
|
||||||
|
def min_files(path):
|
||||||
|
start_sum, end_sum = 0, 0
|
||||||
|
|
||||||
|
for (root, _, files) in os.walk(path):
|
||||||
|
os.makedirs('_minified/' + root, exist_ok=True)
|
||||||
|
|
||||||
|
for f in files:
|
||||||
|
start, end = minify(root + "/" + f)
|
||||||
|
|
||||||
|
start_sum = start_sum + start
|
||||||
|
end_sum = end_sum + end
|
||||||
|
|
||||||
|
delta = start_sum - end_sum
|
||||||
|
|
||||||
|
print(f"> done with '{path}': shrunk from {start_sum} bytes to {end_sum} bytes (saved {delta} bytes, or {(100*delta/start_sum):.2f}%)")
|
||||||
|
|
||||||
|
return list
|
||||||
|
|
||||||
|
# minify a file
|
||||||
|
def minify(path: str):
|
||||||
|
size_start = os.stat(path).st_size
|
||||||
|
|
||||||
|
f = open(path, "r")
|
||||||
|
contents = f.read()
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
if re.search(r'--+\[+', contents) != None:
|
||||||
|
# absolutely not dealing with lua multiline comments
|
||||||
|
# - there are more important things to do
|
||||||
|
# - this minification is intended to be 100% safe, so working with multiline comments is asking for trouble
|
||||||
|
# - the project doesn't use them as of writing this (except in test/), and it might as well stay that way
|
||||||
|
raise Exception(f"no multiline comments allowed! (offending file: {path})")
|
||||||
|
|
||||||
|
if re.search(r'\\$', contents, flags=re.MULTILINE) != None:
|
||||||
|
# '\' allows for multiline strings, which would require reverting to processing syntax line by line to support them
|
||||||
|
raise Exception(f"no escaping newlines! (offending file: {path})")
|
||||||
|
|
||||||
|
# drop the comments, unless the line has quotes, because quotes are scary
|
||||||
|
# (quotes are scary since we could actually be inside a string: "-- ..." shouldn't get deleted)
|
||||||
|
# -> whitespace before '--' and anything after that, which includes '---' comments
|
||||||
|
minified = re.sub(r'\s*--+(?!.*[\'"]).*', '', contents)
|
||||||
|
|
||||||
|
# drop leading whitespace on each line
|
||||||
|
minified = re.sub(r'^ +', '', minified, flags=re.MULTILINE)
|
||||||
|
|
||||||
|
# drop blank lines
|
||||||
|
while minified != re.sub(r'\n\n', '\n', minified):
|
||||||
|
minified = re.sub(r'\n\n', '\n', minified)
|
||||||
|
|
||||||
|
# write the minified file
|
||||||
|
f_min = open(f"_minified/{path}", "w")
|
||||||
|
f_min.write(minified)
|
||||||
|
f_min.close()
|
||||||
|
|
||||||
|
size_end = os.stat(f"_minified/{path}").st_size
|
||||||
|
|
||||||
|
print(f">> shrunk '{path}' from {size_start} bytes to {size_end} bytes (saved {size_start-size_end} bytes)")
|
||||||
|
|
||||||
|
return size_start, size_end
|
||||||
|
|
||||||
|
# minify applications and libraries
|
||||||
|
dirs = [ 'scada-common', 'graphics', 'lockbox', 'reactor-plc', 'rtu', 'supervisor', 'coordinator', 'pocket' ]
|
||||||
|
for _, d in enumerate(dirs):
|
||||||
|
min_files(d)
|
||||||
|
|
||||||
|
# minify root files
|
||||||
|
minify("startup.lua")
|
||||||
|
minify("initenv.lua")
|
||||||
|
minify("configure.lua")
|
||||||
|
|
||||||
|
# copy in license for build usage
|
||||||
|
lic1 = open("LICENSE", "r")
|
||||||
|
lic2 = open("_minified/LICENSE", "w")
|
||||||
|
lic2.write(lic1.read())
|
||||||
|
lic1.close()
|
||||||
|
lic2.close()
|
229
ccmsi.lua
229
ccmsi.lua
@ -15,15 +15,63 @@ 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 function println(message) print(tostring(message)) end
|
local CCMSI_VERSION = "v1.17"
|
||||||
local function print(message) term.write(tostring(message)) end
|
|
||||||
|
|
||||||
local CCMSI_VERSION = "v1.14"
|
|
||||||
|
|
||||||
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/"
|
||||||
local repo_path = "http://raw.githubusercontent.com/MikaylaFischler/cc-mek-scada/"
|
local repo_path = "http://raw.githubusercontent.com/MikaylaFischler/cc-mek-scada/"
|
||||||
|
|
||||||
|
---@diagnostic disable-next-line: undefined-global
|
||||||
|
local _is_pkt_env = pocket -- luacheck: ignore pocket
|
||||||
|
|
||||||
|
local function println(msg) print(tostring(msg)) end
|
||||||
|
|
||||||
|
-- stripped down & modified copy of log.dmesg
|
||||||
|
local function print(msg)
|
||||||
|
msg = tostring(msg)
|
||||||
|
|
||||||
|
local cur_x, cur_y = term.getCursorPos()
|
||||||
|
local out_w, out_h = term.getSize()
|
||||||
|
|
||||||
|
-- jump to next line if needed
|
||||||
|
if cur_x == out_w then
|
||||||
|
cur_x = 1
|
||||||
|
if cur_y == out_h then
|
||||||
|
term.scroll(1)
|
||||||
|
term.setCursorPos(1, cur_y)
|
||||||
|
else
|
||||||
|
term.setCursorPos(1, cur_y + 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- wrap
|
||||||
|
local lines, remaining, s_start, s_end, ln = {}, true, 1, out_w + 1 - cur_x, 1
|
||||||
|
while remaining do
|
||||||
|
local line = string.sub(msg, s_start, s_end)
|
||||||
|
|
||||||
|
if line == "" then
|
||||||
|
remaining = false
|
||||||
|
else
|
||||||
|
lines[ln] = line
|
||||||
|
s_start = s_end + 1
|
||||||
|
s_end = s_end + out_w
|
||||||
|
ln = ln + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- print
|
||||||
|
for i = 1, #lines do
|
||||||
|
cur_x, cur_y = term.getCursorPos()
|
||||||
|
if i > 1 and cur_x > 1 then
|
||||||
|
if cur_y == out_h then
|
||||||
|
term.scroll(1)
|
||||||
|
term.setCursorPos(1, cur_y)
|
||||||
|
else term.setCursorPos(1, cur_y + 1) end
|
||||||
|
end
|
||||||
|
term.write(lines[i])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
local opts = { ... }
|
local opts = { ... }
|
||||||
local mode, app, target
|
local mode, app, target
|
||||||
local install_manifest = manifest_path.."main/install_manifest.json"
|
local install_manifest = manifest_path.."main/install_manifest.json"
|
||||||
@ -120,8 +168,29 @@ local function write_install_manifest(manifest, dependencies)
|
|||||||
imfile.close()
|
imfile.close()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- try at most 3 times to download a file from the repository and write into w_path base directory
|
||||||
|
local function http_get_file(file, w_path)
|
||||||
|
local dl, err
|
||||||
|
for i = 1, 3 do
|
||||||
|
dl, err = http.get(repo_path..file)
|
||||||
|
if dl then
|
||||||
|
if i > 1 then green();println("success!");lgray() end
|
||||||
|
local f = fs.open(w_path..file, "w")
|
||||||
|
f.write(dl.readAll())
|
||||||
|
f.close()
|
||||||
|
break
|
||||||
|
else
|
||||||
|
red();println("HTTP Error: "..err)
|
||||||
|
if i < 3 then lgray();print("> retrying...") end
|
||||||
|
---@diagnostic disable-next-line: undefined-field
|
||||||
|
os.sleep(i/3.0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return dl ~= nil
|
||||||
|
end
|
||||||
|
|
||||||
-- recursively build a tree out of the file manifest
|
-- recursively build a tree out of the file manifest
|
||||||
local function gen_tree(manifest)
|
local function gen_tree(manifest, log)
|
||||||
local function _tree_add(tree, split)
|
local function _tree_add(tree, split)
|
||||||
if #split > 1 then
|
if #split > 1 then
|
||||||
local name = table.remove(split, 1)
|
local name = table.remove(split, 1)
|
||||||
@ -131,13 +200,14 @@ local function gen_tree(manifest)
|
|||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
local list, tree = {}, {}
|
local list, tree = { log }, {}
|
||||||
|
|
||||||
-- make a list of each and every file
|
-- make a list of each and every file
|
||||||
for _, files in pairs(manifest.files) do for i = 1, #files do table.insert(list, files[i]) end end
|
for _, files in pairs(manifest.files) do for i = 1, #files do table.insert(list, files[i]) end end
|
||||||
|
|
||||||
for i = 1, #list do
|
for i = 1, #list do
|
||||||
local split = {}
|
local split = {}
|
||||||
|
---@diagnostic disable-next-line: discard-returns
|
||||||
string.gsub(list[i], "([^/]+)", function(c) split[#split + 1] = c end)
|
string.gsub(list[i], "([^/]+)", function(c) split[#split + 1] = c end)
|
||||||
if #split == 1 then table.insert(tree, list[i])
|
if #split == 1 then table.insert(tree, list[i])
|
||||||
else table.insert(tree, _tree_add(tree, split)) end
|
else table.insert(tree, _tree_add(tree, split)) end
|
||||||
@ -159,7 +229,7 @@ local function _clean_dir(dir, tree)
|
|||||||
if fs.isDir(path) then
|
if fs.isDir(path) then
|
||||||
_clean_dir(path, tree[val])
|
_clean_dir(path, tree[val])
|
||||||
if #fs.list(path) == 0 then fs.delete(path);println("deleted "..path) end
|
if #fs.list(path) == 0 then fs.delete(path);println("deleted "..path) end
|
||||||
elseif (not _in_array(val, tree)) and (val ~= "config.lua" ) then
|
elseif (not _in_array(val, tree)) and (val ~= "config.lua" ) then ---@todo remove config.lua on full release
|
||||||
fs.delete(path)
|
fs.delete(path)
|
||||||
println("deleted "..path)
|
println("deleted "..path)
|
||||||
end
|
end
|
||||||
@ -168,11 +238,16 @@ end
|
|||||||
|
|
||||||
-- go through app/common directories to delete unused files
|
-- go through app/common directories to delete unused files
|
||||||
local function clean(manifest)
|
local function clean(manifest)
|
||||||
local tree = gen_tree(manifest)
|
local log = nil
|
||||||
|
if fs.exists(app..".settings") and settings.load(app..".settings") then
|
||||||
|
log = settings.get("LogPath")
|
||||||
|
if log:sub(1, 1) == "/" then log = log:sub(2) end
|
||||||
|
end
|
||||||
|
|
||||||
|
local tree = gen_tree(manifest, log)
|
||||||
|
|
||||||
table.insert(tree, "install_manifest.json")
|
table.insert(tree, "install_manifest.json")
|
||||||
table.insert(tree, "ccmsi.lua")
|
table.insert(tree, "ccmsi.lua")
|
||||||
table.insert(tree, "log.txt") ---@fixme fix after migration to settings files?
|
|
||||||
|
|
||||||
local ls = fs.list("/")
|
local ls = fs.list("/")
|
||||||
for _, val in pairs(ls) do
|
for _, val in pairs(ls) do
|
||||||
@ -192,10 +267,27 @@ end
|
|||||||
|
|
||||||
-- get and validate command line options
|
-- get and validate command line options
|
||||||
|
|
||||||
println("-- CC Mekanism SCADA Installer "..CCMSI_VERSION.." --")
|
if _is_pkt_env then println("- SCADA Installer "..CCMSI_VERSION.." -")
|
||||||
|
else println("-- CC Mekanism SCADA Installer "..CCMSI_VERSION.." --") end
|
||||||
|
|
||||||
if #opts == 0 or opts[1] == "help" then
|
if #opts == 0 or opts[1] == "help" then
|
||||||
println("usage: ccmsi <mode> <app> <branch>")
|
println("usage: ccmsi <mode> <app> <branch>")
|
||||||
|
if _is_pkt_env then
|
||||||
|
yellow();println("<mode>");lgray()
|
||||||
|
println(" check - check latest")
|
||||||
|
println(" install - fresh install")
|
||||||
|
println(" update - update app")
|
||||||
|
println(" uninstall - remove app")
|
||||||
|
yellow();println("<app>");lgray()
|
||||||
|
println(" reactor-plc")
|
||||||
|
println(" rtu")
|
||||||
|
println(" supervisor")
|
||||||
|
println(" coordinator")
|
||||||
|
println(" pocket")
|
||||||
|
println(" installer (update only)")
|
||||||
|
yellow();println("<branch>");lgray();
|
||||||
|
println(" main (default) | devel");white()
|
||||||
|
else
|
||||||
println("<mode>")
|
println("<mode>")
|
||||||
lgray()
|
lgray()
|
||||||
println(" check - check latest versions available")
|
println(" check - check latest versions available")
|
||||||
@ -214,6 +306,7 @@ if #opts == 0 or opts[1] == "help" then
|
|||||||
println(" installer - ccmsi installer (update only)")
|
println(" installer - ccmsi installer (update only)")
|
||||||
white();println("<branch>")
|
white();println("<branch>")
|
||||||
lgray();println(" main (default) | devel");white()
|
lgray();println(" main (default) | devel");white()
|
||||||
|
end
|
||||||
return
|
return
|
||||||
else
|
else
|
||||||
mode = get_opt(opts[1], { "check", "install", "update", "uninstall" })
|
mode = get_opt(opts[1], { "check", "install", "update", "uninstall" })
|
||||||
@ -244,7 +337,6 @@ else
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- run selected mode
|
-- run selected mode
|
||||||
|
|
||||||
if mode == "check" then
|
if mode == "check" then
|
||||||
local ok, manifest = get_remote_manifest()
|
local ok, manifest = get_remote_manifest()
|
||||||
if not ok then return end
|
if not ok then return end
|
||||||
@ -260,20 +352,22 @@ if mode == "check" then
|
|||||||
-- list all versions
|
-- list all versions
|
||||||
for key, value in pairs(manifest.versions) do
|
for key, value in pairs(manifest.versions) do
|
||||||
term.setTextColor(colors.purple)
|
term.setTextColor(colors.purple)
|
||||||
print(string.format("%-14s", "["..key.."]"))
|
local tag = string.format("%-14s", "["..key.."]")
|
||||||
|
if not _is_pkt_env then print(tag) end
|
||||||
if key == "installer" or (local_ok and (local_manifest.versions[key] ~= nil)) then
|
if key == "installer" or (local_ok and (local_manifest.versions[key] ~= nil)) then
|
||||||
|
if _is_pkt_env then println(tag) end
|
||||||
blue();print(local_manifest.versions[key])
|
blue();print(local_manifest.versions[key])
|
||||||
if value ~= local_manifest.versions[key] then
|
if value ~= local_manifest.versions[key] then
|
||||||
white();print(" (")
|
white();print(" (")
|
||||||
cyan();print(value);white();println(" available)")
|
cyan();print(value);white();println(" available)")
|
||||||
else green();println(" (up to date)") end
|
else green();println(" (up to date)") end
|
||||||
else
|
elseif not _is_pkt_env then
|
||||||
lgray();print("not installed");white();print(" (latest ")
|
lgray();print("not installed");white();print(" (latest ")
|
||||||
cyan();print(value);white();println(")")
|
cyan();print(value);white();println(")")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if manifest.versions.installer ~= local_manifest.versions.installer then
|
if manifest.versions.installer ~= local_manifest.versions.installer and not _is_pkt_env 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
|
||||||
@ -318,7 +412,7 @@ elseif mode == "install" or mode == "update" then
|
|||||||
local dl, err = http.get(repo_path.."ccmsi.lua")
|
local dl, err = http.get(repo_path.."ccmsi.lua")
|
||||||
|
|
||||||
if dl == nil then
|
if dl == nil then
|
||||||
red();println("HTTP Error "..err)
|
red();println("HTTP Error: "..err)
|
||||||
println("Installer download failed.");white()
|
println("Installer download failed.");white()
|
||||||
else
|
else
|
||||||
local handle = fs.open(debug.getinfo(1, "S").source:sub(2), "w") -- this file, regardless of name or location
|
local handle = fs.open(debug.getinfo(1, "S").source:sub(2), "w") -- this file, regardless of name or location
|
||||||
@ -355,9 +449,6 @@ 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)
|
||||||
|
|
||||||
-- ask for confirmation
|
|
||||||
if not ask_y_n("Continue", false) then return end
|
|
||||||
|
|
||||||
--------------------------
|
--------------------------
|
||||||
-- START INSTALL/UPDATE --
|
-- START INSTALL/UPDATE --
|
||||||
--------------------------
|
--------------------------
|
||||||
@ -372,11 +463,32 @@ elseif mode == "install" or mode == "update" then
|
|||||||
|
|
||||||
table.insert(dependencies, app)
|
table.insert(dependencies, app)
|
||||||
|
|
||||||
|
-- helper function to check if a dependency is unchanged
|
||||||
|
local function unchanged(dependency)
|
||||||
|
if dependency == "system" then return not ver.boot.changed
|
||||||
|
elseif dependency == "graphics" then return not ver.graphics.changed
|
||||||
|
elseif dependency == "lockbox" then return not ver.lockbox.changed
|
||||||
|
elseif dependency == "common" then return not (ver.common.changed or ver.comms.changed)
|
||||||
|
elseif dependency == app then return not ver.app.changed
|
||||||
|
else return true end
|
||||||
|
end
|
||||||
|
|
||||||
|
local any_change = false
|
||||||
|
|
||||||
for _, dependency in pairs(dependencies) do
|
for _, dependency in pairs(dependencies) do
|
||||||
local size = size_list[dependency]
|
local size = size_list[dependency]
|
||||||
space_required = space_required + size
|
space_required = space_required + size
|
||||||
|
any_change = any_change or not unchanged(dependency)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if mode == "update" and not any_change then
|
||||||
|
yellow();println("Nothing to do, everything is already up-to-date!");white()
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ask for confirmation
|
||||||
|
if not ask_y_n("Continue", false) then return end
|
||||||
|
|
||||||
-- check space constraints
|
-- check space constraints
|
||||||
if space_available < space_required then
|
if space_available < space_required then
|
||||||
single_file_mode = true
|
single_file_mode = true
|
||||||
@ -392,16 +504,6 @@ elseif mode == "install" or mode == "update" then
|
|||||||
|
|
||||||
local success = true
|
local success = true
|
||||||
|
|
||||||
-- helper function to check if a dependency is unchanged
|
|
||||||
local function unchanged(dependency)
|
|
||||||
if dependency == "system" then return not ver.boot.changed
|
|
||||||
elseif dependency == "graphics" then return not ver.graphics.changed
|
|
||||||
elseif dependency == "lockbox" then return not ver.lockbox.changed
|
|
||||||
elseif dependency == "common" then return not (ver.common.changed or ver.comms.changed)
|
|
||||||
elseif dependency == app then return not ver.app.changed
|
|
||||||
else return true end
|
|
||||||
end
|
|
||||||
|
|
||||||
if not single_file_mode then
|
if not single_file_mode 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
|
||||||
|
|
||||||
@ -416,19 +518,14 @@ elseif mode == "install" or mode == "update" then
|
|||||||
local files = file_list[dependency]
|
local files = file_list[dependency]
|
||||||
for _, file in pairs(files) do
|
for _, file in pairs(files) do
|
||||||
println("GET "..file)
|
println("GET "..file)
|
||||||
local dl, err = http.get(repo_path..file)
|
if not http_get_file(file, install_dir.."/") then
|
||||||
|
red();println("failed to download "..file)
|
||||||
if dl == nil then
|
|
||||||
red();println("HTTP Error "..err)
|
|
||||||
success = false
|
success = false
|
||||||
break
|
break
|
||||||
else
|
|
||||||
local handle = fs.open(install_dir.."/"..file, "w")
|
|
||||||
handle.write(dl.readAll())
|
|
||||||
handle.close()
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
if not success then break end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- copy in downloaded files (installation)
|
-- copy in downloaded files (installation)
|
||||||
@ -478,19 +575,14 @@ elseif mode == "install" or mode == "update" then
|
|||||||
local files = file_list[dependency]
|
local files = file_list[dependency]
|
||||||
for _, file in pairs(files) do
|
for _, file in pairs(files) do
|
||||||
println("GET "..file)
|
println("GET "..file)
|
||||||
local dl, err = http.get(repo_path..file)
|
if not http_get_file(file, "/") then
|
||||||
|
red();println("failed to download "..file)
|
||||||
if dl == nil then
|
|
||||||
red();println("HTTP Error "..err)
|
|
||||||
success = false
|
success = false
|
||||||
break
|
break
|
||||||
else
|
|
||||||
local handle = fs.open("/"..file, "w")
|
|
||||||
handle.write(dl.readAll())
|
|
||||||
handle.close()
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
if not success then break end
|
||||||
end
|
end
|
||||||
|
|
||||||
if success then
|
if success then
|
||||||
@ -534,36 +626,8 @@ elseif mode == "uninstall" then
|
|||||||
|
|
||||||
table.insert(dependencies, app)
|
table.insert(dependencies, app)
|
||||||
|
|
||||||
-- delete log file
|
|
||||||
local log_deleted = false
|
|
||||||
local settings_file = app..".settings"
|
|
||||||
local legacy_config_file = app.."/config.lua"
|
|
||||||
|
|
||||||
lgray()
|
|
||||||
if fs.exists(legacy_config_file) then
|
|
||||||
log_deleted = pcall(function ()
|
|
||||||
local config = require(app..".config")
|
|
||||||
if fs.exists(config.LOG_PATH) then
|
|
||||||
fs.delete(config.LOG_PATH)
|
|
||||||
println("deleted log file "..config.LOG_PATH)
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
elseif fs.exists(settings_file) and settings.load(settings_file) then
|
|
||||||
local log = settings.get("LogPath")
|
|
||||||
if log ~= nil and fs.exists(log) then
|
|
||||||
log_deleted = true
|
|
||||||
fs.delete(log)
|
|
||||||
println("deleted log file "..log)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if not log_deleted then
|
|
||||||
red();println("Failed to delete log file.")
|
|
||||||
white();println("press any key to continue...")
|
|
||||||
any_key();lgray()
|
|
||||||
end
|
|
||||||
|
|
||||||
-- delete all installed files
|
-- delete all installed files
|
||||||
|
lgray()
|
||||||
for _, dependency in pairs(dependencies) do
|
for _, dependency in pairs(dependencies) do
|
||||||
local files = file_list[dependency]
|
local files = file_list[dependency]
|
||||||
for _, file in pairs(files) do
|
for _, file in pairs(files) do
|
||||||
@ -582,8 +646,23 @@ elseif mode == "uninstall" then
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if fs.exists(legacy_config_file) then
|
-- delete log file
|
||||||
fs.delete(legacy_config_file);println("deleted "..legacy_config_file)
|
local log_deleted = false
|
||||||
|
local settings_file = app..".settings"
|
||||||
|
|
||||||
|
if fs.exists(settings_file) and settings.load(settings_file) then
|
||||||
|
local log = settings.get("LogPath")
|
||||||
|
if log ~= nil then
|
||||||
|
log_deleted = true
|
||||||
|
if fs.exists(log) then
|
||||||
|
fs.delete(log)
|
||||||
|
println("deleted log file "..log)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not log_deleted then
|
||||||
|
red();println("Failed to delete log file (it may not exist).");lgray()
|
||||||
end
|
end
|
||||||
|
|
||||||
if fs.exists(settings_file) then
|
if fs.exists(settings_file) then
|
||||||
|
@ -7,10 +7,11 @@ 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 themes = require("graphics.themes")
|
|
||||||
|
|
||||||
local core = require("graphics.core")
|
local core = require("graphics.core")
|
||||||
|
local themes = require("graphics.themes")
|
||||||
|
|
||||||
local DisplayBox = require("graphics.elements.displaybox")
|
local DisplayBox = require("graphics.elements.displaybox")
|
||||||
local Div = require("graphics.elements.div")
|
local Div = require("graphics.elements.div")
|
||||||
@ -44,7 +45,9 @@ local RIGHT = core.ALIGN.RIGHT
|
|||||||
-- 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.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.5.1", { "Added energy scale options" } }
|
||||||
}
|
}
|
||||||
|
|
||||||
---@class crd_configurator
|
---@class crd_configurator
|
||||||
@ -68,7 +71,7 @@ local tool_ctl = {
|
|||||||
nic = nil, ---@type nic
|
nic = nil, ---@type nic
|
||||||
net_listen = false,
|
net_listen = false,
|
||||||
sv_addr = comms.BROADCAST,
|
sv_addr = comms.BROADCAST,
|
||||||
sv_seq_num = 0,
|
sv_seq_num = util.time_ms() * 10,
|
||||||
sv_cool_conf = nil, ---@type table list of boiler & turbine counts
|
sv_cool_conf = nil, ---@type table list of boiler & turbine counts
|
||||||
show_sv_cfg = nil, ---@type function
|
show_sv_cfg = nil, ---@type function
|
||||||
|
|
||||||
@ -117,6 +120,7 @@ local tmp_cfg = {
|
|||||||
SpeakerVolume = 1.0,
|
SpeakerVolume = 1.0,
|
||||||
Time24Hour = true,
|
Time24Hour = true,
|
||||||
TempScale = 1,
|
TempScale = 1,
|
||||||
|
EnergyScale = 1,
|
||||||
DisableFlowView = false,
|
DisableFlowView = false,
|
||||||
MainDisplay = nil, ---@type string
|
MainDisplay = nil, ---@type string
|
||||||
FlowDisplay = nil, ---@type string
|
FlowDisplay = nil, ---@type string
|
||||||
@ -149,7 +153,8 @@ 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 },
|
||||||
{ "TempScale", "Temperature Scale", 1 },
|
{ "TempScale", "Temperature Scale", types.TEMP_SCALE.KELVIN },
|
||||||
|
{ "EnergyScale", "Energy Scale", types.ENERGY_SCALE.FE },
|
||||||
{ "DisableFlowView", "Disable Flow Monitor (legacy, discouraged)", false },
|
{ "DisableFlowView", "Disable Flow Monitor (legacy, discouraged)", false },
|
||||||
{ "SVR_Channel", "SVR Channel", 16240 },
|
{ "SVR_Channel", "SVR Channel", 16240 },
|
||||||
{ "CRD_Channel", "CRD Channel", 16243 },
|
{ "CRD_Channel", "CRD Channel", 16243 },
|
||||||
@ -303,7 +308,7 @@ local function config_view(display)
|
|||||||
---@diagnostic disable-next-line: undefined-field
|
---@diagnostic disable-next-line: undefined-field
|
||||||
local function exit() os.queueEvent("terminate") end
|
local function exit() os.queueEvent("terminate") end
|
||||||
|
|
||||||
TextBox{parent=display,y=1,text="Coordinator Configurator",alignment=CENTER,height=1,fg_bg=style.header}
|
TextBox{parent=display,y=1,text="Coordinator Configurator",alignment=CENTER,fg_bg=style.header}
|
||||||
|
|
||||||
local root_pane_div = Div{parent=display,x=1,y=2}
|
local root_pane_div = Div{parent=display,x=1,y=2}
|
||||||
|
|
||||||
@ -331,7 +336,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 has 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 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)}
|
||||||
y_start = y_start + 5
|
y_start = y_start + 5
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -358,7 +363,7 @@ local function config_view(display)
|
|||||||
end
|
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}
|
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)}
|
||||||
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}
|
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}
|
||||||
|
|
||||||
if not tool_ctl.has_config then
|
if not tool_ctl.has_config then
|
||||||
@ -375,24 +380,24 @@ local function config_view(display)
|
|||||||
|
|
||||||
local net_pane = MultiPane{parent=net_cfg,x=1,y=4,panes={net_c_1,net_c_2,net_c_3,net_c_4}}
|
local net_pane = MultiPane{parent=net_cfg,x=1,y=4,panes={net_c_1,net_c_2,net_c_3,net_c_4}}
|
||||||
|
|
||||||
TextBox{parent=net_cfg,x=1,y=2,height=1,text=" Network Configuration",fg_bg=cpair(colors.black,colors.lightBlue)}
|
TextBox{parent=net_cfg,x=1,y=2,text=" Network Configuration",fg_bg=cpair(colors.black,colors.lightBlue)}
|
||||||
|
|
||||||
TextBox{parent=net_c_1,x=1,y=1,height=1,text="Please set the network channels below."}
|
TextBox{parent=net_c_1,x=1,y=1,text="Please set the network channels below."}
|
||||||
TextBox{parent=net_c_1,x=1,y=3,height=4,text="Each of the 5 uniquely named channels, including the 3 below, must be the same for each device in this SCADA network. For multiplayer servers, it is recommended to not use the default channels.",fg_bg=g_lg_fg_bg}
|
TextBox{parent=net_c_1,x=1,y=3,height=4,text="Each of the 5 uniquely named channels, including the 3 below, must be the same for each device in this SCADA network. For multiplayer servers, it is recommended to not use the default channels.",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=net_c_1,x=1,y=8,height=1,width=18,text="Supervisor Channel"}
|
TextBox{parent=net_c_1,x=1,y=8,width=18,text="Supervisor Channel"}
|
||||||
local svr_chan = NumberField{parent=net_c_1,x=21,y=8,width=7,default=ini_cfg.SVR_Channel,min=1,max=65535,fg_bg=bw_fg_bg}
|
local svr_chan = NumberField{parent=net_c_1,x=21,y=8,width=7,default=ini_cfg.SVR_Channel,min=1,max=65535,fg_bg=bw_fg_bg}
|
||||||
TextBox{parent=net_c_1,x=29,y=8,height=4,text="[SVR_CHANNEL]",fg_bg=g_lg_fg_bg}
|
TextBox{parent=net_c_1,x=29,y=8,height=4,text="[SVR_CHANNEL]",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=net_c_1,x=1,y=10,height=1,width=19,text="Coordinator Channel"}
|
TextBox{parent=net_c_1,x=1,y=10,width=19,text="Coordinator Channel"}
|
||||||
local crd_chan = NumberField{parent=net_c_1,x=21,y=10,width=7,default=ini_cfg.CRD_Channel,min=1,max=65535,fg_bg=bw_fg_bg}
|
local crd_chan = NumberField{parent=net_c_1,x=21,y=10,width=7,default=ini_cfg.CRD_Channel,min=1,max=65535,fg_bg=bw_fg_bg}
|
||||||
TextBox{parent=net_c_1,x=29,y=10,height=4,text="[CRD_CHANNEL]",fg_bg=g_lg_fg_bg}
|
TextBox{parent=net_c_1,x=29,y=10,height=4,text="[CRD_CHANNEL]",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=net_c_1,x=1,y=12,height=1,width=14,text="Pocket Channel"}
|
TextBox{parent=net_c_1,x=1,y=12,width=14,text="Pocket Channel"}
|
||||||
local pkt_chan = NumberField{parent=net_c_1,x=21,y=12,width=7,default=ini_cfg.PKT_Channel,min=1,max=65535,fg_bg=bw_fg_bg}
|
local pkt_chan = NumberField{parent=net_c_1,x=21,y=12,width=7,default=ini_cfg.PKT_Channel,min=1,max=65535,fg_bg=bw_fg_bg}
|
||||||
TextBox{parent=net_c_1,x=29,y=12,height=4,text="[PKT_CHANNEL]",fg_bg=g_lg_fg_bg}
|
TextBox{parent=net_c_1,x=29,y=12,height=4,text="[PKT_CHANNEL]",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
local chan_err = TextBox{parent=net_c_1,x=8,y=14,height=1,width=35,text="Please set all channels.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
local chan_err = TextBox{parent=net_c_1,x=8,y=14,width=35,text="Please set all channels.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||||
|
|
||||||
local function submit_channels()
|
local function submit_channels()
|
||||||
local svr_c, crd_c, pkt_c = tonumber(svr_chan.get_value()), tonumber(crd_chan.get_value()), tonumber(pkt_chan.get_value())
|
local svr_c, crd_c, pkt_c = tonumber(svr_chan.get_value()), tonumber(crd_chan.get_value()), tonumber(pkt_chan.get_value())
|
||||||
@ -406,18 +411,18 @@ local function config_view(display)
|
|||||||
PushButton{parent=net_c_1,x=1,y=14,text="\x1b Back",callback=function()main_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=net_c_1,x=1,y=14,text="\x1b Back",callback=function()main_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
PushButton{parent=net_c_1,x=44,y=14,text="Next \x1a",callback=submit_channels,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=net_c_1,x=44,y=14,text="Next \x1a",callback=submit_channels,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=net_c_2,x=1,y=1,height=1,text="Please set the connection timeouts below."}
|
TextBox{parent=net_c_2,x=1,y=1,text="Please set the connection timeouts below."}
|
||||||
TextBox{parent=net_c_2,x=1,y=3,height=4,text="You generally should not need to modify these. On slow servers, you can try to increase this to make the system wait longer before assuming a disconnection. The default for all is 5 seconds.",fg_bg=g_lg_fg_bg}
|
TextBox{parent=net_c_2,x=1,y=3,height=4,text="You generally should not need to modify these. On slow servers, you can try to increase this to make the system wait longer before assuming a disconnection. The default for all is 5 seconds.",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=net_c_2,x=1,y=8,height=1,width=19,text="Supervisor Timeout"}
|
TextBox{parent=net_c_2,x=1,y=8,width=19,text="Supervisor Timeout"}
|
||||||
local svr_timeout = NumberField{parent=net_c_2,x=20,y=8,width=7,default=ini_cfg.SVR_Timeout,min=2,max=25,max_chars=6,max_frac_digits=2,allow_decimal=true,fg_bg=bw_fg_bg}
|
local svr_timeout = NumberField{parent=net_c_2,x=20,y=8,width=7,default=ini_cfg.SVR_Timeout,min=2,max=25,max_chars=6,max_frac_digits=2,allow_decimal=true,fg_bg=bw_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=net_c_2,x=1,y=10,height=1,width=14,text="Pocket Timeout"}
|
TextBox{parent=net_c_2,x=1,y=10,width=14,text="Pocket Timeout"}
|
||||||
local api_timeout = NumberField{parent=net_c_2,x=20,y=10,width=7,default=ini_cfg.API_Timeout,min=2,max=25,max_chars=6,max_frac_digits=2,allow_decimal=true,fg_bg=bw_fg_bg}
|
local api_timeout = NumberField{parent=net_c_2,x=20,y=10,width=7,default=ini_cfg.API_Timeout,min=2,max=25,max_chars=6,max_frac_digits=2,allow_decimal=true,fg_bg=bw_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=net_c_2,x=28,y=8,height=4,width=7,text="seconds\n\nseconds",fg_bg=g_lg_fg_bg}
|
TextBox{parent=net_c_2,x=28,y=8,height=4,width=7,text="seconds\n\nseconds",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
local ct_err = TextBox{parent=net_c_2,x=8,y=14,height=1,width=35,text="Please set all connection timeouts.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
local ct_err = TextBox{parent=net_c_2,x=8,y=14,width=35,text="Please set all connection timeouts.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||||
|
|
||||||
local function submit_timeouts()
|
local function submit_timeouts()
|
||||||
local svr_cto, api_cto = tonumber(svr_timeout.get_value()), tonumber(api_timeout.get_value())
|
local svr_cto, api_cto = tonumber(svr_timeout.get_value()), tonumber(api_timeout.get_value())
|
||||||
@ -431,13 +436,13 @@ local function config_view(display)
|
|||||||
PushButton{parent=net_c_2,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=net_c_2,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
PushButton{parent=net_c_2,x=44,y=14,text="Next \x1a",callback=submit_timeouts,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=net_c_2,x=44,y=14,text="Next \x1a",callback=submit_timeouts,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=net_c_3,x=1,y=1,height=1,text="Please set the trusted range below."}
|
TextBox{parent=net_c_3,x=1,y=1,text="Please set the trusted range below."}
|
||||||
TextBox{parent=net_c_3,x=1,y=3,height=3,text="Setting this to a value larger than 0 prevents connections with devices that many meters (blocks) away in any direction.",fg_bg=g_lg_fg_bg}
|
TextBox{parent=net_c_3,x=1,y=3,height=3,text="Setting this to a value larger than 0 prevents connections with devices that many meters (blocks) away in any direction.",fg_bg=g_lg_fg_bg}
|
||||||
TextBox{parent=net_c_3,x=1,y=7,height=2,text="This is optional. You can disable this functionality by setting the value to 0.",fg_bg=g_lg_fg_bg}
|
TextBox{parent=net_c_3,x=1,y=7,height=2,text="This is optional. You can disable this functionality by setting the value to 0.",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
local range = NumberField{parent=net_c_3,x=1,y=10,width=10,default=ini_cfg.TrustedRange,min=0,max_chars=20,allow_decimal=true,fg_bg=bw_fg_bg}
|
local range = NumberField{parent=net_c_3,x=1,y=10,width=10,default=ini_cfg.TrustedRange,min=0,max_chars=20,allow_decimal=true,fg_bg=bw_fg_bg}
|
||||||
|
|
||||||
local tr_err = TextBox{parent=net_c_3,x=8,y=14,height=1,width=35,text="Please set the trusted range.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
local tr_err = TextBox{parent=net_c_3,x=8,y=14,width=35,text="Please set the trusted range.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||||
|
|
||||||
local function submit_tr()
|
local function submit_tr()
|
||||||
local range_val = tonumber(range.get_value())
|
local range_val = tonumber(range.get_value())
|
||||||
@ -455,7 +460,7 @@ local function config_view(display)
|
|||||||
TextBox{parent=net_c_4,x=1,y=1,height=2,text="Optionally, set the facility authentication key below. Do NOT use one of your passwords."}
|
TextBox{parent=net_c_4,x=1,y=1,height=2,text="Optionally, set the facility authentication key below. Do NOT use one of your passwords."}
|
||||||
TextBox{parent=net_c_4,x=1,y=4,height=6,text="This enables verifying that messages are authentic, so it is intended for security on multiplayer servers. All devices on the same network MUST use the same key if any device has a key. This does result in some extra compution (can slow things down).",fg_bg=g_lg_fg_bg}
|
TextBox{parent=net_c_4,x=1,y=4,height=6,text="This enables verifying that messages are authentic, so it is intended for security on multiplayer servers. All devices on the same network MUST use the same key if any device has a key. This does result in some extra compution (can slow things down).",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=net_c_4,x=1,y=11,height=1,text="Facility Auth Key"}
|
TextBox{parent=net_c_4,x=1,y=11,text="Facility Auth Key"}
|
||||||
local key, _, censor = TextField{parent=net_c_4,x=1,y=12,max_len=64,value=ini_cfg.AuthKey,width=32,height=1,fg_bg=bw_fg_bg}
|
local key, _, censor = TextField{parent=net_c_4,x=1,y=12,max_len=64,value=ini_cfg.AuthKey,width=32,height=1,fg_bg=bw_fg_bg}
|
||||||
|
|
||||||
local function censor_key(enable) censor(util.trinary(enable, "*", nil)) end
|
local function censor_key(enable) censor(util.trinary(enable, "*", nil)) end
|
||||||
@ -465,7 +470,7 @@ local function config_view(display)
|
|||||||
hide_key.set_value(true)
|
hide_key.set_value(true)
|
||||||
censor_key(true)
|
censor_key(true)
|
||||||
|
|
||||||
local key_err = TextBox{parent=net_c_4,x=8,y=14,height=1,width=35,text="Key must be at least 8 characters.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
local key_err = TextBox{parent=net_c_4,x=8,y=14,width=35,text="Key must be at least 8 characters.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||||
|
|
||||||
local function submit_auth()
|
local function submit_auth()
|
||||||
local v = key.get_value()
|
local v = key.get_value()
|
||||||
@ -503,12 +508,12 @@ local function config_view(display)
|
|||||||
|
|
||||||
local fac_pane = MultiPane{parent=mon_cfg,x=1,y=4,panes={fac_c_1,fac_c_2,fac_c_3}}
|
local fac_pane = MultiPane{parent=mon_cfg,x=1,y=4,panes={fac_c_1,fac_c_2,fac_c_3}}
|
||||||
|
|
||||||
TextBox{parent=fac_cfg,x=1,y=2,height=1,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)}
|
||||||
|
|
||||||
TextBox{parent=fac_c_1,x=1,y=1,height=4,text="This tool can attempt to connect to your supervisor computer. This would load facility information in order to get the unit count and aid monitor setup."}
|
TextBox{parent=fac_c_1,x=1,y=1,height=4,text="This tool can attempt to connect to your supervisor computer. This would load facility information in order to get the unit count and aid monitor setup."}
|
||||||
TextBox{parent=fac_c_1,x=1,y=6,height=2,text="The supervisor startup app must be running and fully configured on your supervisor computer."}
|
TextBox{parent=fac_c_1,x=1,y=6,height=2,text="The supervisor startup app must be running and fully configured on your supervisor computer."}
|
||||||
|
|
||||||
tool_ctl.sv_conn_status = TextBox{parent=fac_c_1,x=11,y=9,height=1,text=""}
|
tool_ctl.sv_conn_status = TextBox{parent=fac_c_1,x=11,y=9,text=""}
|
||||||
tool_ctl.sv_conn_detail = TextBox{parent=fac_c_1,x=1,y=11,height=2,text=""}
|
tool_ctl.sv_conn_detail = TextBox{parent=fac_c_1,x=1,y=11,height=2,text=""}
|
||||||
|
|
||||||
tool_ctl.sv_conn_button = PushButton{parent=fac_c_1,x=1,y=9,text="Connect",min_width=9,callback=function()tool_ctl.sv_connect()end,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg,dis_fg_bg=dis_fg_bg}
|
tool_ctl.sv_conn_button = PushButton{parent=fac_c_1,x=1,y=9,text="Connect",min_width=9,callback=function()tool_ctl.sv_connect()end,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg,dis_fg_bg=dis_fg_bg}
|
||||||
@ -532,11 +537,11 @@ local function config_view(display)
|
|||||||
|
|
||||||
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."}
|
||||||
local 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}
|
local 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,height=1,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=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}
|
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,height=1,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}
|
||||||
|
|
||||||
local function submit_num_units()
|
local function submit_num_units()
|
||||||
local count = tonumber(num_units.get_value())
|
local count = tonumber(num_units.get_value())
|
||||||
@ -569,7 +574,7 @@ local function config_view(display)
|
|||||||
|
|
||||||
local mon_pane = MultiPane{parent=mon_cfg,x=1,y=4,panes={mon_c_1,mon_c_2,mon_c_3,mon_c_4}}
|
local mon_pane = MultiPane{parent=mon_cfg,x=1,y=4,panes={mon_c_1,mon_c_2,mon_c_3,mon_c_4}}
|
||||||
|
|
||||||
TextBox{parent=mon_cfg,x=1,y=2,height=1,text=" Monitor Configuration",fg_bg=cpair(colors.black,colors.blue)}
|
TextBox{parent=mon_cfg,x=1,y=2,text=" Monitor Configuration",fg_bg=cpair(colors.black,colors.blue)}
|
||||||
|
|
||||||
TextBox{parent=mon_c_1,x=1,y=1,height=5,text="Your configuration requires the following monitors. The main and flow monitors' heights are dependent on your unit count and cooling setup. If you manually entered the unit count, a * will be shown on potentially inaccurate calculations."}
|
TextBox{parent=mon_c_1,x=1,y=1,height=5,text="Your configuration requires the following monitors. The main and flow monitors' heights are dependent on your unit count and cooling setup. If you manually entered the unit count, a * will be shown on potentially inaccurate calculations."}
|
||||||
local mon_reqs = ListBox{parent=mon_c_1,x=1,y=7,height=6,width=49,scroll_height=100,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
|
local mon_reqs = ListBox{parent=mon_c_1,x=1,y=7,height=6,width=49,scroll_height=100,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
|
||||||
@ -590,7 +595,7 @@ local function config_view(display)
|
|||||||
|
|
||||||
local mon_list = ListBox{parent=mon_c_2,x=1,y=6,height=7,width=49,scroll_height=100,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
|
local mon_list = ListBox{parent=mon_c_2,x=1,y=6,height=7,width=49,scroll_height=100,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
|
||||||
|
|
||||||
local assign_err = TextBox{parent=mon_c_2,x=8,y=14,height=1,width=35,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
local assign_err = TextBox{parent=mon_c_2,x=8,y=14,width=35,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||||
|
|
||||||
local function submit_monitors()
|
local function submit_monitors()
|
||||||
if tmp_cfg.MainDisplay == nil then
|
if tmp_cfg.MainDisplay == nil then
|
||||||
@ -650,13 +655,13 @@ local function config_view(display)
|
|||||||
if value == "0" or value == nil then mon_unit.set_value(0) end
|
if value == "0" or value == nil then mon_unit.set_value(0) end
|
||||||
end
|
end
|
||||||
|
|
||||||
TextBox{parent=mon_c_3,x=1,y=6,width=10,height=1,text="Assignment"}
|
TextBox{parent=mon_c_3,x=1,y=6,width=10,text="Assignment"}
|
||||||
local mon_assign = RadioButton{parent=mon_c_3,x=1,y=7,default=1,options={"Main Monitor","Flow Monitor","Unit Monitor"},callback=on_assign_mon,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.blue}
|
local mon_assign = RadioButton{parent=mon_c_3,x=1,y=7,default=1,options={"Main Monitor","Flow Monitor","Unit Monitor"},callback=on_assign_mon,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.blue}
|
||||||
|
|
||||||
mon_unit_l = TextBox{parent=mon_c_3,x=18,y=6,width=7,height=1,text="Unit ID"}
|
mon_unit_l = TextBox{parent=mon_c_3,x=18,y=6,width=7,text="Unit ID"}
|
||||||
mon_unit = NumberField{parent=mon_c_3,x=18,y=7,width=10,max_chars=2,min=1,max=4,fg_bg=bw_fg_bg}
|
mon_unit = NumberField{parent=mon_c_3,x=18,y=7,width=10,max_chars=2,min=1,max=4,fg_bg=bw_fg_bg}
|
||||||
|
|
||||||
local mon_u_err = TextBox{parent=mon_c_3,x=8,y=14,height=1,width=35,text="Please provide a unit ID.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
local mon_u_err = TextBox{parent=mon_c_3,x=8,y=14,width=35,text="Please provide a unit ID.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||||
|
|
||||||
-- purge all assignments for a given monitor
|
-- purge all assignments for a given monitor
|
||||||
---@param iface string
|
---@param iface string
|
||||||
@ -718,7 +723,7 @@ local function config_view(display)
|
|||||||
|
|
||||||
local spkr_c = Div{parent=spkr_cfg,x=2,y=4,width=49}
|
local spkr_c = Div{parent=spkr_cfg,x=2,y=4,width=49}
|
||||||
|
|
||||||
TextBox{parent=spkr_cfg,x=1,y=2,height=1,text=" Speaker Configuration",fg_bg=cpair(colors.black,colors.cyan)}
|
TextBox{parent=spkr_cfg,x=1,y=2,text=" Speaker Configuration",fg_bg=cpair(colors.black,colors.cyan)}
|
||||||
|
|
||||||
TextBox{parent=spkr_c,x=1,y=1,height=2,text="The coordinator uses a speaker to play alarm sounds."}
|
TextBox{parent=spkr_c,x=1,y=1,height=2,text="The coordinator uses a speaker to play alarm sounds."}
|
||||||
TextBox{parent=spkr_c,x=1,y=4,height=3,text="You can change the speaker audio volume from the default. The range is 0.0 to 3.0, where 1.0 is standard volume."}
|
TextBox{parent=spkr_c,x=1,y=4,height=3,text="You can change the speaker audio volume from the default. The range is 0.0 to 3.0, where 1.0 is standard volume."}
|
||||||
@ -727,7 +732,7 @@ local function config_view(display)
|
|||||||
|
|
||||||
TextBox{parent=spkr_c,x=1,y=10,height=3,text="Note: alarm sine waves are at half scale so that multiple will be required to reach full scale.",fg_bg=g_lg_fg_bg}
|
TextBox{parent=spkr_c,x=1,y=10,height=3,text="Note: alarm sine waves are at half scale so that multiple will be required to reach full scale.",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
local s_vol_err = TextBox{parent=spkr_c,x=8,y=14,height=1,width=35,text="Please set a volume.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
local s_vol_err = TextBox{parent=spkr_c,x=8,y=14,width=35,text="Please set a volume.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||||
|
|
||||||
local function submit_vol()
|
local function submit_vol()
|
||||||
local vol = tonumber(s_vol.get_value())
|
local vol = tonumber(s_vol.get_value())
|
||||||
@ -747,19 +752,23 @@ local function config_view(display)
|
|||||||
|
|
||||||
local crd_c_1 = Div{parent=crd_cfg,x=2,y=4,width=49}
|
local crd_c_1 = Div{parent=crd_cfg,x=2,y=4,width=49}
|
||||||
|
|
||||||
TextBox{parent=crd_cfg,x=1,y=2,height=1,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=3,text="Configure the UI interface options below if you wish to customize formats."}
|
||||||
|
|
||||||
TextBox{parent=crd_c_1,x=1,y=4,height=1,text="Clock Time Format"}
|
TextBox{parent=crd_c_1,x=1,y=4,text="Clock Time Format"}
|
||||||
local 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}
|
local clock_fmt = RadioButton{parent=crd_c_1,x=1,y=5,default=util.trinary(ini_cfg.Time24Hour,1,2),options={"24-Hour","12-Hour"},callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime}
|
||||||
|
|
||||||
TextBox{parent=crd_c_1,x=1,y=8,height=1,text="Temperature Scale"}
|
TextBox{parent=crd_c_1,x=1,y=8,text="Temperature Scale"}
|
||||||
local temp_scale = RadioButton{parent=crd_c_1,x=1,y=9,default=ini_cfg.TempScale,options={"Kelvin","Celsius","Fahrenheit","Rankine"},callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime}
|
local 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"}
|
||||||
|
local energy_scale = RadioButton{parent=crd_c_1,x=24,y=9,default=ini_cfg.EnergyScale,options=types.ENERGY_SCALE_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime}
|
||||||
|
|
||||||
local function submit_ui_opts()
|
local function submit_ui_opts()
|
||||||
tmp_cfg.Time24Hour = clock_fmt.get_value() == 1
|
tmp_cfg.Time24Hour = clock_fmt.get_value() == 1
|
||||||
tmp_cfg.TempScale = temp_scale.get_value()
|
tmp_cfg.TempScale = temp_scale.get_value()
|
||||||
|
tmp_cfg.EnergyScale = energy_scale.get_value()
|
||||||
main_pane.set_value(7)
|
main_pane.set_value(7)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -772,20 +781,20 @@ local function config_view(display)
|
|||||||
|
|
||||||
local log_c_1 = Div{parent=log_cfg,x=2,y=4,width=49}
|
local log_c_1 = Div{parent=log_cfg,x=2,y=4,width=49}
|
||||||
|
|
||||||
TextBox{parent=log_cfg,x=1,y=2,height=1,text=" Logging Configuration",fg_bg=cpair(colors.black,colors.pink)}
|
TextBox{parent=log_cfg,x=1,y=2,text=" Logging Configuration",fg_bg=cpair(colors.black,colors.pink)}
|
||||||
|
|
||||||
TextBox{parent=log_c_1,x=1,y=1,height=1,text="Please configure logging below."}
|
TextBox{parent=log_c_1,x=1,y=1,text="Please configure logging below."}
|
||||||
|
|
||||||
TextBox{parent=log_c_1,x=1,y=3,height=1,text="Log File Mode"}
|
TextBox{parent=log_c_1,x=1,y=3,text="Log File Mode"}
|
||||||
local mode = RadioButton{parent=log_c_1,x=1,y=4,default=ini_cfg.LogMode+1,options={"Append on Startup","Replace on Startup"},callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.pink}
|
local mode = RadioButton{parent=log_c_1,x=1,y=4,default=ini_cfg.LogMode+1,options={"Append on Startup","Replace on Startup"},callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.pink}
|
||||||
|
|
||||||
TextBox{parent=log_c_1,x=1,y=7,height=1,text="Log File Path"}
|
TextBox{parent=log_c_1,x=1,y=7,text="Log File Path"}
|
||||||
local path = TextField{parent=log_c_1,x=1,y=8,width=49,height=1,value=ini_cfg.LogPath,max_len=128,fg_bg=bw_fg_bg}
|
local path = TextField{parent=log_c_1,x=1,y=8,width=49,height=1,value=ini_cfg.LogPath,max_len=128,fg_bg=bw_fg_bg}
|
||||||
|
|
||||||
local en_dbg = CheckBox{parent=log_c_1,x=1,y=10,default=ini_cfg.LogDebug,label="Enable Logging Debug Messages",box_fg_bg=cpair(colors.pink,colors.black)}
|
local en_dbg = CheckBox{parent=log_c_1,x=1,y=10,default=ini_cfg.LogDebug,label="Enable Logging Debug Messages",box_fg_bg=cpair(colors.pink,colors.black)}
|
||||||
TextBox{parent=log_c_1,x=3,y=11,height=2,text="This results in much larger log files. It is best to only use this when there is a problem.",fg_bg=g_lg_fg_bg}
|
TextBox{parent=log_c_1,x=3,y=11,height=2,text="This results in much larger log files. It is best to only use this when there is a problem.",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
local path_err = TextBox{parent=log_c_1,x=8,y=14,height=1,width=35,text="Please provide a log file path.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
local path_err = TextBox{parent=log_c_1,x=8,y=14,width=35,text="Please provide a log file path.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||||
|
|
||||||
local function submit_log()
|
local function submit_log()
|
||||||
if path.get_value() ~= "" then
|
if path.get_value() ~= "" then
|
||||||
@ -813,23 +822,38 @@ local function config_view(display)
|
|||||||
|
|
||||||
local clr_pane = MultiPane{parent=clr_cfg,x=1,y=4,panes={clr_c_1,clr_c_2,clr_c_3,clr_c_4}}
|
local clr_pane = MultiPane{parent=clr_cfg,x=1,y=4,panes={clr_c_1,clr_c_2,clr_c_3,clr_c_4}}
|
||||||
|
|
||||||
TextBox{parent=clr_cfg,x=1,y=2,height=1,text=" Color Configuration",fg_bg=cpair(colors.black,colors.magenta)}
|
TextBox{parent=clr_cfg,x=1,y=2,text=" Color Configuration",fg_bg=cpair(colors.black,colors.magenta)}
|
||||||
|
|
||||||
TextBox{parent=clr_c_1,x=1,y=1,height=2,text="Here you can select the color themes for the different UI displays."}
|
TextBox{parent=clr_c_1,x=1,y=1,height=2,text="Here you can select the color themes for the different UI displays."}
|
||||||
TextBox{parent=clr_c_1,x=1,y=4,height=2,text="Click 'Accessibility' below to access colorblind assistive options.",fg_bg=g_lg_fg_bg}
|
TextBox{parent=clr_c_1,x=1,y=4,height=2,text="Click 'Accessibility' below to access colorblind assistive options.",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=clr_c_1,x=1,y=7,height=1,text="Main UI Theme"}
|
TextBox{parent=clr_c_1,x=1,y=7,text="Main UI Theme"}
|
||||||
local main_theme = RadioButton{parent=clr_c_1,x=1,y=8,default=ini_cfg.MainTheme,options=themes.UI_THEME_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.magenta}
|
local main_theme = RadioButton{parent=clr_c_1,x=1,y=8,default=ini_cfg.MainTheme,options=themes.UI_THEME_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.magenta}
|
||||||
|
|
||||||
TextBox{parent=clr_c_1,x=18,y=7,height=1,text="Front Panel Theme"}
|
TextBox{parent=clr_c_1,x=18,y=7,text="Front Panel Theme"}
|
||||||
local fp_theme = RadioButton{parent=clr_c_1,x=18,y=8,default=ini_cfg.FrontPanelTheme,options=themes.FP_THEME_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.magenta}
|
local fp_theme = RadioButton{parent=clr_c_1,x=18,y=8,default=ini_cfg.FrontPanelTheme,options=themes.FP_THEME_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.magenta}
|
||||||
|
|
||||||
TextBox{parent=clr_c_2,x=1,y=1,height=6,text="By default, this project uses green/red heavily to distinguish ok and not, with some indicators also using multiple colors. By selecting a color blindness below, blues will be used instead of greens on indicators and multi-color indicators will be split up as space permits."}
|
TextBox{parent=clr_c_2,x=1,y=1,height=6,text="This system uses color heavily to distinguish ok and not, with some indicators using many colors. By selecting a mode below, indicators will change as shown. For non-standard modes, indicators with more than two colors will be split up."}
|
||||||
|
|
||||||
|
TextBox{parent=clr_c_2,x=21,y=7,text="Preview"}
|
||||||
|
local _ = IndLight{parent=clr_c_2,x=21,y=8,label="Good",colors=cpair(colors.black,colors.green)}
|
||||||
|
_ = IndLight{parent=clr_c_2,x=21,y=9,label="Warning",colors=cpair(colors.black,colors.yellow)}
|
||||||
|
_ = IndLight{parent=clr_c_2,x=21,y=10,label="Bad",colors=cpair(colors.black,colors.red)}
|
||||||
|
local b_off = IndLight{parent=clr_c_2,x=21,y=11,label="Off",colors=cpair(colors.black,colors.black),hidden=true}
|
||||||
|
local g_off = IndLight{parent=clr_c_2,x=21,y=11,label="Off",colors=cpair(colors.gray,colors.gray),hidden=true}
|
||||||
|
|
||||||
local function recolor(value)
|
local function recolor(value)
|
||||||
local c = themes.smooth_stone.color_modes[value]
|
local c = themes.smooth_stone.color_modes[value]
|
||||||
|
|
||||||
if value == 1 then
|
if value == themes.COLOR_MODE.STANDARD or value == themes.COLOR_MODE.BLUE_IND then
|
||||||
|
b_off.hide()
|
||||||
|
g_off.show()
|
||||||
|
else
|
||||||
|
g_off.hide()
|
||||||
|
b_off.show()
|
||||||
|
end
|
||||||
|
|
||||||
|
if #c == 0 then
|
||||||
for i = 1, #style.colors do term.setPaletteColor(style.colors[i].c, style.colors[i].hex) end
|
for i = 1, #style.colors do term.setPaletteColor(style.colors[i].c, style.colors[i].hex) end
|
||||||
else
|
else
|
||||||
term.setPaletteColor(colors.green, c[1].hex)
|
term.setPaletteColor(colors.green, c[1].hex)
|
||||||
@ -838,15 +862,10 @@ local function config_view(display)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
TextBox{parent=clr_c_2,x=1,y=8,height=1,text="Color Mode"}
|
TextBox{parent=clr_c_2,x=1,y=7,width=10,text="Color Mode"}
|
||||||
local c_mode = RadioButton{parent=clr_c_2,x=1,y=9,default=ini_cfg.ColorMode,options=themes.COLOR_MODE_NAMES,callback=recolor,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.magenta}
|
local c_mode = RadioButton{parent=clr_c_2,x=1,y=8,default=ini_cfg.ColorMode,options=themes.COLOR_MODE_NAMES,callback=recolor,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.magenta}
|
||||||
|
|
||||||
TextBox{parent=clr_c_2,x=20,y=8,height=1,text="Preview"}
|
TextBox{parent=clr_c_2,x=21,y=13,height=2,width=18,text="Note: exact color varies by theme.",fg_bg=g_lg_fg_bg}
|
||||||
local _ = IndLight{parent=clr_c_2,x=20,y=9,label="Good",colors=cpair(colors.black,colors.green)}
|
|
||||||
_ = IndLight{parent=clr_c_2,x=20,y=10,label="Warning",colors=cpair(colors.black,colors.yellow)}
|
|
||||||
_ = IndLight{parent=clr_c_2,x=20,y=11,label="Bad",colors=cpair(colors.black,colors.red)}
|
|
||||||
|
|
||||||
TextBox{parent=clr_c_2,x=1,y=14,height=6,text="Note: exact color varies by theme.",fg_bg=g_lg_fg_bg}
|
|
||||||
|
|
||||||
PushButton{parent=clr_c_2,x=44,y=14,min_width=6,text="Done",callback=function()clr_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=clr_c_2,x=44,y=14,min_width=6,text="Done",callback=function()clr_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
@ -899,7 +918,7 @@ local function config_view(display)
|
|||||||
clr_pane.set_value(1)
|
clr_pane.set_value(1)
|
||||||
end
|
end
|
||||||
|
|
||||||
TextBox{parent=clr_c_3,x=1,y=1,height=1,text="Settings saved!"}
|
TextBox{parent=clr_c_3,x=1,y=1,text="Settings saved!"}
|
||||||
PushButton{parent=clr_c_3,x=1,y=14,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray)}
|
PushButton{parent=clr_c_3,x=1,y=14,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray)}
|
||||||
PushButton{parent=clr_c_3,x=44,y=14,min_width=6,text="Home",callback=c_go_home,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=clr_c_3,x=44,y=14,min_width=6,text="Home",callback=c_go_home,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
@ -918,7 +937,7 @@ local function config_view(display)
|
|||||||
|
|
||||||
local sum_pane = MultiPane{parent=summary,x=1,y=4,panes={sum_c_1,sum_c_2,sum_c_3,sum_c_4}}
|
local sum_pane = MultiPane{parent=summary,x=1,y=4,panes={sum_c_1,sum_c_2,sum_c_3,sum_c_4}}
|
||||||
|
|
||||||
TextBox{parent=summary,x=1,y=2,height=1,text=" Summary",fg_bg=cpair(colors.black,colors.green)}
|
TextBox{parent=summary,x=1,y=2,text=" Summary",fg_bg=cpair(colors.black,colors.green)}
|
||||||
|
|
||||||
local setting_list = ListBox{parent=sum_c_1,x=1,y=1,height=12,width=49,scroll_height=100,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
|
local setting_list = ListBox{parent=sum_c_1,x=1,y=1,height=12,width=49,scroll_height=100,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
|
||||||
|
|
||||||
@ -940,7 +959,10 @@ local function config_view(display)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function save_and_continue()
|
local function save_and_continue()
|
||||||
for k, v in pairs(tmp_cfg) do settings.set(k, v) end
|
for _, field in ipairs(fields) do
|
||||||
|
local k, v = field[1], tmp_cfg[field[1]]
|
||||||
|
if v == nil then settings.unset(k) else settings.set(k, v) end
|
||||||
|
end
|
||||||
|
|
||||||
if settings.save("/coordinator.settings") then
|
if settings.save("/coordinator.settings") then
|
||||||
load_settings(settings_cfg, true)
|
load_settings(settings_cfg, true)
|
||||||
@ -957,6 +979,8 @@ local function config_view(display)
|
|||||||
try_set(dis_flow_view, ini_cfg.DisableFlowView)
|
try_set(dis_flow_view, ini_cfg.DisableFlowView)
|
||||||
try_set(s_vol, ini_cfg.SpeakerVolume)
|
try_set(s_vol, ini_cfg.SpeakerVolume)
|
||||||
try_set(clock_fmt, util.trinary(ini_cfg.Time24Hour, 1, 2))
|
try_set(clock_fmt, util.trinary(ini_cfg.Time24Hour, 1, 2))
|
||||||
|
try_set(temp_scale, ini_cfg.TempScale)
|
||||||
|
try_set(energy_scale, ini_cfg.EnergyScale)
|
||||||
try_set(mode, ini_cfg.LogMode)
|
try_set(mode, ini_cfg.LogMode)
|
||||||
try_set(path, ini_cfg.LogPath)
|
try_set(path, ini_cfg.LogPath)
|
||||||
try_set(en_dbg, ini_cfg.LogDebug)
|
try_set(en_dbg, ini_cfg.LogDebug)
|
||||||
@ -986,7 +1010,7 @@ local function config_view(display)
|
|||||||
tool_ctl.show_key_btn = PushButton{parent=sum_c_1,x=8,y=14,min_width=17,text="Unhide Auth Key",callback=function()tool_ctl.show_auth_key()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg,dis_fg_bg=dis_fg_bg}
|
tool_ctl.show_key_btn = PushButton{parent=sum_c_1,x=8,y=14,min_width=17,text="Unhide Auth Key",callback=function()tool_ctl.show_auth_key()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg,dis_fg_bg=dis_fg_bg}
|
||||||
tool_ctl.settings_apply = PushButton{parent=sum_c_1,x=43,y=14,min_width=7,text="Apply",callback=save_and_continue,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg}
|
tool_ctl.settings_apply = PushButton{parent=sum_c_1,x=43,y=14,min_width=7,text="Apply",callback=save_and_continue,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=sum_c_2,x=1,y=1,height=1,text="Settings saved!"}
|
TextBox{parent=sum_c_2,x=1,y=1,text="Settings saved!"}
|
||||||
|
|
||||||
local function go_home()
|
local function go_home()
|
||||||
main_pane.set_value(1)
|
main_pane.set_value(1)
|
||||||
@ -1021,15 +1045,15 @@ local function config_view(display)
|
|||||||
|
|
||||||
local cl = Div{parent=changelog,x=2,y=4,width=49}
|
local cl = Div{parent=changelog,x=2,y=4,width=49}
|
||||||
|
|
||||||
TextBox{parent=changelog,x=1,y=2,height=1,text=" Config Change Log",fg_bg=bw_fg_bg}
|
TextBox{parent=changelog,x=1,y=2,text=" Config Change Log",fg_bg=bw_fg_bg}
|
||||||
|
|
||||||
local c_log = ListBox{parent=cl,x=1,y=1,height=12,width=49,scroll_height=100,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
|
local c_log = ListBox{parent=cl,x=1,y=1,height=12,width=49,scroll_height=100,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
|
||||||
|
|
||||||
for _, change in ipairs(changes) do
|
for _, change in ipairs(changes) do
|
||||||
TextBox{parent=c_log,text=change[1],height=1,fg_bg=bw_fg_bg}
|
TextBox{parent=c_log,text=change[1],fg_bg=bw_fg_bg}
|
||||||
for _, v in ipairs(change[2]) do
|
for _, v in ipairs(change[2]) do
|
||||||
local e = Div{parent=c_log,height=#util.strwrap(v,46)}
|
local e = Div{parent=c_log,height=#util.strwrap(v,46)}
|
||||||
TextBox{parent=e,y=1,x=1,text="- ",height=1,fg_bg=cpair(colors.gray,colors.white)}
|
TextBox{parent=e,y=1,x=1,text="- ",fg_bg=cpair(colors.gray,colors.white)}
|
||||||
TextBox{parent=e,y=1,x=3,text=v,height=e.get_height(),fg_bg=cpair(colors.gray,colors.white)}
|
TextBox{parent=e,y=1,x=3,text=v,height=e.get_height(),fg_bg=cpair(colors.gray,colors.white)}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -1110,7 +1134,6 @@ local function config_view(display)
|
|||||||
tool_ctl.nic.open(tmp_cfg.CRD_Channel)
|
tool_ctl.nic.open(tmp_cfg.CRD_Channel)
|
||||||
|
|
||||||
tool_ctl.sv_addr = comms.BROADCAST
|
tool_ctl.sv_addr = comms.BROADCAST
|
||||||
tool_ctl.sv_seq_num = 0
|
|
||||||
tool_ctl.net_listen = true
|
tool_ctl.net_listen = true
|
||||||
|
|
||||||
send_sv(MGMT_TYPE.ESTABLISH, { comms.version, "0.0.0", DEVICE_TYPE.CRD })
|
send_sv(MGMT_TYPE.ESTABLISH, { comms.version, "0.0.0", DEVICE_TYPE.CRD })
|
||||||
@ -1125,12 +1148,12 @@ local function config_view(display)
|
|||||||
fac_config_list.remove_all()
|
fac_config_list.remove_all()
|
||||||
|
|
||||||
local str = util.sprintf("Facility has %d reactor unit%s:", #conf, util.trinary(#conf==1,"","s"))
|
local str = util.sprintf("Facility has %d reactor unit%s:", #conf, util.trinary(#conf==1,"","s"))
|
||||||
TextBox{parent=fac_config_list,height=1,text=str,fg_bg=cpair(colors.gray,colors.white)}
|
TextBox{parent=fac_config_list,text=str,fg_bg=cpair(colors.gray,colors.white)}
|
||||||
|
|
||||||
for i = 1, #conf do
|
for i = 1, #conf do
|
||||||
local num_b, num_t = conf[i][1], conf[i][2]
|
local num_b, num_t = conf[i][1], conf[i][2]
|
||||||
str = util.sprintf("\x07 Unit %d has %d boiler%s and %d turbine%s", i, num_b, util.trinary(num_b == 1, "", "s"), num_t, util.trinary(num_t == 1, "", "s"))
|
str = util.sprintf("\x07 Unit %d has %d boiler%s and %d turbine%s", i, num_b, util.trinary(num_b == 1, "", "s"), num_t, util.trinary(num_t == 1, "", "s"))
|
||||||
TextBox{parent=fac_config_list,height=1,text=str,fg_bg=cpair(colors.gray,colors.white)}
|
TextBox{parent=fac_config_list,text=str,fg_bg=cpair(colors.gray,colors.white)}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -1166,13 +1189,13 @@ local function config_view(display)
|
|||||||
|
|
||||||
mon_reqs.remove_all()
|
mon_reqs.remove_all()
|
||||||
|
|
||||||
TextBox{parent=mon_reqs,x=1,y=1,height=1,text="\x1a "..tmp_cfg.UnitCount.." Unit View Monitor"..util.trinary(plural,"s","")}
|
TextBox{parent=mon_reqs,x=1,y=1,text="\x1a "..tmp_cfg.UnitCount.." Unit View Monitor"..util.trinary(plural,"s","")}
|
||||||
TextBox{parent=mon_reqs,x=1,y=1,height=1,text=" "..util.trinary(plural,"each ","").."must be 4 blocks wide by 4 tall",fg_bg=cpair(colors.gray,colors.white)}
|
TextBox{parent=mon_reqs,x=1,y=1,text=" "..util.trinary(plural,"each ","").."must be 4 blocks wide by 4 tall",fg_bg=cpair(colors.gray,colors.white)}
|
||||||
TextBox{parent=mon_reqs,x=1,y=1,height=1,text="\x1a 1 Main View Monitor"}
|
TextBox{parent=mon_reqs,x=1,y=1,text="\x1a 1 Main View Monitor"}
|
||||||
TextBox{parent=mon_reqs,x=1,y=1,height=1,text=" must be 8 blocks wide by "..m_at_least..tool_ctl.main_mon_h..asterisk.." tall",fg_bg=cpair(colors.gray,colors.white)}
|
TextBox{parent=mon_reqs,x=1,y=1,text=" must be 8 blocks wide by "..m_at_least..tool_ctl.main_mon_h..asterisk.." tall",fg_bg=cpair(colors.gray,colors.white)}
|
||||||
if not tmp_cfg.DisableFlowView then
|
if not tmp_cfg.DisableFlowView then
|
||||||
TextBox{parent=mon_reqs,x=1,y=1,height=1,text="\x1a 1 Flow View Monitor"}
|
TextBox{parent=mon_reqs,x=1,y=1,text="\x1a 1 Flow View Monitor"}
|
||||||
TextBox{parent=mon_reqs,x=1,y=1,height=1,text=" must be 8 blocks wide by "..f_at_least..tool_ctl.flow_mon_h.." tall",fg_bg=cpair(colors.gray,colors.white)}
|
TextBox{parent=mon_reqs,x=1,y=1,text=" must be 8 blocks wide by "..f_at_least..tool_ctl.flow_mon_h.." tall",fg_bg=cpair(colors.gray,colors.white)}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -1235,6 +1258,10 @@ local function config_view(display)
|
|||||||
function tool_ctl.gen_mon_list()
|
function tool_ctl.gen_mon_list()
|
||||||
mon_list.remove_all()
|
mon_list.remove_all()
|
||||||
|
|
||||||
|
local missing = { main = tmp_cfg.MainDisplay ~= nil, flow = tmp_cfg.FlowDisplay ~= nil, unit = {} }
|
||||||
|
for i = 1, tmp_cfg.UnitCount do missing.unit[i] = tmp_cfg.UnitDisplays[i] ~= nil end
|
||||||
|
|
||||||
|
-- list connected monitors
|
||||||
local monitors = ppm.get_monitor_list()
|
local monitors = ppm.get_monitor_list()
|
||||||
for iface, device in pairs(monitors) do
|
for iface, device in pairs(monitors) do
|
||||||
local dev = device.dev
|
local dev = device.dev
|
||||||
@ -1254,11 +1281,14 @@ local function config_view(display)
|
|||||||
|
|
||||||
if tmp_cfg.MainDisplay == iface then
|
if tmp_cfg.MainDisplay == iface then
|
||||||
assignment = "Main"
|
assignment = "Main"
|
||||||
|
missing.main = false
|
||||||
elseif tmp_cfg.FlowDisplay == iface then
|
elseif tmp_cfg.FlowDisplay == iface then
|
||||||
assignment = "Flow"
|
assignment = "Flow"
|
||||||
|
missing.flow = false
|
||||||
else
|
else
|
||||||
for i = 1, tmp_cfg.UnitCount do
|
for i = 1, tmp_cfg.UnitCount do
|
||||||
if tmp_cfg.UnitDisplays[i] == iface then
|
if tmp_cfg.UnitDisplays[i] == iface then
|
||||||
|
missing.unit[i] = false
|
||||||
assignment = "Unit " .. i
|
assignment = "Unit " .. i
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
@ -1267,8 +1297,8 @@ local function config_view(display)
|
|||||||
|
|
||||||
local line = Div{parent=mon_list,x=1,y=1,height=1}
|
local line = Div{parent=mon_list,x=1,y=1,height=1}
|
||||||
|
|
||||||
TextBox{parent=line,x=1,y=1,width=6,height=1,text=assignment,fg_bg=cpair(util.trinary(assignment=="Unused",colors.red,colors.blue),colors.white)}
|
TextBox{parent=line,x=1,y=1,width=6,text=assignment,fg_bg=cpair(util.trinary(assignment=="Unused",colors.red,colors.blue),colors.white)}
|
||||||
TextBox{parent=line,x=8,y=1,height=1,text=iface}
|
TextBox{parent=line,x=8,y=1,text=iface}
|
||||||
|
|
||||||
local w, h = ppm.monitor_block_size(dev.getSize())
|
local w, h = ppm.monitor_block_size(dev.getSize())
|
||||||
|
|
||||||
@ -1277,12 +1307,37 @@ local function config_view(display)
|
|||||||
tool_ctl.gen_mon_list()
|
tool_ctl.gen_mon_list()
|
||||||
end
|
end
|
||||||
|
|
||||||
TextBox{parent=line,x=33,y=1,width=4,height=1,text=w.."x"..h,fg_bg=cpair(colors.black,colors.white)}
|
TextBox{parent=line,x=33,y=1,width=4,text=w.."x"..h,fg_bg=cpair(colors.black,colors.white)}
|
||||||
PushButton{parent=line,x=37,y=1,min_width=5,height=1,text="SET",callback=function()tool_ctl.edit_monitor(iface,device)end,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=line,x=37,y=1,min_width=5,height=1,text="SET",callback=function()tool_ctl.edit_monitor(iface,device)end,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg}
|
||||||
local unset = PushButton{parent=line,x=42,y=1,min_width=7,height=1,text="UNSET",callback=unset_mon,fg_bg=cpair(colors.black,colors.red),active_fg_bg=btn_act_fg_bg,dis_fg_bg=cpair(colors.black,colors.gray)}
|
local unset = PushButton{parent=line,x=42,y=1,min_width=7,height=1,text="UNSET",callback=unset_mon,fg_bg=cpair(colors.black,colors.red),active_fg_bg=btn_act_fg_bg,dis_fg_bg=cpair(colors.black,colors.gray)}
|
||||||
|
|
||||||
if assignment == "Unused" then unset.disable() end
|
if assignment == "Unused" then unset.disable() end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local dc_list = {} -- disconnected monitor list
|
||||||
|
|
||||||
|
if missing.main then table.insert(dc_list, { "Main", tmp_cfg.MainDisplay }) end
|
||||||
|
if missing.flow then table.insert(dc_list, { "Flow", tmp_cfg.FlowDisplay }) end
|
||||||
|
for i = 1, tmp_cfg.UnitCount do
|
||||||
|
if missing.unit[i] then table.insert(dc_list, { "Unit " .. i, tmp_cfg.UnitDisplays[i] }) end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- add monitors that are assigned but not connected
|
||||||
|
for i = 1, #dc_list do
|
||||||
|
local line = Div{parent=mon_list,x=1,y=1,height=1}
|
||||||
|
|
||||||
|
TextBox{parent=line,x=1,y=1,width=6,text=dc_list[i][1],fg_bg=cpair(colors.blue,colors.white)}
|
||||||
|
TextBox{parent=line,x=8,y=1,text="disconnected",fg_bg=cpair(colors.red,colors.white)}
|
||||||
|
|
||||||
|
local function unset_mon()
|
||||||
|
purge_assignments(dc_list[i][2])
|
||||||
|
tool_ctl.gen_mon_list()
|
||||||
|
end
|
||||||
|
|
||||||
|
TextBox{parent=line,x=33,y=1,width=4,text="?x?",fg_bg=cpair(colors.black,colors.white)}
|
||||||
|
PushButton{parent=line,x=37,y=1,min_width=5,height=1,text="SET",callback=function()end,dis_fg_bg=cpair(colors.black,colors.gray)}.disable()
|
||||||
|
PushButton{parent=line,x=42,y=1,min_width=7,height=1,text="UNSET",callback=unset_mon,fg_bg=cpair(colors.black,colors.red),active_fg_bg=btn_act_fg_bg,dis_fg_bg=cpair(colors.black,colors.gray)}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- expose the auth key on the summary page
|
-- expose the auth key on the summary page
|
||||||
@ -1313,7 +1368,9 @@ local function config_view(display)
|
|||||||
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] == "TempScale" then
|
elseif f[1] == "TempScale" then
|
||||||
if raw == 1 then val = "Kelvin" elseif raw == 2 then val = "Celsius" elseif raw == 3 then val = "Fahrenheit" elseif raw == 4 then val = "Rankine" end
|
val = util.strval(types.TEMP_SCALE_NAMES[raw])
|
||||||
|
elseif f[1] == "EnergyScale" then
|
||||||
|
val = util.strval(types.ENERGY_SCALE_NAMES[raw])
|
||||||
elseif f[1] == "MainTheme" then
|
elseif f[1] == "MainTheme" then
|
||||||
val = util.strval(themes.ui_theme_name(raw))
|
val = util.strval(themes.ui_theme_name(raw))
|
||||||
elseif f[1] == "FrontPanelTheme" then
|
elseif f[1] == "FrontPanelTheme" then
|
||||||
@ -1406,9 +1463,11 @@ function configurator.configure(start_code, message)
|
|||||||
elseif event == "paste" then
|
elseif event == "paste" then
|
||||||
display.handle_paste(param1)
|
display.handle_paste(param1)
|
||||||
elseif event == "peripheral_detach" then
|
elseif event == "peripheral_detach" then
|
||||||
|
---@diagnostic disable-next-line: discard-returns
|
||||||
ppm.handle_unmount(param1)
|
ppm.handle_unmount(param1)
|
||||||
tool_ctl.gen_mon_list()
|
tool_ctl.gen_mon_list()
|
||||||
elseif event == "peripheral" then
|
elseif event == "peripheral" then
|
||||||
|
---@diagnostic disable-next-line: discard-returns
|
||||||
ppm.mount(param1)
|
ppm.mount(param1)
|
||||||
tool_ctl.gen_mon_list()
|
tool_ctl.gen_mon_list()
|
||||||
elseif event == "monitor_resize" then
|
elseif event == "monitor_resize" then
|
||||||
|
@ -4,6 +4,8 @@ local ppm = require("scada-common.ppm")
|
|||||||
local util = require("scada-common.util")
|
local util = require("scada-common.util")
|
||||||
local types = require("scada-common.types")
|
local types = require("scada-common.types")
|
||||||
|
|
||||||
|
local themes = require("graphics.themes")
|
||||||
|
|
||||||
local iocontrol = require("coordinator.iocontrol")
|
local iocontrol = require("coordinator.iocontrol")
|
||||||
local process = require("coordinator.process")
|
local process = require("coordinator.process")
|
||||||
|
|
||||||
@ -36,6 +38,7 @@ function coordinator.load_config()
|
|||||||
config.SpeakerVolume = settings.get("SpeakerVolume")
|
config.SpeakerVolume = settings.get("SpeakerVolume")
|
||||||
config.Time24Hour = settings.get("Time24Hour")
|
config.Time24Hour = settings.get("Time24Hour")
|
||||||
config.TempScale = settings.get("TempScale")
|
config.TempScale = settings.get("TempScale")
|
||||||
|
config.EnergyScale = settings.get("EnergyScale")
|
||||||
|
|
||||||
config.DisableFlowView = settings.get("DisableFlowView")
|
config.DisableFlowView = settings.get("DisableFlowView")
|
||||||
config.MainDisplay = settings.get("MainDisplay")
|
config.MainDisplay = settings.get("MainDisplay")
|
||||||
@ -65,6 +68,8 @@ function coordinator.load_config()
|
|||||||
cfv.assert_type_bool(config.Time24Hour)
|
cfv.assert_type_bool(config.Time24Hour)
|
||||||
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_range(config.EnergyScale, 1, 3)
|
||||||
|
|
||||||
cfv.assert_type_bool(config.DisableFlowView)
|
cfv.assert_type_bool(config.DisableFlowView)
|
||||||
cfv.assert_type_table(config.UnitDisplays)
|
cfv.assert_type_table(config.UnitDisplays)
|
||||||
@ -87,7 +92,7 @@ function coordinator.load_config()
|
|||||||
|
|
||||||
if type(config.AuthKey) == "string" then
|
if type(config.AuthKey) == "string" then
|
||||||
local len = string.len(config.AuthKey)
|
local len = string.len(config.AuthKey)
|
||||||
cfv.assert_eq(len == 0 or len >= 8, true)
|
cfv.assert(len == 0 or len >= 8)
|
||||||
end
|
end
|
||||||
|
|
||||||
cfv.assert_type_int(config.LogMode)
|
cfv.assert_type_int(config.LogMode)
|
||||||
@ -100,7 +105,7 @@ function coordinator.load_config()
|
|||||||
cfv.assert_type_int(config.FrontPanelTheme)
|
cfv.assert_type_int(config.FrontPanelTheme)
|
||||||
cfv.assert_range(config.FrontPanelTheme, 1, 2)
|
cfv.assert_range(config.FrontPanelTheme, 1, 2)
|
||||||
cfv.assert_type_int(config.ColorMode)
|
cfv.assert_type_int(config.ColorMode)
|
||||||
cfv.assert_range(config.ColorMode, 1, 4)
|
cfv.assert_range(config.ColorMode, 1, themes.COLOR_MODE.NUM_MODES)
|
||||||
|
|
||||||
-- Monitor Setup
|
-- Monitor Setup
|
||||||
|
|
||||||
@ -190,7 +195,7 @@ end
|
|||||||
---@return function? update, function? done
|
---@return function? update, function? done
|
||||||
local function log_dmesg(message, dmesg_tag, working)
|
local function log_dmesg(message, dmesg_tag, working)
|
||||||
local colors = {
|
local colors = {
|
||||||
GRAPHICS = colors.green,
|
RENDER = colors.green,
|
||||||
SYSTEM = colors.cyan,
|
SYSTEM = colors.cyan,
|
||||||
BOOT = colors.blue,
|
BOOT = colors.blue,
|
||||||
COMMS = colors.purple,
|
COMMS = colors.purple,
|
||||||
@ -204,7 +209,7 @@ local function log_dmesg(message, dmesg_tag, working)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function coordinator.log_graphics(message) log_dmesg(message, "GRAPHICS") end
|
function coordinator.log_render(message) log_dmesg(message, "RENDER") end
|
||||||
function coordinator.log_sys(message) log_dmesg(message, "SYSTEM") end
|
function coordinator.log_sys(message) log_dmesg(message, "SYSTEM") end
|
||||||
function coordinator.log_boot(message) log_dmesg(message, "BOOT") end
|
function coordinator.log_boot(message) log_dmesg(message, "BOOT") end
|
||||||
function coordinator.log_comms(message) log_dmesg(message, "COMMS") end
|
function coordinator.log_comms(message) log_dmesg(message, "COMMS") end
|
||||||
@ -230,8 +235,8 @@ function coordinator.comms(version, nic, sv_watchdog)
|
|||||||
local self = {
|
local self = {
|
||||||
sv_linked = false,
|
sv_linked = false,
|
||||||
sv_addr = comms.BROADCAST,
|
sv_addr = comms.BROADCAST,
|
||||||
sv_seq_num = 0,
|
sv_seq_num = util.time_ms() * 10, -- unique per peer, restarting will not re-use seq nums due to message rate
|
||||||
sv_r_seq_num = nil,
|
sv_r_seq_num = nil, ---@type nil|integer
|
||||||
sv_config_err = false,
|
sv_config_err = false,
|
||||||
last_est_ack = ESTABLISH_ACK.ALLOW,
|
last_est_ack = ESTABLISH_ACK.ALLOW,
|
||||||
last_api_est_acks = {},
|
last_api_est_acks = {},
|
||||||
@ -277,11 +282,12 @@ function coordinator.comms(version, nic, sv_watchdog)
|
|||||||
-- send an API establish request response
|
-- send an API establish request response
|
||||||
---@param packet scada_packet
|
---@param packet scada_packet
|
||||||
---@param ack ESTABLISH_ACK
|
---@param ack ESTABLISH_ACK
|
||||||
local function _send_api_establish_ack(packet, ack)
|
---@param data any?
|
||||||
|
local function _send_api_establish_ack(packet, ack, data)
|
||||||
local s_pkt = comms.scada_packet()
|
local s_pkt = comms.scada_packet()
|
||||||
local m_pkt = comms.mgmt_packet()
|
local m_pkt = comms.mgmt_packet()
|
||||||
|
|
||||||
m_pkt.make(MGMT_TYPE.ESTABLISH, { ack })
|
m_pkt.make(MGMT_TYPE.ESTABLISH, { ack, data })
|
||||||
s_pkt.make(packet.src_addr(), packet.seq_num() + 1, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable())
|
s_pkt.make(packet.src_addr(), packet.seq_num() + 1, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable())
|
||||||
|
|
||||||
nic.transmit(config.PKT_Channel, config.CRD_Channel, s_pkt)
|
nic.transmit(config.PKT_Channel, config.CRD_Channel, s_pkt)
|
||||||
@ -345,6 +351,7 @@ function coordinator.comms(version, nic, sv_watchdog)
|
|||||||
|
|
||||||
ok = false
|
ok = false
|
||||||
elseif self.sv_config_err then
|
elseif self.sv_config_err then
|
||||||
|
self.est_task_done(false)
|
||||||
coordinator.log_comms("supervisor unit count does not match coordinator unit count, check configs")
|
coordinator.log_comms("supervisor unit count does not match coordinator unit count, check configs")
|
||||||
ok = false
|
ok = false
|
||||||
elseif (os.clock() - self.est_last) > 1.0 then
|
elseif (os.clock() - self.est_last) > 1.0 then
|
||||||
@ -468,10 +475,11 @@ function coordinator.comms(version, nic, sv_watchdog)
|
|||||||
elseif packet.type == MGMT_TYPE.ESTABLISH then
|
elseif packet.type == MGMT_TYPE.ESTABLISH then
|
||||||
-- establish a new session
|
-- establish a new session
|
||||||
-- validate packet and continue
|
-- validate packet and continue
|
||||||
if packet.length == 3 and type(packet.data[1]) == "string" and type(packet.data[2]) == "string" then
|
if packet.length == 4 then
|
||||||
local comms_v = packet.data[1]
|
local comms_v = util.strval(packet.data[1])
|
||||||
local firmware_v = packet.data[2]
|
local firmware_v = util.strval(packet.data[2])
|
||||||
local dev_type = packet.data[3]
|
local dev_type = packet.data[3]
|
||||||
|
local api_v = util.strval(packet.data[4])
|
||||||
|
|
||||||
if comms_v ~= comms.version then
|
if comms_v ~= comms.version then
|
||||||
if self.last_api_est_acks[src_addr] ~= ESTABLISH_ACK.BAD_VERSION then
|
if self.last_api_est_acks[src_addr] ~= ESTABLISH_ACK.BAD_VERSION then
|
||||||
@ -479,12 +487,19 @@ function coordinator.comms(version, nic, sv_watchdog)
|
|||||||
end
|
end
|
||||||
|
|
||||||
_send_api_establish_ack(packet.scada_frame, ESTABLISH_ACK.BAD_VERSION)
|
_send_api_establish_ack(packet.scada_frame, ESTABLISH_ACK.BAD_VERSION)
|
||||||
|
elseif api_v ~= comms.api_version then
|
||||||
|
if self.last_api_est_acks[src_addr] ~= ESTABLISH_ACK.BAD_API_VERSION then
|
||||||
|
log.info(util.c("dropping API establish packet with incorrect api version v", api_v, " (expected v", comms.api_version, ")"))
|
||||||
|
end
|
||||||
|
|
||||||
|
_send_api_establish_ack(packet.scada_frame, ESTABLISH_ACK.BAD_API_VERSION)
|
||||||
elseif dev_type == DEVICE_TYPE.PKT then
|
elseif dev_type == DEVICE_TYPE.PKT then
|
||||||
-- pocket linking request
|
-- pocket linking request
|
||||||
local id = apisessions.establish_session(src_addr, firmware_v)
|
local id = apisessions.establish_session(src_addr, packet.scada_frame.seq_num(), firmware_v)
|
||||||
coordinator.log_comms(util.c("API_ESTABLISH: pocket (", firmware_v, ") [@", src_addr, "] connected with session ID ", id))
|
coordinator.log_comms(util.c("API_ESTABLISH: pocket (", firmware_v, ") [@", src_addr, "] connected with session ID ", id))
|
||||||
|
|
||||||
_send_api_establish_ack(packet.scada_frame, ESTABLISH_ACK.ALLOW)
|
local conf = iocontrol.get_db().facility.conf
|
||||||
|
_send_api_establish_ack(packet.scada_frame, ESTABLISH_ACK.ALLOW, { conf.num_units, conf.cooling })
|
||||||
else
|
else
|
||||||
log.debug(util.c("API_ESTABLISH: illegal establish packet for device ", dev_type, " on pocket channel"))
|
log.debug(util.c("API_ESTABLISH: illegal establish packet for device ", dev_type, " on pocket channel"))
|
||||||
_send_api_establish_ack(packet.scada_frame, ESTABLISH_ACK.DENY)
|
_send_api_establish_ack(packet.scada_frame, ESTABLISH_ACK.DENY)
|
||||||
@ -503,15 +518,15 @@ function coordinator.comms(version, nic, sv_watchdog)
|
|||||||
elseif r_chan == config.SVR_Channel then
|
elseif r_chan == config.SVR_Channel then
|
||||||
-- check sequence number
|
-- check sequence number
|
||||||
if self.sv_r_seq_num == nil then
|
if self.sv_r_seq_num == nil then
|
||||||
self.sv_r_seq_num = packet.scada_frame.seq_num()
|
self.sv_r_seq_num = packet.scada_frame.seq_num() + 1
|
||||||
elseif self.sv_linked and ((self.sv_r_seq_num + 1) ~= packet.scada_frame.seq_num()) then
|
elseif self.sv_r_seq_num ~= packet.scada_frame.seq_num() then
|
||||||
log.warning("sequence out-of-order: last = " .. self.sv_r_seq_num .. ", new = " .. packet.scada_frame.seq_num())
|
log.warning("sequence out-of-order: next = " .. self.sv_r_seq_num .. ", new = " .. packet.scada_frame.seq_num())
|
||||||
return false
|
return false
|
||||||
elseif self.sv_linked and src_addr ~= self.sv_addr then
|
elseif self.sv_linked and src_addr ~= self.sv_addr then
|
||||||
log.debug("received packet from unknown computer " .. src_addr .. " while linked; channel in use by another system?")
|
log.debug("received packet from unknown computer " .. src_addr .. " while linked; channel in use by another system?")
|
||||||
return false
|
return false
|
||||||
else
|
else
|
||||||
self.sv_r_seq_num = packet.scada_frame.seq_num()
|
self.sv_r_seq_num = packet.scada_frame.seq_num() + 1
|
||||||
end
|
end
|
||||||
|
|
||||||
-- feed watchdog on valid sequence number
|
-- feed watchdog on valid sequence number
|
||||||
@ -690,11 +705,10 @@ function coordinator.comms(version, nic, sv_watchdog)
|
|||||||
|
|
||||||
if conf.num_units == config.UnitCount then
|
if conf.num_units == config.UnitCount then
|
||||||
-- init io controller
|
-- init io controller
|
||||||
iocontrol.init(conf, public, config.TempScale)
|
iocontrol.init(conf, public, config.TempScale, config.EnergyScale)
|
||||||
|
|
||||||
self.sv_addr = src_addr
|
self.sv_addr = src_addr
|
||||||
self.sv_linked = true
|
self.sv_linked = true
|
||||||
self.sv_r_seq_num = nil
|
|
||||||
self.sv_config_err = false
|
self.sv_config_err = false
|
||||||
|
|
||||||
iocontrol.fp_link_state(types.PANEL_LINK_STATE.LINKED)
|
iocontrol.fp_link_state(types.PANEL_LINK_STATE.LINKED)
|
||||||
|
@ -15,6 +15,11 @@ local pgi = require("coordinator.ui.pgi")
|
|||||||
local ALARM_STATE = types.ALARM_STATE
|
local ALARM_STATE = types.ALARM_STATE
|
||||||
local PROCESS = types.PROCESS
|
local PROCESS = types.PROCESS
|
||||||
|
|
||||||
|
local ENERGY_SCALE = types.ENERGY_SCALE
|
||||||
|
local ENERGY_UNITS = types.ENERGY_SCALE_UNITS
|
||||||
|
local TEMP_SCALE = types.TEMP_SCALE
|
||||||
|
local TEMP_UNITS = types.TEMP_SCALE_UNITS
|
||||||
|
|
||||||
-- 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
|
||||||
@ -47,29 +52,44 @@ end
|
|||||||
-- initialize the coordinator IO controller
|
-- initialize the coordinator IO controller
|
||||||
---@param conf facility_conf configuration
|
---@param conf facility_conf configuration
|
||||||
---@param comms coord_comms comms reference
|
---@param comms coord_comms comms reference
|
||||||
---@param temp_scale integer temperature unit (1 = K, 2 = C, 3 = F, 4 = R)
|
---@param temp_scale TEMP_SCALE temperature unit
|
||||||
function iocontrol.init(conf, comms, temp_scale)
|
---@param energy_scale ENERGY_SCALE energy unit
|
||||||
|
function iocontrol.init(conf, comms, temp_scale, energy_scale)
|
||||||
|
io.temp_label = TEMP_UNITS[temp_scale]
|
||||||
|
io.energy_label = ENERGY_UNITS[energy_scale]
|
||||||
|
|
||||||
-- temperature unit label and conversion function (from Kelvin)
|
-- temperature unit label and conversion function (from Kelvin)
|
||||||
if temp_scale == 2 then
|
if temp_scale == TEMP_SCALE.CELSIUS then
|
||||||
io.temp_label = "\xb0C"
|
|
||||||
io.temp_convert = function (t) return t - 273.15 end
|
io.temp_convert = function (t) return t - 273.15 end
|
||||||
elseif temp_scale == 3 then
|
elseif temp_scale == TEMP_SCALE.FAHRENHEIT then
|
||||||
io.temp_label = "\xb0F"
|
|
||||||
io.temp_convert = function (t) return (1.8 * (t - 273.15)) + 32 end
|
io.temp_convert = function (t) return (1.8 * (t - 273.15)) + 32 end
|
||||||
elseif temp_scale == 4 then
|
elseif temp_scale == TEMP_SCALE.RANKINE then
|
||||||
io.temp_label = "\xb0R"
|
|
||||||
io.temp_convert = function (t) return 1.8 * t end
|
io.temp_convert = function (t) return 1.8 * t end
|
||||||
else
|
else
|
||||||
io.temp_label = "K"
|
io.temp_label = "K"
|
||||||
io.temp_convert = function (t) return t end
|
io.temp_convert = function (t) return t end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- energy unit label and conversion function (from Joules unless otherwise specified)
|
||||||
|
if energy_scale == ENERGY_SCALE.FE or energy_scale == ENERGY_SCALE.RF then
|
||||||
|
io.energy_convert = util.joules_to_fe_rf
|
||||||
|
io.energy_convert_from_fe = function (t) return t end
|
||||||
|
io.energy_convert_to_fe = function (t) return t end
|
||||||
|
else
|
||||||
|
io.energy_label = "J"
|
||||||
|
io.energy_convert = function (t) return t end
|
||||||
|
io.energy_convert_from_fe = util.fe_rf_to_joules
|
||||||
|
io.energy_convert_to_fe = util.joules_to_fe_rf
|
||||||
|
end
|
||||||
|
|
||||||
-- facility data structure
|
-- facility data structure
|
||||||
---@class ioctl_facility
|
---@class ioctl_facility
|
||||||
io.facility = {
|
io.facility = {
|
||||||
|
conf = 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,
|
||||||
all_sys_ok = false,
|
all_sys_ok = false,
|
||||||
rtu_count = 0,
|
rtu_count = 0,
|
||||||
|
|
||||||
@ -91,6 +111,7 @@ function iocontrol.init(conf, comms, temp_scale)
|
|||||||
---@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,
|
||||||
|
|
||||||
radiation = types.new_zero_radiation_reading(),
|
radiation = types.new_zero_radiation_reading(),
|
||||||
|
|
||||||
@ -123,92 +144,6 @@ function iocontrol.init(conf, comms, temp_scale)
|
|||||||
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, {})
|
||||||
|
|
||||||
-- determine tank information
|
|
||||||
if io.facility.tank_mode == 0 then
|
|
||||||
io.facility.tank_defs = {}
|
|
||||||
-- on facility tank mode 0, setup tank defs to match unit tank option
|
|
||||||
for i = 1, conf.num_units do
|
|
||||||
io.facility.tank_defs[i] = util.trinary(conf.cooling.r_cool[i].TankConnection, 1, 0)
|
|
||||||
end
|
|
||||||
|
|
||||||
io.facility.tank_list = { table.unpack(io.facility.tank_defs) }
|
|
||||||
else
|
|
||||||
-- decode the layout of tanks from the connections definitions
|
|
||||||
local tank_mode = io.facility.tank_mode
|
|
||||||
local tank_defs = io.facility.tank_defs
|
|
||||||
local tank_list = { table.unpack(tank_defs) }
|
|
||||||
|
|
||||||
local function calc_fdef(start_idx, end_idx)
|
|
||||||
local first = 4
|
|
||||||
for i = start_idx, end_idx do
|
|
||||||
if io.facility.tank_defs[i] == 2 then
|
|
||||||
if i < first then first = i end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return first
|
|
||||||
end
|
|
||||||
|
|
||||||
if tank_mode == 1 then
|
|
||||||
-- (1) 1 total facility tank (A A A A)
|
|
||||||
local first_fdef = calc_fdef(1, #tank_defs)
|
|
||||||
for i = 1, #tank_defs do
|
|
||||||
if i > first_fdef and tank_defs[i] == 2 then
|
|
||||||
tank_list[i] = 0
|
|
||||||
end
|
|
||||||
end
|
|
||||||
elseif tank_mode == 2 then
|
|
||||||
-- (2) 2 total facility tanks (A A A B)
|
|
||||||
local first_fdef = calc_fdef(1, math.min(3, #tank_defs))
|
|
||||||
for i = 1, #tank_defs do
|
|
||||||
if (i ~= 4) and (i > first_fdef) and (tank_defs[i] == 2) then
|
|
||||||
tank_list[i] = 0
|
|
||||||
end
|
|
||||||
end
|
|
||||||
elseif tank_mode == 3 then
|
|
||||||
-- (3) 2 total facility tanks (A A B B)
|
|
||||||
for _, a in pairs({ 1, 3 }) do
|
|
||||||
local b = a + 1
|
|
||||||
if (tank_defs[a] == 2) and (tank_defs[b] == 2) then
|
|
||||||
tank_list[b] = 0
|
|
||||||
end
|
|
||||||
end
|
|
||||||
elseif tank_mode == 4 then
|
|
||||||
-- (4) 2 total facility tanks (A B B B)
|
|
||||||
local first_fdef = calc_fdef(2, #tank_defs)
|
|
||||||
for i = 1, #tank_defs do
|
|
||||||
if (i ~= 1) and (i > first_fdef) and (tank_defs[i] == 2) then
|
|
||||||
tank_list[i] = 0
|
|
||||||
end
|
|
||||||
end
|
|
||||||
elseif tank_mode == 5 then
|
|
||||||
-- (5) 3 total facility tanks (A A B C)
|
|
||||||
local first_fdef = calc_fdef(1, math.min(2, #tank_defs))
|
|
||||||
for i = 1, #tank_defs do
|
|
||||||
if (not (i == 3 or i == 4)) and (i > first_fdef) and (tank_defs[i] == 2) then
|
|
||||||
tank_list[i] = 0
|
|
||||||
end
|
|
||||||
end
|
|
||||||
elseif tank_mode == 6 then
|
|
||||||
-- (6) 3 total facility tanks (A B B C)
|
|
||||||
local first_fdef = calc_fdef(2, math.min(3, #tank_defs))
|
|
||||||
for i = 1, #tank_defs do
|
|
||||||
if (not (i == 1 or i == 4)) and (i > first_fdef) and (tank_defs[i] == 2) then
|
|
||||||
tank_list[i] = 0
|
|
||||||
end
|
|
||||||
end
|
|
||||||
elseif tank_mode == 7 then
|
|
||||||
-- (7) 3 total facility tanks (A B C C)
|
|
||||||
local first_fdef = calc_fdef(3, #tank_defs)
|
|
||||||
for i = 1, #tank_defs do
|
|
||||||
if (not (i == 1 or i == 2)) and (i > first_fdef) and (tank_defs[i] == 2) then
|
|
||||||
tank_list[i] = 0
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
io.facility.tank_list = tank_list
|
|
||||||
end
|
|
||||||
|
|
||||||
-- create facility tank tables
|
-- create facility tank tables
|
||||||
for i = 1, #io.facility.tank_list do
|
for i = 1, #io.facility.tank_list do
|
||||||
if io.facility.tank_list[i] == 2 then
|
if io.facility.tank_list[i] == 2 then
|
||||||
@ -226,6 +161,8 @@ function iocontrol.init(conf, comms, temp_scale)
|
|||||||
---@class ioctl_unit
|
---@class ioctl_unit
|
||||||
local entry = {
|
local entry = {
|
||||||
unit_id = i,
|
unit_id = i,
|
||||||
|
connected = false,
|
||||||
|
rtu_hw = { boilers = {}, turbines = {} },
|
||||||
|
|
||||||
num_boilers = 0,
|
num_boilers = 0,
|
||||||
num_turbines = 0,
|
num_turbines = 0,
|
||||||
@ -243,6 +180,9 @@ function iocontrol.init(conf, comms, temp_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,
|
||||||
|
|
||||||
|
last_rate_change_ms = 0,
|
||||||
|
turbine_flow_stable = false,
|
||||||
|
|
||||||
-- auto control group
|
-- auto control group
|
||||||
a_group = 0,
|
a_group = 0,
|
||||||
|
|
||||||
@ -279,18 +219,18 @@ function iocontrol.init(conf, comms, temp_scale)
|
|||||||
|
|
||||||
---@type alarms
|
---@type alarms
|
||||||
alarms = {
|
alarms = {
|
||||||
ALARM_STATE.INACTIVE, -- containment breach
|
ALARM_STATE.INACTIVE, -- containment breach
|
||||||
ALARM_STATE.INACTIVE, -- containment radiation
|
ALARM_STATE.INACTIVE, -- containment radiation
|
||||||
ALARM_STATE.INACTIVE, -- reactor lost
|
ALARM_STATE.INACTIVE, -- reactor lost
|
||||||
ALARM_STATE.INACTIVE, -- damage critical
|
ALARM_STATE.INACTIVE, -- damage critical
|
||||||
ALARM_STATE.INACTIVE, -- reactor taking damage
|
ALARM_STATE.INACTIVE, -- reactor taking damage
|
||||||
ALARM_STATE.INACTIVE, -- reactor over temperature
|
ALARM_STATE.INACTIVE, -- reactor over temperature
|
||||||
ALARM_STATE.INACTIVE, -- reactor high temperature
|
ALARM_STATE.INACTIVE, -- reactor high temperature
|
||||||
ALARM_STATE.INACTIVE, -- waste leak
|
ALARM_STATE.INACTIVE, -- waste leak
|
||||||
ALARM_STATE.INACTIVE, -- waste level high
|
ALARM_STATE.INACTIVE, -- waste level high
|
||||||
ALARM_STATE.INACTIVE, -- RPS transient
|
ALARM_STATE.INACTIVE, -- RPS transient
|
||||||
ALARM_STATE.INACTIVE, -- RCS transient
|
ALARM_STATE.INACTIVE, -- RCS transient
|
||||||
ALARM_STATE.INACTIVE -- turbine trip
|
ALARM_STATE.INACTIVE -- turbine trip
|
||||||
},
|
},
|
||||||
|
|
||||||
annunciator = {}, ---@type annunciator
|
annunciator = {}, ---@type annunciator
|
||||||
@ -317,12 +257,14 @@ function iocontrol.init(conf, comms, temp_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
|
||||||
@ -376,6 +318,13 @@ function iocontrol.fp_monitor_state(id, connected)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- report thread (routine) statuses
|
||||||
|
---@param thread string thread name
|
||||||
|
---@param ok boolean thread state
|
||||||
|
function iocontrol.fp_rt_status(thread, ok)
|
||||||
|
io.fp.ps.publish(util.c("routine__", thread), ok)
|
||||||
|
end
|
||||||
|
|
||||||
-- report PKT firmware version and PKT session connection state
|
-- report PKT firmware version and PKT session connection state
|
||||||
---@param session_id integer PKT session
|
---@param session_id integer PKT session
|
||||||
---@param fw string firmware version
|
---@param fw string firmware version
|
||||||
@ -585,7 +534,7 @@ function iocontrol.update_facility_status(status)
|
|||||||
|
|
||||||
local ctl_status = status[1]
|
local ctl_status = status[1]
|
||||||
|
|
||||||
if type(ctl_status) == "table" and #ctl_status == 16 then
|
if type(ctl_status) == "table" and #ctl_status == 17 then
|
||||||
fac.all_sys_ok = ctl_status[1]
|
fac.all_sys_ok = ctl_status[1]
|
||||||
fac.auto_ready = ctl_status[2]
|
fac.auto_ready = ctl_status[2]
|
||||||
|
|
||||||
@ -636,9 +585,11 @@ function iocontrol.update_facility_status(status)
|
|||||||
|
|
||||||
fac.auto_current_waste_product = ctl_status[15]
|
fac.auto_current_waste_product = ctl_status[15]
|
||||||
fac.auto_pu_fallback_active = ctl_status[16]
|
fac.auto_pu_fallback_active = ctl_status[16]
|
||||||
|
fac.auto_sps_disabled = ctl_status[17]
|
||||||
|
|
||||||
fac.ps.publish("current_waste_product", fac.auto_current_waste_product)
|
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("pu_fallback_active", fac.auto_pu_fallback_active)
|
||||||
|
fac.ps.publish("sps_disabled_low_power", fac.auto_sps_disabled)
|
||||||
else
|
else
|
||||||
log.debug(log_header .. "control status not a table or length mismatch")
|
log.debug(log_header .. "control status not a table or length mismatch")
|
||||||
valid = false
|
valid = false
|
||||||
@ -655,10 +606,27 @@ function iocontrol.update_facility_status(status)
|
|||||||
fac.rtu_count = rtu_statuses.count
|
fac.rtu_count = rtu_statuses.count
|
||||||
|
|
||||||
-- power statistics
|
-- power statistics
|
||||||
if type(rtu_statuses.power) == "table" then
|
if type(rtu_statuses.power) == "table" and #rtu_statuses.power == 4 then
|
||||||
fac.induction_ps_tbl[1].publish("avg_charge", rtu_statuses.power[1])
|
local data = fac.induction_data_tbl[1] ---@type imatrix_session_db
|
||||||
fac.induction_ps_tbl[1].publish("avg_inflow", rtu_statuses.power[2])
|
local ps = fac.induction_ps_tbl[1] ---@type psil
|
||||||
fac.induction_ps_tbl[1].publish("avg_outflow", rtu_statuses.power[3])
|
|
||||||
|
local chg = tonumber(rtu_statuses.power[1])
|
||||||
|
local in_f = tonumber(rtu_statuses.power[2])
|
||||||
|
local out_f = tonumber(rtu_statuses.power[3])
|
||||||
|
local eta = tonumber(rtu_statuses.power[4])
|
||||||
|
|
||||||
|
ps.publish("avg_charge", chg)
|
||||||
|
ps.publish("avg_inflow", in_f)
|
||||||
|
ps.publish("avg_outflow", out_f)
|
||||||
|
ps.publish("eta_ms", eta)
|
||||||
|
|
||||||
|
ps.publish("is_charging", in_f > out_f)
|
||||||
|
ps.publish("is_discharging", out_f > in_f)
|
||||||
|
|
||||||
|
if data and data.build then
|
||||||
|
local cap = util.joules_to_fe_rf(data.build.transfer_cap)
|
||||||
|
ps.publish("at_max_io", in_f >= cap or out_f >= cap)
|
||||||
|
end
|
||||||
else
|
else
|
||||||
log.debug(log_header .. "power statistics list not a table")
|
log.debug(log_header .. "power statistics list not a table")
|
||||||
valid = false
|
valid = false
|
||||||
@ -869,6 +837,7 @@ function iocontrol.update_unit_statuses(statuses)
|
|||||||
end
|
end
|
||||||
|
|
||||||
if #reactor_status == 0 then
|
if #reactor_status == 0 then
|
||||||
|
unit.connected = false
|
||||||
unit.unit_ps.publish("computed_status", 1) -- disconnected
|
unit.unit_ps.publish("computed_status", 1) -- disconnected
|
||||||
elseif #reactor_status == 3 then
|
elseif #reactor_status == 3 then
|
||||||
local mek_status = reactor_status[1]
|
local mek_status = reactor_status[1]
|
||||||
@ -928,6 +897,8 @@ function iocontrol.update_unit_statuses(statuses)
|
|||||||
unit.unit_ps.publish(key, val)
|
unit.unit_ps.publish(key, val)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
unit.connected = true
|
||||||
else
|
else
|
||||||
log.debug(log_header .. "reactor status length mismatch")
|
log.debug(log_header .. "reactor status length mismatch")
|
||||||
valid = false
|
valid = false
|
||||||
@ -942,7 +913,10 @@ function iocontrol.update_unit_statuses(statuses)
|
|||||||
local boil_sum = 0
|
local boil_sum = 0
|
||||||
|
|
||||||
for id = 1, #unit.boiler_ps_tbl do
|
for id = 1, #unit.boiler_ps_tbl do
|
||||||
if rtu_statuses.boilers[id] == nil then
|
local connected = rtu_statuses.boilers[id] ~= nil
|
||||||
|
unit.rtu_hw.boilers[id].connected = connected
|
||||||
|
|
||||||
|
if not connected then
|
||||||
-- disconnected
|
-- disconnected
|
||||||
unit.boiler_ps_tbl[id].publish("computed_status", 1)
|
unit.boiler_ps_tbl[id].publish("computed_status", 1)
|
||||||
end
|
end
|
||||||
@ -954,6 +928,7 @@ function iocontrol.update_unit_statuses(statuses)
|
|||||||
local ps = unit.boiler_ps_tbl[id] ---@type psil
|
local ps = unit.boiler_ps_tbl[id] ---@type psil
|
||||||
|
|
||||||
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
|
ps.publish("computed_status", 3) -- faulted
|
||||||
@ -985,7 +960,10 @@ function iocontrol.update_unit_statuses(statuses)
|
|||||||
local flow_sum = 0
|
local flow_sum = 0
|
||||||
|
|
||||||
for id = 1, #unit.turbine_ps_tbl do
|
for id = 1, #unit.turbine_ps_tbl do
|
||||||
if rtu_statuses.turbines[id] == nil then
|
local connected = rtu_statuses.turbines[id] ~= nil
|
||||||
|
unit.rtu_hw.turbines[id].connected = connected
|
||||||
|
|
||||||
|
if not connected then
|
||||||
-- disconnected
|
-- disconnected
|
||||||
unit.turbine_ps_tbl[id].publish("computed_status", 1)
|
unit.turbine_ps_tbl[id].publish("computed_status", 1)
|
||||||
end
|
end
|
||||||
@ -997,6 +975,7 @@ function iocontrol.update_unit_statuses(statuses)
|
|||||||
local ps = unit.turbine_ps_tbl[id] ---@type psil
|
local ps = unit.turbine_ps_tbl[id] ---@type psil
|
||||||
|
|
||||||
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
|
ps.publish("computed_status", 3) -- faulted
|
||||||
@ -1171,9 +1150,11 @@ function iocontrol.update_unit_statuses(statuses)
|
|||||||
local unit_state = status[5]
|
local unit_state = status[5]
|
||||||
|
|
||||||
if type(unit_state) == "table" then
|
if type(unit_state) == "table" then
|
||||||
if #unit_state == 6 then
|
if #unit_state == 8 then
|
||||||
unit.waste_mode = unit_state[5]
|
unit.waste_mode = unit_state[5]
|
||||||
unit.waste_product = unit_state[6]
|
unit.waste_product = unit_state[6]
|
||||||
|
unit.last_rate_change_ms = unit_state[7]
|
||||||
|
unit.turbine_flow_stable = unit_state[8]
|
||||||
|
|
||||||
unit.unit_ps.publish("U_StatusLine1", unit_state[1])
|
unit.unit_ps.publish("U_StatusLine1", unit_state[1])
|
||||||
unit.unit_ps.publish("U_StatusLine2", unit_state[2])
|
unit.unit_ps.publish("U_StatusLine2", unit_state[2])
|
||||||
|
@ -29,7 +29,8 @@ local self = {
|
|||||||
gen_target = 0.0,
|
gen_target = 0.0,
|
||||||
limits = {},
|
limits = {},
|
||||||
waste_product = PRODUCT.PLUTONIUM,
|
waste_product = PRODUCT.PLUTONIUM,
|
||||||
pu_fallback = false
|
pu_fallback = false,
|
||||||
|
sps_low_power = false
|
||||||
},
|
},
|
||||||
waste_modes = {},
|
waste_modes = {},
|
||||||
priority_groups = {}
|
priority_groups = {}
|
||||||
@ -65,13 +66,15 @@ function process.init(iocontrol, coord_comms)
|
|||||||
ctl_proc.limits = config.limits
|
ctl_proc.limits = config.limits
|
||||||
ctl_proc.waste_product = config.waste_product
|
ctl_proc.waste_product = config.waste_product
|
||||||
ctl_proc.pu_fallback = config.pu_fallback
|
ctl_proc.pu_fallback = config.pu_fallback
|
||||||
|
ctl_proc.sps_low_power = config.sps_low_power
|
||||||
|
|
||||||
self.io.facility.ps.publish("process_mode", ctl_proc.mode)
|
self.io.facility.ps.publish("process_mode", ctl_proc.mode)
|
||||||
self.io.facility.ps.publish("process_burn_target", ctl_proc.burn_target)
|
self.io.facility.ps.publish("process_burn_target", ctl_proc.burn_target)
|
||||||
self.io.facility.ps.publish("process_charge_target", ctl_proc.charge_target)
|
self.io.facility.ps.publish("process_charge_target", self.io.energy_convert_from_fe(ctl_proc.charge_target))
|
||||||
self.io.facility.ps.publish("process_gen_target", ctl_proc.gen_target)
|
self.io.facility.ps.publish("process_gen_target", self.io.energy_convert_from_fe(ctl_proc.gen_target))
|
||||||
self.io.facility.ps.publish("process_waste_product", ctl_proc.waste_product)
|
self.io.facility.ps.publish("process_waste_product", ctl_proc.waste_product)
|
||||||
self.io.facility.ps.publish("process_pu_fallback", ctl_proc.pu_fallback)
|
self.io.facility.ps.publish("process_pu_fallback", ctl_proc.pu_fallback)
|
||||||
|
self.io.facility.ps.publish("process_sps_low_power", ctl_proc.sps_low_power)
|
||||||
|
|
||||||
for id = 1, math.min(#ctl_proc.limits, self.io.facility.num_units) do
|
for id = 1, math.min(#ctl_proc.limits, self.io.facility.num_units) do
|
||||||
local unit = self.io.units[id] ---@type ioctl_unit
|
local unit = self.io.units[id] ---@type ioctl_unit
|
||||||
@ -83,6 +86,7 @@ function process.init(iocontrol, coord_comms)
|
|||||||
-- notify supervisor of auto waste config
|
-- notify supervisor of auto waste config
|
||||||
self.comms.send_fac_command(FAC_COMMAND.SET_WASTE_MODE, ctl_proc.waste_product)
|
self.comms.send_fac_command(FAC_COMMAND.SET_WASTE_MODE, ctl_proc.waste_product)
|
||||||
self.comms.send_fac_command(FAC_COMMAND.SET_PU_FB, ctl_proc.pu_fallback)
|
self.comms.send_fac_command(FAC_COMMAND.SET_PU_FB, ctl_proc.pu_fallback)
|
||||||
|
self.comms.send_fac_command(FAC_COMMAND.SET_SPS_LP, ctl_proc.sps_low_power)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- unit waste states
|
-- unit waste states
|
||||||
@ -259,6 +263,18 @@ function process.set_pu_fallback(enabled)
|
|||||||
_write_auto_config()
|
_write_auto_config()
|
||||||
end
|
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(FAC_COMMAND.SET_SPS_LP, enabled)
|
||||||
|
|
||||||
|
log.debug(util.c("PROCESS: SET SPS LOW POWER ", enabled))
|
||||||
|
|
||||||
|
-- update config table and save
|
||||||
|
self.control_states.process.sps_low_power = enabled
|
||||||
|
_write_auto_config()
|
||||||
|
end
|
||||||
|
|
||||||
-- save process control settings
|
-- save process control settings
|
||||||
---@param mode PROCESS control mode
|
---@param mode PROCESS control mode
|
||||||
---@param burn_target number burn rate target
|
---@param burn_target number burn rate target
|
||||||
@ -300,8 +316,8 @@ function process.start_ack_handle(response)
|
|||||||
|
|
||||||
self.io.facility.ps.publish("process_mode", ctl_proc.mode)
|
self.io.facility.ps.publish("process_mode", ctl_proc.mode)
|
||||||
self.io.facility.ps.publish("process_burn_target", ctl_proc.burn_target)
|
self.io.facility.ps.publish("process_burn_target", ctl_proc.burn_target)
|
||||||
self.io.facility.ps.publish("process_charge_target", ctl_proc.charge_target)
|
self.io.facility.ps.publish("process_charge_target", self.io.energy_convert_from_fe(ctl_proc.charge_target))
|
||||||
self.io.facility.ps.publish("process_gen_target", ctl_proc.gen_target)
|
self.io.facility.ps.publish("process_gen_target", self.io.energy_convert_from_fe(ctl_proc.gen_target))
|
||||||
|
|
||||||
self.io.facility.start_ack(ack)
|
self.io.facility.start_ack(ack)
|
||||||
end
|
end
|
||||||
|
@ -2,22 +2,26 @@
|
|||||||
-- Graphics Rendering Control
|
-- Graphics Rendering Control
|
||||||
--
|
--
|
||||||
|
|
||||||
local log = require("scada-common.log")
|
local log = require("scada-common.log")
|
||||||
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
local iocontrol = require("coordinator.iocontrol")
|
local coordinator = require("coordinator.coordinator")
|
||||||
|
local iocontrol = require("coordinator.iocontrol")
|
||||||
|
|
||||||
local style = require("coordinator.ui.style")
|
local style = require("coordinator.ui.style")
|
||||||
local pgi = require("coordinator.ui.pgi")
|
local pgi = require("coordinator.ui.pgi")
|
||||||
|
|
||||||
local flow_view = require("coordinator.ui.layout.flow_view")
|
local flow_view = require("coordinator.ui.layout.flow_view")
|
||||||
local panel_view = require("coordinator.ui.layout.front_panel")
|
local panel_view = require("coordinator.ui.layout.front_panel")
|
||||||
local main_view = require("coordinator.ui.layout.main_view")
|
local main_view = require("coordinator.ui.layout.main_view")
|
||||||
local unit_view = require("coordinator.ui.layout.unit_view")
|
local unit_view = require("coordinator.ui.layout.unit_view")
|
||||||
|
|
||||||
local core = require("graphics.core")
|
local core = require("graphics.core")
|
||||||
local flasher = require("graphics.flasher")
|
local flasher = require("graphics.flasher")
|
||||||
|
|
||||||
local DisplayBox = require("graphics.elements.displaybox")
|
local DisplayBox = require("graphics.elements.displaybox")
|
||||||
|
|
||||||
|
local log_render = coordinator.log_render
|
||||||
|
|
||||||
---@class coord_renderer
|
---@class coord_renderer
|
||||||
local renderer = {}
|
local renderer = {}
|
||||||
@ -195,18 +199,21 @@ function renderer.try_start_ui()
|
|||||||
if engine.monitors.main ~= nil then
|
if engine.monitors.main ~= nil then
|
||||||
engine.ui.main_display = DisplayBox{window=engine.monitors.main,fg_bg=style.root}
|
engine.ui.main_display = DisplayBox{window=engine.monitors.main,fg_bg=style.root}
|
||||||
main_view(engine.ui.main_display)
|
main_view(engine.ui.main_display)
|
||||||
|
util.nop()
|
||||||
end
|
end
|
||||||
|
|
||||||
-- show flow view on flow monitor
|
-- show flow view on flow monitor
|
||||||
if engine.monitors.flow ~= nil then
|
if engine.monitors.flow ~= nil then
|
||||||
engine.ui.flow_display = DisplayBox{window=engine.monitors.flow,fg_bg=style.root}
|
engine.ui.flow_display = DisplayBox{window=engine.monitors.flow,fg_bg=style.root}
|
||||||
flow_view(engine.ui.flow_display)
|
flow_view(engine.ui.flow_display)
|
||||||
|
util.nop()
|
||||||
end
|
end
|
||||||
|
|
||||||
-- show unit views on unit displays
|
-- show unit views on unit displays
|
||||||
for idx, display in pairs(engine.monitors.unit_displays) do
|
for idx, display in pairs(engine.monitors.unit_displays) do
|
||||||
engine.ui.unit_displays[idx] = DisplayBox{window=display,fg_bg=style.root}
|
engine.ui.unit_displays[idx] = DisplayBox{window=display,fg_bg=style.root}
|
||||||
unit_view(engine.ui.unit_displays[idx], idx)
|
unit_view(engine.ui.unit_displays[idx], idx)
|
||||||
|
util.nop()
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
@ -247,6 +254,11 @@ function renderer.close_ui()
|
|||||||
-- clear unit monitors
|
-- clear unit monitors
|
||||||
for _, monitor in ipairs(engine.monitors.unit_displays) do monitor.clear() end
|
for _, monitor in ipairs(engine.monitors.unit_displays) do monitor.clear() end
|
||||||
|
|
||||||
|
if not engine.disable_flow_view then
|
||||||
|
-- clear flow monitor
|
||||||
|
engine.monitors.flow.clear()
|
||||||
|
end
|
||||||
|
|
||||||
-- re-draw dmesg
|
-- re-draw dmesg
|
||||||
engine.dmesg_window.setVisible(true)
|
engine.dmesg_window.setVisible(true)
|
||||||
engine.dmesg_window.redraw()
|
engine.dmesg_window.redraw()
|
||||||
@ -383,12 +395,15 @@ function renderer.handle_resize(name)
|
|||||||
engine.dmesg_window.setVisible(not engine.ui_ready)
|
engine.dmesg_window.setVisible(not engine.ui_ready)
|
||||||
|
|
||||||
if engine.ui_ready then
|
if engine.ui_ready then
|
||||||
|
local draw_start = util.time_ms()
|
||||||
local ok = pcall(function ()
|
local ok = pcall(function ()
|
||||||
ui.main_display = DisplayBox{window=device,fg_bg=style.root}
|
ui.main_display = DisplayBox{window=device,fg_bg=style.root}
|
||||||
main_view(ui.main_display)
|
main_view(ui.main_display)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
if not ok then
|
if ok then
|
||||||
|
log_render("main view re-draw completed in " .. (util.time_ms() - draw_start) .. "ms")
|
||||||
|
else
|
||||||
if ui.main_display then
|
if ui.main_display then
|
||||||
ui.main_display.delete()
|
ui.main_display.delete()
|
||||||
ui.main_display = nil
|
ui.main_display = nil
|
||||||
@ -416,14 +431,15 @@ function renderer.handle_resize(name)
|
|||||||
iocontrol.fp_monitor_state("flow", true)
|
iocontrol.fp_monitor_state("flow", true)
|
||||||
|
|
||||||
if engine.ui_ready then
|
if engine.ui_ready then
|
||||||
engine.dmesg_window.setVisible(false)
|
local draw_start = util.time_ms()
|
||||||
|
|
||||||
local ok = pcall(function ()
|
local ok = pcall(function ()
|
||||||
ui.flow_display = DisplayBox{window=device,fg_bg=style.root}
|
ui.flow_display = DisplayBox{window=device,fg_bg=style.root}
|
||||||
flow_view(ui.flow_display)
|
flow_view(ui.flow_display)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
if not ok then
|
if ok then
|
||||||
|
log_render("flow view re-draw completed in " .. (util.time_ms() - draw_start) .. "ms")
|
||||||
|
else
|
||||||
if ui.flow_display then
|
if ui.flow_display then
|
||||||
ui.flow_display.delete()
|
ui.flow_display.delete()
|
||||||
ui.flow_display = nil
|
ui.flow_display = nil
|
||||||
@ -453,14 +469,15 @@ function renderer.handle_resize(name)
|
|||||||
iocontrol.fp_monitor_state(idx, true)
|
iocontrol.fp_monitor_state(idx, true)
|
||||||
|
|
||||||
if engine.ui_ready then
|
if engine.ui_ready then
|
||||||
engine.dmesg_window.setVisible(false)
|
local draw_start = util.time_ms()
|
||||||
|
|
||||||
local ok = pcall(function ()
|
local ok = pcall(function ()
|
||||||
ui.unit_displays[idx] = DisplayBox{window=device,fg_bg=style.root}
|
ui.unit_displays[idx] = DisplayBox{window=device,fg_bg=style.root}
|
||||||
unit_view(ui.unit_displays[idx], idx)
|
unit_view(ui.unit_displays[idx], idx)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
if not ok then
|
if ok then
|
||||||
|
log_render("unit " .. idx .. " view re-draw completed in " .. (util.time_ms() - draw_start) .. "ms")
|
||||||
|
else
|
||||||
if ui.unit_displays[idx] then
|
if ui.unit_displays[idx] then
|
||||||
ui.unit_displays[idx].delete()
|
ui.unit_displays[idx].delete()
|
||||||
ui.unit_displays[idx] = nil
|
ui.unit_displays[idx] = nil
|
||||||
|
@ -89,10 +89,11 @@ end
|
|||||||
|
|
||||||
-- establish a new API session
|
-- establish a new API session
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
---@param source_addr integer
|
---@param source_addr integer pocket computer ID
|
||||||
---@param version string
|
---@param i_seq_num integer initial (most recent) sequence number
|
||||||
|
---@param version string pocket version
|
||||||
---@return integer session_id
|
---@return integer session_id
|
||||||
function apisessions.establish_session(source_addr, version)
|
function apisessions.establish_session(source_addr, i_seq_num, version)
|
||||||
---@class pkt_session_struct
|
---@class pkt_session_struct
|
||||||
local pkt_s = {
|
local pkt_s = {
|
||||||
open = true,
|
open = true,
|
||||||
@ -105,7 +106,7 @@ function apisessions.establish_session(source_addr, version)
|
|||||||
|
|
||||||
local id = self.next_id
|
local id = self.next_id
|
||||||
|
|
||||||
pkt_s.instance = pocket.new_session(id, source_addr, pkt_s.in_queue, pkt_s.out_queue, self.config.API_Timeout)
|
pkt_s.instance = pocket.new_session(id, source_addr, i_seq_num, pkt_s.in_queue, pkt_s.out_queue, self.config.API_Timeout)
|
||||||
table.insert(self.sessions, pkt_s)
|
table.insert(self.sessions, pkt_s)
|
||||||
|
|
||||||
local mt = {
|
local mt = {
|
||||||
|
@ -8,7 +8,7 @@ local iocontrol = require("coordinator.iocontrol")
|
|||||||
local pocket = {}
|
local pocket = {}
|
||||||
|
|
||||||
local PROTOCOL = comms.PROTOCOL
|
local PROTOCOL = comms.PROTOCOL
|
||||||
-- local CRDN_TYPE = comms.CRDN_TYPE
|
local CRDN_TYPE = comms.CRDN_TYPE
|
||||||
local MGMT_TYPE = comms.MGMT_TYPE
|
local MGMT_TYPE = comms.MGMT_TYPE
|
||||||
|
|
||||||
-- retry time constants in ms
|
-- retry time constants in ms
|
||||||
@ -32,16 +32,17 @@ local PERIODICS = {
|
|||||||
---@nodiscard
|
---@nodiscard
|
||||||
---@param id integer session ID
|
---@param id integer session ID
|
||||||
---@param s_addr integer device source address
|
---@param s_addr integer device source address
|
||||||
|
---@param i_seq_num integer initial sequence number
|
||||||
---@param in_queue mqueue in message queue
|
---@param in_queue mqueue in message queue
|
||||||
---@param out_queue mqueue out message queue
|
---@param out_queue mqueue out message queue
|
||||||
---@param timeout number communications timeout
|
---@param timeout number communications timeout
|
||||||
function pocket.new_session(id, s_addr, in_queue, out_queue, timeout)
|
function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout)
|
||||||
local log_header = "pkt_session(" .. id .. "): "
|
local log_header = "pkt_session(" .. id .. "): "
|
||||||
|
|
||||||
local self = {
|
local self = {
|
||||||
-- connection properties
|
-- connection properties
|
||||||
seq_num = 0,
|
seq_num = i_seq_num + 2, -- next after the establish approval was sent
|
||||||
r_seq_num = nil,
|
r_seq_num = i_seq_num + 1,
|
||||||
connected = true,
|
connected = true,
|
||||||
conn_watchdog = util.new_watchdog(timeout),
|
conn_watchdog = util.new_watchdog(timeout),
|
||||||
last_rtt = 0,
|
last_rtt = 0,
|
||||||
@ -73,18 +74,18 @@ function pocket.new_session(id, s_addr, in_queue, out_queue, timeout)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- send a CRDN packet
|
-- send a CRDN packet
|
||||||
-----@param msg_type CRDN_TYPE
|
---@param msg_type CRDN_TYPE
|
||||||
-----@param msg table
|
---@param msg table
|
||||||
-- local function _send(msg_type, msg)
|
local function _send(msg_type, msg)
|
||||||
-- local s_pkt = comms.scada_packet()
|
local s_pkt = comms.scada_packet()
|
||||||
-- local c_pkt = comms.crdn_packet()
|
local c_pkt = comms.crdn_packet()
|
||||||
|
|
||||||
-- c_pkt.make(msg_type, msg)
|
c_pkt.make(msg_type, msg)
|
||||||
-- s_pkt.make(self.seq_num, PROTOCOL.SCADA_CRDN, c_pkt.raw_sendable())
|
s_pkt.make(s_addr, self.seq_num, PROTOCOL.SCADA_CRDN, c_pkt.raw_sendable())
|
||||||
|
|
||||||
-- out_queue.push_packet(s_pkt)
|
out_queue.push_packet(s_pkt)
|
||||||
-- self.seq_num = self.seq_num + 1
|
self.seq_num = self.seq_num + 1
|
||||||
-- end
|
end
|
||||||
|
|
||||||
-- send a SCADA management packet
|
-- send a SCADA management packet
|
||||||
---@param msg_type MGMT_TYPE
|
---@param msg_type MGMT_TYPE
|
||||||
@ -104,13 +105,11 @@ function pocket.new_session(id, s_addr, in_queue, out_queue, timeout)
|
|||||||
---@param pkt mgmt_frame|crdn_frame
|
---@param pkt mgmt_frame|crdn_frame
|
||||||
local function _handle_packet(pkt)
|
local function _handle_packet(pkt)
|
||||||
-- check sequence number
|
-- check sequence number
|
||||||
if self.r_seq_num == nil then
|
if self.r_seq_num ~= pkt.scada_frame.seq_num() then
|
||||||
self.r_seq_num = pkt.scada_frame.seq_num()
|
log.warning(log_header .. "sequence out-of-order: next = " .. self.r_seq_num .. ", new = " .. pkt.scada_frame.seq_num())
|
||||||
elseif (self.r_seq_num + 1) ~= pkt.scada_frame.seq_num() then
|
|
||||||
log.warning(log_header .. "sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. pkt.scada_frame.seq_num())
|
|
||||||
return
|
return
|
||||||
else
|
else
|
||||||
self.r_seq_num = pkt.scada_frame.seq_num()
|
self.r_seq_num = pkt.scada_frame.seq_num() + 1
|
||||||
end
|
end
|
||||||
|
|
||||||
-- feed watchdog
|
-- feed watchdog
|
||||||
@ -120,8 +119,46 @@ function pocket.new_session(id, s_addr, in_queue, out_queue, timeout)
|
|||||||
if pkt.scada_frame.protocol() == PROTOCOL.SCADA_CRDN then
|
if pkt.scada_frame.protocol() == PROTOCOL.SCADA_CRDN then
|
||||||
---@cast pkt crdn_frame
|
---@cast pkt crdn_frame
|
||||||
|
|
||||||
|
local db = iocontrol.get_db()
|
||||||
|
|
||||||
-- handle packet by type
|
-- handle packet by type
|
||||||
if pkt.type == nil then
|
if pkt.type == CRDN_TYPE.API_GET_FAC then
|
||||||
|
local fac = db.facility
|
||||||
|
|
||||||
|
local data = {
|
||||||
|
fac.all_sys_ok,
|
||||||
|
fac.rtu_count,
|
||||||
|
fac.radiation,
|
||||||
|
{ fac.auto_ready, fac.auto_active, fac.auto_ramping, fac.auto_saturated },
|
||||||
|
{ fac.auto_current_waste_product, fac.auto_pu_fallback_active },
|
||||||
|
util.table_len(fac.tank_data_tbl),
|
||||||
|
fac.induction_data_tbl[1] ~= nil,
|
||||||
|
fac.sps_data_tbl[1] ~= nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
_send(CRDN_TYPE.API_GET_FAC, data)
|
||||||
|
elseif pkt.type == CRDN_TYPE.API_GET_UNIT then
|
||||||
|
if pkt.length == 1 and type(pkt.data[1]) == "number" then
|
||||||
|
local u = db.units[pkt.data[1]] ---@type ioctl_unit
|
||||||
|
|
||||||
|
if u then
|
||||||
|
local data = {
|
||||||
|
u.unit_id,
|
||||||
|
u.connected,
|
||||||
|
u.rtu_hw,
|
||||||
|
u.alarms,
|
||||||
|
u.annunciator,
|
||||||
|
u.reactor_data,
|
||||||
|
u.boiler_data_tbl,
|
||||||
|
u.turbine_data_tbl,
|
||||||
|
u.tank_data_tbl,
|
||||||
|
u.last_rate_change_ms,
|
||||||
|
u.turbine_flow_stable
|
||||||
|
}
|
||||||
|
|
||||||
|
_send(CRDN_TYPE.API_GET_UNIT, data)
|
||||||
|
end
|
||||||
|
end
|
||||||
else
|
else
|
||||||
log.debug(log_header .. "handler received unsupported CRDN packet type " .. pkt.type)
|
log.debug(log_header .. "handler received unsupported CRDN packet type " .. pkt.type)
|
||||||
end
|
end
|
||||||
@ -149,6 +186,10 @@ function pocket.new_session(id, s_addr, in_queue, out_queue, timeout)
|
|||||||
elseif pkt.type == MGMT_TYPE.CLOSE then
|
elseif pkt.type == MGMT_TYPE.CLOSE then
|
||||||
-- close the session
|
-- close the session
|
||||||
_close()
|
_close()
|
||||||
|
elseif pkt.type == MGMT_TYPE.ESTABLISH then
|
||||||
|
-- something is wrong, kill the session
|
||||||
|
_close()
|
||||||
|
log.warning(log_header .. "terminated session due to an unexpected ESTABLISH packet")
|
||||||
else
|
else
|
||||||
log.debug(log_header .. "handler received unsupported SCADA_MGMT packet type " .. pkt.type)
|
log.debug(log_header .. "handler received unsupported SCADA_MGMT packet type " .. pkt.type)
|
||||||
end
|
end
|
||||||
|
@ -7,32 +7,29 @@ require("/initenv").init_env()
|
|||||||
local comms = require("scada-common.comms")
|
local comms = require("scada-common.comms")
|
||||||
local crash = require("scada-common.crash")
|
local crash = require("scada-common.crash")
|
||||||
local log = require("scada-common.log")
|
local log = require("scada-common.log")
|
||||||
|
local mqueue = require("scada-common.mqueue")
|
||||||
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 util = require("scada-common.util")
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
local core = require("graphics.core")
|
|
||||||
|
|
||||||
local configure = require("coordinator.configure")
|
local configure = require("coordinator.configure")
|
||||||
local coordinator = require("coordinator.coordinator")
|
local coordinator = require("coordinator.coordinator")
|
||||||
local iocontrol = require("coordinator.iocontrol")
|
local iocontrol = require("coordinator.iocontrol")
|
||||||
local renderer = require("coordinator.renderer")
|
local renderer = require("coordinator.renderer")
|
||||||
local sounder = require("coordinator.sounder")
|
local sounder = require("coordinator.sounder")
|
||||||
|
local threads = require("coordinator.threads")
|
||||||
|
|
||||||
local apisessions = require("coordinator.session.apisessions")
|
local COORDINATOR_VERSION = "v1.5.6"
|
||||||
|
|
||||||
local COORDINATOR_VERSION = "v1.3.0"
|
|
||||||
|
|
||||||
local CHUNK_LOAD_DELAY_S = 30.0
|
local CHUNK_LOAD_DELAY_S = 30.0
|
||||||
|
|
||||||
local println = util.println
|
local println = util.println
|
||||||
local println_ts = util.println_ts
|
local println_ts = util.println_ts
|
||||||
|
|
||||||
local log_graphics = coordinator.log_graphics
|
local log_render = coordinator.log_render
|
||||||
local log_sys = coordinator.log_sys
|
local log_sys = coordinator.log_sys
|
||||||
local log_boot = coordinator.log_boot
|
local log_boot = coordinator.log_boot
|
||||||
local log_comms = coordinator.log_comms
|
local log_comms = coordinator.log_comms
|
||||||
local log_crypto = coordinator.log_crypto
|
local log_crypto = coordinator.log_crypto
|
||||||
|
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
@ -103,6 +100,7 @@ log.info("========================================")
|
|||||||
println(">> SCADA Coordinator " .. COORDINATOR_VERSION .. " <<")
|
println(">> SCADA Coordinator " .. COORDINATOR_VERSION .. " <<")
|
||||||
|
|
||||||
crash.set_env("coordinator", COORDINATOR_VERSION)
|
crash.set_env("coordinator", COORDINATOR_VERSION)
|
||||||
|
crash.dbg_log_env()
|
||||||
|
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
-- main application
|
-- main application
|
||||||
@ -128,16 +126,58 @@ local function main()
|
|||||||
-- lets get started!
|
-- lets get started!
|
||||||
log.info("monitors ready, dmesg output incoming...")
|
log.info("monitors ready, dmesg output incoming...")
|
||||||
|
|
||||||
log_graphics("displays connected and reset")
|
log_render("displays connected and reset")
|
||||||
log_sys("system start on " .. os.date("%c"))
|
log_sys("system start on " .. os.date("%c"))
|
||||||
log_boot("starting " .. COORDINATOR_VERSION)
|
log_boot("starting " .. COORDINATOR_VERSION)
|
||||||
|
|
||||||
|
----------------------------------------
|
||||||
|
-- memory allocation
|
||||||
|
----------------------------------------
|
||||||
|
|
||||||
|
-- shared memory across threads
|
||||||
|
---@class crd_shared_memory
|
||||||
|
local __shared_memory = {
|
||||||
|
-- time and date format for display
|
||||||
|
date_format = util.trinary(config.Time24Hour, "%X \x04 %A, %B %d %Y", "%r \x04 %A, %B %d %Y"),
|
||||||
|
|
||||||
|
-- coordinator system state flags
|
||||||
|
---@class crd_state
|
||||||
|
crd_state = {
|
||||||
|
fp_ok = false,
|
||||||
|
ui_ok = true, -- default true, used to abort on fail
|
||||||
|
link_fail = false,
|
||||||
|
shutdown = false
|
||||||
|
},
|
||||||
|
|
||||||
|
-- core coordinator devices
|
||||||
|
crd_dev = {
|
||||||
|
modem = ppm.get_wireless_modem(),
|
||||||
|
speaker = ppm.get_device("speaker")
|
||||||
|
},
|
||||||
|
|
||||||
|
-- system objects
|
||||||
|
crd_sys = {
|
||||||
|
nic = nil, ---@type nic
|
||||||
|
coord_comms = nil, ---@type coord_comms
|
||||||
|
conn_watchdog = nil ---@type watchdog
|
||||||
|
},
|
||||||
|
|
||||||
|
-- message queues
|
||||||
|
q = {
|
||||||
|
mq_render = mqueue.new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
local smem_dev = __shared_memory.crd_dev
|
||||||
|
local smem_sys = __shared_memory.crd_sys
|
||||||
|
|
||||||
|
local crd_state = __shared_memory.crd_state
|
||||||
|
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
-- setup alarm sounder subsystem
|
-- setup alarm sounder subsystem
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
|
|
||||||
local speaker = ppm.get_device("speaker")
|
if smem_dev.speaker == nil then
|
||||||
if speaker == nil then
|
|
||||||
log_boot("annunciator alarm speaker not found")
|
log_boot("annunciator alarm speaker not found")
|
||||||
println("startup> speaker not found")
|
println("startup> speaker not found")
|
||||||
log.fatal("no annunciator alarm speaker found")
|
log.fatal("no annunciator alarm speaker found")
|
||||||
@ -145,7 +185,7 @@ local function main()
|
|||||||
else
|
else
|
||||||
local sounder_start = util.time_ms()
|
local sounder_start = util.time_ms()
|
||||||
log_boot("annunciator alarm speaker connected")
|
log_boot("annunciator alarm speaker connected")
|
||||||
sounder.init(speaker, config.SpeakerVolume)
|
sounder.init(smem_dev.speaker, config.SpeakerVolume)
|
||||||
log_boot("tone generation took " .. (util.time_ms() - sounder_start) .. "ms")
|
log_boot("tone generation took " .. (util.time_ms() - sounder_start) .. "ms")
|
||||||
log_sys("annunciator alarm configured")
|
log_sys("annunciator alarm configured")
|
||||||
iocontrol.fp_has_speaker(true)
|
iocontrol.fp_has_speaker(true)
|
||||||
@ -162,8 +202,7 @@ local function main()
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- get the communications modem
|
-- get the communications modem
|
||||||
local modem = ppm.get_wireless_modem()
|
if smem_dev.modem == nil then
|
||||||
if modem == nil then
|
|
||||||
log_comms("wireless modem not found")
|
log_comms("wireless modem not found")
|
||||||
println("startup> wireless modem not found")
|
println("startup> wireless modem not found")
|
||||||
log.fatal("no wireless modem on startup")
|
log.fatal("no wireless modem on startup")
|
||||||
@ -174,243 +213,54 @@ local function main()
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- create connection watchdog
|
-- create connection watchdog
|
||||||
local conn_watchdog = util.new_watchdog(config.SVR_Timeout)
|
smem_sys.conn_watchdog = util.new_watchdog(config.SVR_Timeout)
|
||||||
conn_watchdog.cancel()
|
smem_sys.conn_watchdog.cancel()
|
||||||
log.debug("startup> conn watchdog created")
|
log.debug("startup> conn watchdog created")
|
||||||
|
|
||||||
-- create network interface then setup comms
|
-- create network interface then setup comms
|
||||||
local nic = network.nic(modem)
|
smem_sys.nic = network.nic(smem_dev.modem)
|
||||||
local coord_comms = coordinator.comms(COORDINATOR_VERSION, nic, conn_watchdog)
|
smem_sys.coord_comms = coordinator.comms(COORDINATOR_VERSION, smem_sys.nic, smem_sys.conn_watchdog)
|
||||||
log.debug("startup> comms init")
|
log.debug("startup> comms init")
|
||||||
log_comms("comms initialized")
|
log_comms("comms initialized")
|
||||||
|
|
||||||
-- base loop clock (2Hz, 10 ticks)
|
|
||||||
local MAIN_CLOCK = 0.5
|
|
||||||
local loop_clock = util.new_clock(MAIN_CLOCK)
|
|
||||||
|
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
-- start front panel & UI start function
|
-- start front panel
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
|
|
||||||
log_graphics("starting front panel UI...")
|
log_render("starting front panel UI...")
|
||||||
|
|
||||||
local fp_ok, fp_message = renderer.try_start_fp()
|
local fp_message
|
||||||
if not fp_ok then
|
crd_state.fp_ok, fp_message = renderer.try_start_fp()
|
||||||
log_graphics(util.c("front panel UI error: ", fp_message))
|
if not crd_state.fp_ok then
|
||||||
|
log_render(util.c("front panel UI error: ", fp_message))
|
||||||
println_ts("front panel UI creation failed")
|
println_ts("front panel UI creation failed")
|
||||||
log.fatal(util.c("front panel GUI render failed with error ", fp_message))
|
log.fatal(util.c("front panel GUI render failed with error ", fp_message))
|
||||||
return
|
return
|
||||||
else log_graphics("front panel ready") end
|
else log_render("front panel ready") end
|
||||||
|
|
||||||
-- start up the main UI
|
|
||||||
---@return boolean ui_ok started ok
|
|
||||||
local function start_main_ui()
|
|
||||||
log_graphics("starting main UI...")
|
|
||||||
|
|
||||||
local draw_start = util.time_ms()
|
|
||||||
|
|
||||||
local ui_ok, ui_message = renderer.try_start_ui()
|
|
||||||
if not ui_ok then
|
|
||||||
log_graphics(util.c("main UI error: ", ui_message))
|
|
||||||
log.fatal(util.c("main GUI render failed with error ", ui_message))
|
|
||||||
else
|
|
||||||
log_graphics("main UI draw took " .. (util.time_ms() - draw_start) .. "ms")
|
|
||||||
end
|
|
||||||
|
|
||||||
return ui_ok
|
|
||||||
end
|
|
||||||
|
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
-- main event loop
|
-- start system
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
|
|
||||||
local link_failed = false
|
-- init threads
|
||||||
local ui_ok = true
|
local main_thread = threads.thread__main(__shared_memory)
|
||||||
local date_format = util.trinary(config.Time24Hour, "%X \x04 %A, %B %d %Y", "%r \x04 %A, %B %d %Y")
|
local render_thread = threads.thread__render(__shared_memory)
|
||||||
|
|
||||||
-- start clock
|
log.info("startup> completed")
|
||||||
loop_clock.start()
|
|
||||||
|
|
||||||
log_sys("system started successfully")
|
-- run threads
|
||||||
|
parallel.waitForAll(main_thread.p_exec, render_thread.p_exec)
|
||||||
-- main event loop
|
|
||||||
while true do
|
|
||||||
local event, param1, param2, param3, param4, param5 = util.pull_event()
|
|
||||||
|
|
||||||
-- handle event
|
|
||||||
if event == "peripheral_detach" then
|
|
||||||
local type, device = ppm.handle_unmount(param1)
|
|
||||||
|
|
||||||
if type ~= nil and device ~= nil then
|
|
||||||
if type == "modem" then
|
|
||||||
-- we only really care if this is our wireless modem
|
|
||||||
-- if it is another modem, handle other peripheral losses separately
|
|
||||||
if nic.is_modem(device) then
|
|
||||||
nic.disconnect()
|
|
||||||
log_sys("comms modem disconnected")
|
|
||||||
|
|
||||||
local other_modem = ppm.get_wireless_modem()
|
|
||||||
if other_modem then
|
|
||||||
log_sys("found another wireless modem, using it for comms")
|
|
||||||
nic.connect(other_modem)
|
|
||||||
else
|
|
||||||
-- close out main UI
|
|
||||||
renderer.close_ui()
|
|
||||||
|
|
||||||
-- alert user to status
|
|
||||||
log_sys("awaiting comms modem reconnect...")
|
|
||||||
|
|
||||||
iocontrol.fp_has_modem(false)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
log_sys("non-comms modem disconnected")
|
|
||||||
end
|
|
||||||
elseif type == "monitor" then
|
|
||||||
if renderer.handle_disconnect(device) then
|
|
||||||
log_sys("lost a configured monitor")
|
|
||||||
else
|
|
||||||
log_sys("lost an unused monitor")
|
|
||||||
end
|
|
||||||
elseif type == "speaker" then
|
|
||||||
log_sys("lost alarm sounder speaker")
|
|
||||||
iocontrol.fp_has_speaker(false)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
elseif event == "peripheral" then
|
|
||||||
local type, device = ppm.mount(param1)
|
|
||||||
|
|
||||||
if type ~= nil and device ~= nil then
|
|
||||||
if type == "modem" then
|
|
||||||
if device.isWireless() and not nic.is_connected() then
|
|
||||||
-- reconnected modem
|
|
||||||
log_sys("comms modem reconnected")
|
|
||||||
nic.connect(device)
|
|
||||||
iocontrol.fp_has_modem(true)
|
|
||||||
elseif device.isWireless() then
|
|
||||||
log.info("unused wireless modem reconnected")
|
|
||||||
else
|
|
||||||
log_sys("wired modem reconnected")
|
|
||||||
end
|
|
||||||
elseif type == "monitor" then
|
|
||||||
if renderer.handle_reconnect(param1, device) then
|
|
||||||
log_sys(util.c("configured monitor ", param1, " reconnected"))
|
|
||||||
else
|
|
||||||
log_sys(util.c("unused monitor ", param1, " connected"))
|
|
||||||
end
|
|
||||||
elseif type == "speaker" then
|
|
||||||
log_sys("alarm sounder speaker reconnected")
|
|
||||||
sounder.reconnect(device)
|
|
||||||
iocontrol.fp_has_speaker(true)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
elseif event == "monitor_resize" then
|
|
||||||
local is_used, is_ok = renderer.handle_resize(param1)
|
|
||||||
if is_used then
|
|
||||||
log_sys(util.c("configured monitor ", param1, " resized, ", util.trinary(is_ok, "display still fits", "display no longer fits")))
|
|
||||||
end
|
|
||||||
elseif event == "timer" then
|
|
||||||
if loop_clock.is_clock(param1) then
|
|
||||||
-- main loop tick
|
|
||||||
|
|
||||||
-- toggle heartbeat
|
|
||||||
iocontrol.heartbeat()
|
|
||||||
|
|
||||||
-- maintain connection
|
|
||||||
if nic.is_connected() then
|
|
||||||
local ok, start_ui = coord_comms.try_connect()
|
|
||||||
if not ok then
|
|
||||||
link_failed = true
|
|
||||||
log_sys("supervisor connection failed, shutting down...")
|
|
||||||
log.fatal("failed to connect to supervisor")
|
|
||||||
break
|
|
||||||
elseif start_ui then
|
|
||||||
log_sys("supervisor connected, proceeding to main UI start")
|
|
||||||
ui_ok = start_main_ui()
|
|
||||||
if not ui_ok then break end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- iterate sessions
|
|
||||||
apisessions.iterate_all()
|
|
||||||
|
|
||||||
-- free any closed sessions
|
|
||||||
apisessions.free_all_closed()
|
|
||||||
|
|
||||||
-- update date and time string for main display
|
|
||||||
if coord_comms.is_linked() then
|
|
||||||
iocontrol.get_db().facility.ps.publish("date_time", os.date(date_format))
|
|
||||||
end
|
|
||||||
|
|
||||||
loop_clock.start()
|
|
||||||
elseif conn_watchdog.is_timer(param1) then
|
|
||||||
-- supervisor watchdog timeout
|
|
||||||
log_comms("supervisor server timeout")
|
|
||||||
|
|
||||||
-- close connection, main UI, and stop sounder
|
|
||||||
coord_comms.close()
|
|
||||||
renderer.close_ui()
|
|
||||||
sounder.stop()
|
|
||||||
else
|
|
||||||
-- a non-clock/main watchdog timer event
|
|
||||||
|
|
||||||
-- check API watchdogs
|
|
||||||
apisessions.check_all_watchdogs(param1)
|
|
||||||
|
|
||||||
-- notify timer callback dispatcher
|
|
||||||
tcd.handle(param1)
|
|
||||||
end
|
|
||||||
elseif event == "modem_message" then
|
|
||||||
-- got a packet
|
|
||||||
local packet = coord_comms.parse_packet(param1, param2, param3, param4, param5)
|
|
||||||
|
|
||||||
-- handle then check if it was a disconnect
|
|
||||||
if coord_comms.handle_packet(packet) then
|
|
||||||
log_comms("supervisor closed connection")
|
|
||||||
|
|
||||||
-- close connection, main UI, and stop sounder
|
|
||||||
coord_comms.close()
|
|
||||||
renderer.close_ui()
|
|
||||||
sounder.stop()
|
|
||||||
end
|
|
||||||
elseif event == "monitor_touch" or event == "mouse_click" or event == "mouse_up" or
|
|
||||||
event == "mouse_drag" or event == "mouse_scroll" or event == "double_click" then
|
|
||||||
-- handle a mouse event
|
|
||||||
renderer.handle_mouse(core.events.new_mouse_event(event, param1, param2, param3))
|
|
||||||
elseif event == "speaker_audio_empty" then
|
|
||||||
-- handle speaker buffer emptied
|
|
||||||
sounder.continue()
|
|
||||||
end
|
|
||||||
|
|
||||||
-- check for termination request
|
|
||||||
if event == "terminate" or ppm.should_terminate() then
|
|
||||||
-- handle supervisor connection
|
|
||||||
coord_comms.try_connect(true)
|
|
||||||
|
|
||||||
if coord_comms.is_linked() then
|
|
||||||
log_comms("terminate requested, closing supervisor connection...")
|
|
||||||
else link_failed = true end
|
|
||||||
|
|
||||||
coord_comms.close()
|
|
||||||
log_comms("supervisor connection closed")
|
|
||||||
|
|
||||||
-- handle API sessions
|
|
||||||
log_comms("closing api sessions...")
|
|
||||||
apisessions.close_all()
|
|
||||||
log_comms("api sessions closed")
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
renderer.close_ui()
|
renderer.close_ui()
|
||||||
renderer.close_fp()
|
renderer.close_fp()
|
||||||
sounder.stop()
|
sounder.stop()
|
||||||
log_sys("system shutdown")
|
log_sys("system shutdown")
|
||||||
|
|
||||||
if link_failed then println_ts("failed to connect to supervisor") end
|
if crd_state.link_fail then println_ts("failed to connect to supervisor") end
|
||||||
if not ui_ok then println_ts("main UI creation failed") end
|
if not crd_state.ui_ok then println_ts("main UI creation failed") end
|
||||||
|
|
||||||
-- close on error exit (such as UI error)
|
-- close on error exit (such as UI error)
|
||||||
if coord_comms.is_linked() then coord_comms.close() end
|
if smem_sys.coord_comms.is_linked() then smem_sys.coord_comms.close() end
|
||||||
|
|
||||||
println_ts("exited")
|
println_ts("exited")
|
||||||
log.info("exited")
|
log.info("exited")
|
||||||
|
363
coordinator/threads.lua
Normal file
363
coordinator/threads.lua
Normal file
@ -0,0 +1,363 @@
|
|||||||
|
local log = require("scada-common.log")
|
||||||
|
local mqueue = require("scada-common.mqueue")
|
||||||
|
local ppm = require("scada-common.ppm")
|
||||||
|
local tcd = require("scada-common.tcd")
|
||||||
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
|
local coordinator = require("coordinator.coordinator")
|
||||||
|
local iocontrol = require("coordinator.iocontrol")
|
||||||
|
local renderer = require("coordinator.renderer")
|
||||||
|
local sounder = require("coordinator.sounder")
|
||||||
|
|
||||||
|
local apisessions = require("coordinator.session.apisessions")
|
||||||
|
|
||||||
|
local core = require("graphics.core")
|
||||||
|
|
||||||
|
local log_render = coordinator.log_render
|
||||||
|
local log_sys = coordinator.log_sys
|
||||||
|
local log_comms = coordinator.log_comms
|
||||||
|
|
||||||
|
local threads = {}
|
||||||
|
|
||||||
|
local MAIN_CLOCK = 0.5 -- (2Hz, 10 ticks)
|
||||||
|
local RENDER_SLEEP = 100 -- (100ms, 2 ticks)
|
||||||
|
|
||||||
|
local MQ__RENDER_CMD = {
|
||||||
|
START_MAIN_UI = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
local MQ__RENDER_DATA = {
|
||||||
|
MON_CONNECT = 1,
|
||||||
|
MON_DISCONNECT = 2,
|
||||||
|
MON_RESIZE = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
-- main thread
|
||||||
|
---@nodiscard
|
||||||
|
---@param smem crd_shared_memory
|
||||||
|
function threads.thread__main(smem)
|
||||||
|
---@class parallel_thread
|
||||||
|
local public = {}
|
||||||
|
|
||||||
|
-- execute thread
|
||||||
|
function public.exec()
|
||||||
|
iocontrol.fp_rt_status("main", true)
|
||||||
|
log.debug("main thread start")
|
||||||
|
|
||||||
|
local loop_clock = util.new_clock(MAIN_CLOCK)
|
||||||
|
|
||||||
|
-- start clock
|
||||||
|
loop_clock.start()
|
||||||
|
|
||||||
|
log_sys("system started successfully")
|
||||||
|
|
||||||
|
-- load in from shared memory
|
||||||
|
local crd_state = smem.crd_state
|
||||||
|
local nic = smem.crd_sys.nic
|
||||||
|
local coord_comms = smem.crd_sys.coord_comms
|
||||||
|
local conn_watchdog = smem.crd_sys.conn_watchdog
|
||||||
|
|
||||||
|
-- event loop
|
||||||
|
while true do
|
||||||
|
local event, param1, param2, param3, param4, param5 = util.pull_event()
|
||||||
|
|
||||||
|
-- handle event
|
||||||
|
if event == "peripheral_detach" then
|
||||||
|
local type, device = ppm.handle_unmount(param1)
|
||||||
|
|
||||||
|
if type ~= nil and device ~= nil then
|
||||||
|
if type == "modem" then
|
||||||
|
-- we only really care if this is our wireless modem
|
||||||
|
-- if it is another modem, handle other peripheral losses separately
|
||||||
|
if nic.is_modem(device) then
|
||||||
|
nic.disconnect()
|
||||||
|
log_sys("comms modem disconnected")
|
||||||
|
|
||||||
|
local other_modem = ppm.get_wireless_modem()
|
||||||
|
if other_modem then
|
||||||
|
log_sys("found another wireless modem, using it for comms")
|
||||||
|
nic.connect(other_modem)
|
||||||
|
else
|
||||||
|
-- close out main UI
|
||||||
|
renderer.close_ui()
|
||||||
|
|
||||||
|
-- alert user to status
|
||||||
|
log_sys("awaiting comms modem reconnect...")
|
||||||
|
|
||||||
|
iocontrol.fp_has_modem(false)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
log_sys("non-comms modem disconnected")
|
||||||
|
end
|
||||||
|
elseif type == "monitor" then
|
||||||
|
smem.q.mq_render.push_data(MQ__RENDER_DATA.MON_DISCONNECT, device)
|
||||||
|
elseif type == "speaker" then
|
||||||
|
log_sys("lost alarm sounder speaker")
|
||||||
|
iocontrol.fp_has_speaker(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif event == "peripheral" then
|
||||||
|
local type, device = ppm.mount(param1)
|
||||||
|
|
||||||
|
if type ~= nil and device ~= nil then
|
||||||
|
if type == "modem" then
|
||||||
|
if device.isWireless() and not nic.is_connected() then
|
||||||
|
-- reconnected modem
|
||||||
|
log_sys("comms modem reconnected")
|
||||||
|
nic.connect(device)
|
||||||
|
iocontrol.fp_has_modem(true)
|
||||||
|
elseif device.isWireless() then
|
||||||
|
log.info("unused wireless modem reconnected")
|
||||||
|
else
|
||||||
|
log_sys("wired modem reconnected")
|
||||||
|
end
|
||||||
|
elseif type == "monitor" then
|
||||||
|
smem.q.mq_render.push_data(MQ__RENDER_DATA.MON_CONNECT, { name = param1, device = device })
|
||||||
|
elseif type == "speaker" then
|
||||||
|
log_sys("alarm sounder speaker reconnected")
|
||||||
|
sounder.reconnect(device)
|
||||||
|
iocontrol.fp_has_speaker(true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif event == "monitor_resize" then
|
||||||
|
smem.q.mq_render.push_data(MQ__RENDER_DATA.MON_RESIZE, param1)
|
||||||
|
elseif event == "timer" then
|
||||||
|
if loop_clock.is_clock(param1) then
|
||||||
|
-- main loop tick
|
||||||
|
|
||||||
|
-- toggle heartbeat
|
||||||
|
iocontrol.heartbeat()
|
||||||
|
|
||||||
|
-- maintain connection
|
||||||
|
if nic.is_connected() then
|
||||||
|
local ok, start_ui = coord_comms.try_connect()
|
||||||
|
if not ok then
|
||||||
|
crd_state.link_fail = true
|
||||||
|
crd_state.shutdown = true
|
||||||
|
log_sys("supervisor connection failed, shutting down...")
|
||||||
|
log.fatal("failed to connect to supervisor")
|
||||||
|
break
|
||||||
|
elseif start_ui then
|
||||||
|
log_sys("supervisor connected, dispatching main UI start")
|
||||||
|
smem.q.mq_render.push_command(MQ__RENDER_CMD.START_MAIN_UI)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- iterate sessions and free any closed ones
|
||||||
|
apisessions.iterate_all()
|
||||||
|
apisessions.free_all_closed()
|
||||||
|
|
||||||
|
if renderer.ui_ready() then
|
||||||
|
-- update clock used on main and flow monitors
|
||||||
|
iocontrol.get_db().facility.ps.publish("date_time", os.date(smem.date_format))
|
||||||
|
end
|
||||||
|
|
||||||
|
loop_clock.start()
|
||||||
|
elseif conn_watchdog.is_timer(param1) then
|
||||||
|
-- supervisor watchdog timeout
|
||||||
|
log_comms("supervisor server timeout")
|
||||||
|
|
||||||
|
-- close connection, main UI, and stop sounder
|
||||||
|
coord_comms.close()
|
||||||
|
renderer.close_ui()
|
||||||
|
sounder.stop()
|
||||||
|
else
|
||||||
|
-- a non-clock/main watchdog timer event
|
||||||
|
|
||||||
|
-- check API watchdogs
|
||||||
|
apisessions.check_all_watchdogs(param1)
|
||||||
|
|
||||||
|
-- notify timer callback dispatcher
|
||||||
|
tcd.handle(param1)
|
||||||
|
end
|
||||||
|
elseif event == "modem_message" then
|
||||||
|
-- got a packet
|
||||||
|
local packet = coord_comms.parse_packet(param1, param2, param3, param4, param5)
|
||||||
|
|
||||||
|
-- handle then check if it was a disconnect
|
||||||
|
if coord_comms.handle_packet(packet) then
|
||||||
|
log_comms("supervisor closed connection")
|
||||||
|
|
||||||
|
-- close connection, main UI, and stop sounder
|
||||||
|
coord_comms.close()
|
||||||
|
renderer.close_ui()
|
||||||
|
sounder.stop()
|
||||||
|
end
|
||||||
|
elseif event == "monitor_touch" or event == "mouse_click" or event == "mouse_up" or
|
||||||
|
event == "mouse_drag" or event == "mouse_scroll" or event == "double_click" then
|
||||||
|
-- handle a mouse event
|
||||||
|
renderer.handle_mouse(core.events.new_mouse_event(event, param1, param2, param3))
|
||||||
|
elseif event == "speaker_audio_empty" then
|
||||||
|
-- handle speaker buffer emptied
|
||||||
|
sounder.continue()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- check for termination request or UI crash
|
||||||
|
if event == "terminate" or ppm.should_terminate() then
|
||||||
|
crd_state.shutdown = true
|
||||||
|
log.info("terminate requested, main thread exiting")
|
||||||
|
elseif not crd_state.ui_ok then
|
||||||
|
crd_state.shutdown = true
|
||||||
|
log.info("terminating due to fatal UI error")
|
||||||
|
end
|
||||||
|
|
||||||
|
if crd_state.shutdown then
|
||||||
|
-- handle closing supervisor connection
|
||||||
|
coord_comms.try_connect(true)
|
||||||
|
|
||||||
|
if coord_comms.is_linked() then
|
||||||
|
log_comms("closing supervisor connection...")
|
||||||
|
else crd_state.link_fail = true end
|
||||||
|
|
||||||
|
coord_comms.close()
|
||||||
|
log_comms("supervisor connection closed")
|
||||||
|
|
||||||
|
-- handle API sessions
|
||||||
|
log_comms("closing api sessions...")
|
||||||
|
apisessions.close_all()
|
||||||
|
log_comms("api sessions closed")
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- execute the thread in a protected mode, retrying it on return if not shutting down
|
||||||
|
function public.p_exec()
|
||||||
|
local crd_state = smem.crd_state
|
||||||
|
|
||||||
|
while not crd_state.shutdown do
|
||||||
|
local status, result = pcall(public.exec)
|
||||||
|
if status == false then
|
||||||
|
log.fatal(util.strval(result))
|
||||||
|
end
|
||||||
|
|
||||||
|
iocontrol.fp_rt_status("main", false)
|
||||||
|
|
||||||
|
-- if status is true, then we are probably exiting, so this won't matter
|
||||||
|
-- this thread cannot be slept because it will miss events (namely "terminate")
|
||||||
|
if not crd_state.shutdown then
|
||||||
|
log.info("main thread restarting now...")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return public
|
||||||
|
end
|
||||||
|
|
||||||
|
-- coordinator renderer thread, tasked with long duration draws
|
||||||
|
---@nodiscard
|
||||||
|
---@param smem crd_shared_memory
|
||||||
|
function threads.thread__render(smem)
|
||||||
|
---@class parallel_thread
|
||||||
|
local public = {}
|
||||||
|
|
||||||
|
-- execute thread
|
||||||
|
function public.exec()
|
||||||
|
iocontrol.fp_rt_status("render", true)
|
||||||
|
log.debug("render thread start")
|
||||||
|
|
||||||
|
-- load in from shared memory
|
||||||
|
local crd_state = smem.crd_state
|
||||||
|
local render_queue = smem.q.mq_render
|
||||||
|
|
||||||
|
local last_update = util.time()
|
||||||
|
|
||||||
|
-- thread loop
|
||||||
|
while true do
|
||||||
|
-- check for messages in the message queue
|
||||||
|
while render_queue.ready() and not crd_state.shutdown do
|
||||||
|
local msg = render_queue.pop()
|
||||||
|
|
||||||
|
if msg ~= nil then
|
||||||
|
if msg.qtype == mqueue.TYPE.COMMAND then
|
||||||
|
-- received a command
|
||||||
|
if msg.message == MQ__RENDER_CMD.START_MAIN_UI then
|
||||||
|
-- stop the UI if it was already started
|
||||||
|
-- this may occur on a quick supervisor disconnect -> connect
|
||||||
|
if renderer.ui_ready() then
|
||||||
|
log_render("closing main UI before executing new request to start")
|
||||||
|
renderer.close_ui()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- start up the main UI
|
||||||
|
log_render("starting main UI...")
|
||||||
|
|
||||||
|
local draw_start = util.time_ms()
|
||||||
|
|
||||||
|
local ui_message
|
||||||
|
crd_state.ui_ok, ui_message = renderer.try_start_ui()
|
||||||
|
if not crd_state.ui_ok then
|
||||||
|
log_render(util.c("main UI error: ", ui_message))
|
||||||
|
log.fatal(util.c("main GUI render failed with error ", ui_message))
|
||||||
|
else
|
||||||
|
log_render("main UI draw took " .. (util.time_ms() - draw_start) .. "ms")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif msg.qtype == mqueue.TYPE.DATA then
|
||||||
|
-- received data
|
||||||
|
local cmd = msg.message ---@type queue_data
|
||||||
|
|
||||||
|
if cmd.key == MQ__RENDER_DATA.MON_CONNECT then
|
||||||
|
-- monitor connected
|
||||||
|
if renderer.handle_reconnect(cmd.val.name, cmd.val.device) then
|
||||||
|
log_sys(util.c("configured monitor ", cmd.val.name, " reconnected"))
|
||||||
|
else
|
||||||
|
log_sys(util.c("unused monitor ", cmd.val.name, " connected"))
|
||||||
|
end
|
||||||
|
elseif cmd.key == MQ__RENDER_DATA.MON_DISCONNECT then
|
||||||
|
-- monitor disconnected
|
||||||
|
if renderer.handle_disconnect(cmd.val) then
|
||||||
|
log_sys("lost a configured monitor")
|
||||||
|
else
|
||||||
|
log_sys("lost an unused monitor")
|
||||||
|
end
|
||||||
|
elseif cmd.key == MQ__RENDER_DATA.MON_RESIZE then
|
||||||
|
-- monitor resized
|
||||||
|
local is_used, is_ok = renderer.handle_resize(cmd.val)
|
||||||
|
if is_used then
|
||||||
|
log_sys(util.c("configured monitor ", cmd.val, " resized, ", util.trinary(is_ok, "display fits", "display does not fit")))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif msg.qtype == mqueue.TYPE.PACKET then
|
||||||
|
-- received a packet
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- quick yield
|
||||||
|
util.nop()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- check for termination request
|
||||||
|
if crd_state.shutdown then
|
||||||
|
log.info("render thread exiting")
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
-- delay before next check
|
||||||
|
last_update = util.adaptive_delay(RENDER_SLEEP, last_update)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- execute the thread in a protected mode, retrying it on return if not shutting down
|
||||||
|
function public.p_exec()
|
||||||
|
local crd_state = smem.crd_state
|
||||||
|
|
||||||
|
while not crd_state.shutdown do
|
||||||
|
local status, result = pcall(public.exec)
|
||||||
|
if status == false then
|
||||||
|
log.fatal(util.strval(result))
|
||||||
|
end
|
||||||
|
|
||||||
|
iocontrol.fp_rt_status("render", false)
|
||||||
|
|
||||||
|
if not crd_state.shutdown then
|
||||||
|
log.info("render thread restarting in 5 seconds...")
|
||||||
|
util.psleep(5)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return public
|
||||||
|
end
|
||||||
|
|
||||||
|
return threads
|
@ -1,7 +1,7 @@
|
|||||||
local style = require("coordinator.ui.style")
|
|
||||||
|
|
||||||
local iocontrol = require("coordinator.iocontrol")
|
local iocontrol = require("coordinator.iocontrol")
|
||||||
|
|
||||||
|
local style = require("coordinator.ui.style")
|
||||||
|
|
||||||
local core = require("graphics.core")
|
local core = require("graphics.core")
|
||||||
|
|
||||||
local Rectangle = require("graphics.elements.rectangle")
|
local Rectangle = require("graphics.elements.rectangle")
|
||||||
@ -35,10 +35,10 @@ local function new_view(root, x, y, ps)
|
|||||||
temp.register(ps, "temperature", function (t) temp.update(db.temp_convert(t)) end)
|
temp.register(ps, "temperature", function (t) temp.update(db.temp_convert(t)) end)
|
||||||
boil_r.register(ps, "boil_rate", boil_r.update)
|
boil_r.register(ps, "boil_rate", boil_r.update)
|
||||||
|
|
||||||
TextBox{parent=boiler,text="H",x=2,y=5,height=1,width=1,fg_bg=text_fg}
|
TextBox{parent=boiler,text="H",x=2,y=5,width=1,fg_bg=text_fg}
|
||||||
TextBox{parent=boiler,text="W",x=3,y=5,height=1,width=1,fg_bg=text_fg}
|
TextBox{parent=boiler,text="W",x=3,y=5,width=1,fg_bg=text_fg}
|
||||||
TextBox{parent=boiler,text="S",x=27,y=5,height=1,width=1,fg_bg=text_fg}
|
TextBox{parent=boiler,text="S",x=27,y=5,width=1,fg_bg=text_fg}
|
||||||
TextBox{parent=boiler,text="C",x=28,y=5,height=1,width=1,fg_bg=text_fg}
|
TextBox{parent=boiler,text="C",x=28,y=5,width=1,fg_bg=text_fg}
|
||||||
|
|
||||||
local hcool = VerticalBar{parent=boiler,x=2,y=1,fg_bg=cpair(colors.orange,colors.gray),height=4,width=1}
|
local hcool = VerticalBar{parent=boiler,x=2,y=1,fg_bg=cpair(colors.orange,colors.gray),height=4,width=1}
|
||||||
local water = VerticalBar{parent=boiler,x=3,y=1,fg_bg=cpair(colors.blue,colors.gray),height=4,width=1}
|
local water = VerticalBar{parent=boiler,x=3,y=1,fg_bg=cpair(colors.blue,colors.gray),height=4,width=1}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
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")
|
||||||
@ -9,6 +11,7 @@ local Rectangle = require("graphics.elements.rectangle")
|
|||||||
local TextBox = require("graphics.elements.textbox")
|
local TextBox = require("graphics.elements.textbox")
|
||||||
|
|
||||||
local DataIndicator = require("graphics.elements.indicators.data")
|
local DataIndicator = require("graphics.elements.indicators.data")
|
||||||
|
local IndicatorLight = require("graphics.elements.indicators.light")
|
||||||
local PowerIndicator = require("graphics.elements.indicators.power")
|
local PowerIndicator = require("graphics.elements.indicators.power")
|
||||||
local StateIndicator = require("graphics.elements.indicators.state")
|
local StateIndicator = require("graphics.elements.indicators.state")
|
||||||
local VerticalBar = require("graphics.elements.indicators.vbar")
|
local VerticalBar = require("graphics.elements.indicators.vbar")
|
||||||
@ -26,60 +29,69 @@ local ALIGN = core.ALIGN
|
|||||||
---@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, data, ps, id)
|
||||||
|
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
|
||||||
|
|
||||||
|
local ind_yel = style.ind_yel
|
||||||
|
local ind_wht = style.ind_wht
|
||||||
|
|
||||||
|
local db = iocontrol.get_db()
|
||||||
|
|
||||||
local title = "INDUCTION MATRIX"
|
local title = "INDUCTION MATRIX"
|
||||||
if type(id) == "number" then title = title .. id end
|
if type(id) == "number" then title = title .. id end
|
||||||
|
|
||||||
local matrix = Div{parent=root,fg_bg=style.root,width=33,height=24,x=x,y=y}
|
local matrix = Div{parent=root,fg_bg=style.root,width=33,height=24,x=x,y=y}
|
||||||
|
|
||||||
local cutout_fg_bg = cpair(style.theme.bg, colors.gray)
|
-- black has low contrast with dark gray, so if background is black use white instead
|
||||||
|
local cutout_fg_bg = cpair(util.trinary(style.theme.bg == colors.black, colors.white, style.theme.bg), colors.gray)
|
||||||
|
|
||||||
TextBox{parent=matrix,text=" ",width=33,height=1,x=1,y=1,fg_bg=cutout_fg_bg}
|
TextBox{parent=matrix,text=" ",width=33,x=1,y=1,fg_bg=cutout_fg_bg}
|
||||||
TextBox{parent=matrix,text=title,alignment=ALIGN.CENTER,width=33,height=1,x=1,y=2,fg_bg=cutout_fg_bg}
|
TextBox{parent=matrix,text=title,alignment=ALIGN.CENTER,width=33,x=1,y=2,fg_bg=cutout_fg_bg}
|
||||||
|
|
||||||
local rect = Rectangle{parent=matrix,border=border(1,colors.gray,true),width=33,height=22,x=1,y=3}
|
local rect = Rectangle{parent=matrix,border=border(1,colors.gray,true),width=33,height=22,x=1,y=3}
|
||||||
|
|
||||||
local status = StateIndicator{parent=rect,x=10,y=1,states=style.imatrix.states,value=1,min_width=14}
|
local status = StateIndicator{parent=rect,x=10,y=1,states=style.imatrix.states,value=1,min_width=14}
|
||||||
local energy = PowerIndicator{parent=rect,x=7,y=3,lu_colors=lu_col,label="Energy: ",format="%8.2f",value=0,width=26,fg_bg=text_fg}
|
local capacity = PowerIndicator{parent=rect,x=7,y=3,lu_colors=lu_col,label="Capacity:",unit=db.energy_label,format="%8.2f",value=0,width=26,fg_bg=text_fg}
|
||||||
local capacity = PowerIndicator{parent=rect,x=7,y=4,lu_colors=lu_col,label="Capacity:",format="%8.2f",value=0,width=26,fg_bg=text_fg}
|
local energy = PowerIndicator{parent=rect,x=7,y=4,lu_colors=lu_col,label="Energy: ",unit=db.energy_label,format="%8.2f",value=0,width=26,fg_bg=text_fg}
|
||||||
local input = PowerIndicator{parent=rect,x=7,y=5,lu_colors=lu_col,label="Input: ",format="%8.2f",rate=true,value=0,width=26,fg_bg=text_fg}
|
local avg_chg = PowerIndicator{parent=rect,x=7,y=5,lu_colors=lu_col,label="\xb7Average:",unit=db.energy_label,format="%8.2f",value=0,width=26,fg_bg=text_fg}
|
||||||
local output = PowerIndicator{parent=rect,x=7,y=6,lu_colors=lu_col,label="Output: ",format="%8.2f",rate=true,value=0,width=26,fg_bg=text_fg}
|
local input = PowerIndicator{parent=rect,x=7,y=6,lu_colors=lu_col,label="Input: ",unit=db.energy_label,format="%8.2f",rate=true,value=0,width=26,fg_bg=text_fg}
|
||||||
|
local avg_in = PowerIndicator{parent=rect,x=7,y=7,lu_colors=lu_col,label="\xb7Average:",unit=db.energy_label,format="%8.2f",rate=true,value=0,width=26,fg_bg=text_fg}
|
||||||
local avg_chg = PowerIndicator{parent=rect,x=7,y=8,lu_colors=lu_col,label="Avg. Chg:",format="%8.2f",value=0,width=26,fg_bg=text_fg}
|
local output = PowerIndicator{parent=rect,x=7,y=8,lu_colors=lu_col,label="Output: ",unit=db.energy_label,format="%8.2f",rate=true,value=0,width=26,fg_bg=text_fg}
|
||||||
local avg_in = PowerIndicator{parent=rect,x=7,y=9,lu_colors=lu_col,label="Avg. In: ",format="%8.2f",rate=true,value=0,width=26,fg_bg=text_fg}
|
local avg_out = PowerIndicator{parent=rect,x=7,y=9,lu_colors=lu_col,label="\xb7Average:",unit=db.energy_label,format="%8.2f",rate=true,value=0,width=26,fg_bg=text_fg}
|
||||||
local avg_out = PowerIndicator{parent=rect,x=7,y=10,lu_colors=lu_col,label="Avg. Out:",format="%8.2f",rate=true,value=0,width=26,fg_bg=text_fg}
|
local trans_cap = PowerIndicator{parent=rect,x=7,y=10,lu_colors=lu_col,label="Max I/O: ",unit=db.energy_label,format="%8.2f",rate=true,value=0,width=26,fg_bg=text_fg}
|
||||||
|
|
||||||
status.register(ps, "computed_status", status.update)
|
status.register(ps, "computed_status", status.update)
|
||||||
energy.register(ps, "energy", function (val) energy.update(util.joules_to_fe(val)) end)
|
capacity.register(ps, "max_energy", function (val) capacity.update(db.energy_convert(val)) end)
|
||||||
capacity.register(ps, "max_energy", function (val) capacity.update(util.joules_to_fe(val)) end)
|
energy.register(ps, "energy", function (val) energy.update(db.energy_convert(val)) end)
|
||||||
input.register(ps, "last_input", function (val) input.update(util.joules_to_fe(val)) end)
|
|
||||||
output.register(ps, "last_output", function (val) output.update(util.joules_to_fe(val)) end)
|
|
||||||
|
|
||||||
avg_chg.register(ps, "avg_charge", avg_chg.update)
|
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)
|
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)
|
avg_out.register(ps, "avg_outflow", avg_out.update)
|
||||||
|
trans_cap.register(ps, "transfer_cap", function (val) trans_cap.update(db.energy_convert(val)) end)
|
||||||
|
|
||||||
local fill = DataIndicator{parent=rect,x=11,y=12,lu_colors=lu_col,label="Fill:",unit="%",format="%8.2f",value=0,width=18,fg_bg=text_fg}
|
local fill = DataIndicator{parent=rect,x=11,y=12,lu_colors=lu_col,label="Fill: ",format="%7.2f",unit="%",value=0,width=20,fg_bg=text_fg}
|
||||||
|
local cells = DataIndicator{parent=rect,x=11,y=13,lu_colors=lu_col,label="Cells: ",format="%7d",value=0,width=18,fg_bg=text_fg}
|
||||||
local cells = DataIndicator{parent=rect,x=11,y=14,lu_colors=lu_col,label="Cells: ",format="%7d",value=0,width=18,fg_bg=text_fg}
|
local providers = DataIndicator{parent=rect,x=11,y=14,lu_colors=lu_col,label="Providers:",format="%7d",value=0,width=18,fg_bg=text_fg}
|
||||||
local providers = DataIndicator{parent=rect,x=11,y=15,lu_colors=lu_col,label="Providers:",format="%7d",value=0,width=18,fg_bg=text_fg}
|
|
||||||
|
|
||||||
TextBox{parent=rect,text="Transfer Capacity",x=11,y=17,height=1,width=17,fg_bg=style.theme.label_fg}
|
|
||||||
local trans_cap = PowerIndicator{parent=rect,x=19,y=18,lu_colors=lu_col,label="",format="%5.2f",rate=true,value=0,width=12,fg_bg=text_fg}
|
|
||||||
|
|
||||||
|
fill.register(ps, "energy_fill", function (val) fill.update(val * 100) end)
|
||||||
cells.register(ps, "cells", cells.update)
|
cells.register(ps, "cells", cells.update)
|
||||||
providers.register(ps, "providers", providers.update)
|
providers.register(ps, "providers", providers.update)
|
||||||
fill.register(ps, "energy_fill", function (val) fill.update(val * 100) end)
|
|
||||||
trans_cap.register(ps, "transfer_cap", function (val) trans_cap.update(util.joules_to_fe(val)) end)
|
local chging = IndicatorLight{parent=rect,x=11,y=16,label="Charging",colors=ind_wht}
|
||||||
|
local dischg = IndicatorLight{parent=rect,x=11,y=17,label="Discharging",colors=ind_wht}
|
||||||
|
local max_io = IndicatorLight{parent=rect,x=11,y=18,label="Max I/O Rate",colors=ind_yel}
|
||||||
|
|
||||||
|
chging.register(ps, "is_charging", chging.update)
|
||||||
|
dischg.register(ps, "is_discharging", dischg.update)
|
||||||
|
max_io.register(ps, "at_max_io", max_io.update)
|
||||||
|
|
||||||
local charge = VerticalBar{parent=rect,x=2,y=2,fg_bg=cpair(colors.green,colors.gray),height=17,width=4}
|
local charge = VerticalBar{parent=rect,x=2,y=2,fg_bg=cpair(colors.green,colors.gray),height=17,width=4}
|
||||||
local in_cap = VerticalBar{parent=rect,x=7,y=12,fg_bg=cpair(colors.red,colors.gray),height=7,width=1}
|
local in_cap = VerticalBar{parent=rect,x=7,y=12,fg_bg=cpair(colors.red,colors.gray),height=7,width=1}
|
||||||
local out_cap = VerticalBar{parent=rect,x=9,y=12,fg_bg=cpair(colors.blue,colors.gray),height=7,width=1}
|
local out_cap = VerticalBar{parent=rect,x=9,y=12,fg_bg=cpair(colors.blue,colors.gray),height=7,width=1}
|
||||||
|
|
||||||
TextBox{parent=rect,text="FILL",x=2,y=20,height=1,width=4,fg_bg=text_fg}
|
TextBox{parent=rect,text="FILL I/O",x=2,y=20,width=8,fg_bg=label_fg}
|
||||||
TextBox{parent=rect,text="I/O",x=7,y=20,height=1,width=3,fg_bg=text_fg}
|
|
||||||
|
|
||||||
local function calc_saturation(val)
|
local function calc_saturation(val)
|
||||||
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
|
||||||
@ -90,6 +102,49 @@ local function new_view(root, x, y, data, ps, id)
|
|||||||
charge.register(ps, "energy_fill", charge.update)
|
charge.register(ps, "energy_fill", charge.update)
|
||||||
in_cap.register(ps, "last_input", function (val) in_cap.update(calc_saturation(val)) end)
|
in_cap.register(ps, "last_input", function (val) in_cap.update(calc_saturation(val)) end)
|
||||||
out_cap.register(ps, "last_output", function (val) out_cap.update(calc_saturation(val)) end)
|
out_cap.register(ps, "last_output", function (val) out_cap.update(calc_saturation(val)) end)
|
||||||
|
|
||||||
|
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)
|
||||||
|
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
|
||||||
|
@ -34,18 +34,18 @@ local function init(parent, id)
|
|||||||
|
|
||||||
local ps_prefix = "pkt_" .. id .. "_"
|
local ps_prefix = "pkt_" .. id .. "_"
|
||||||
|
|
||||||
TextBox{parent=entry,x=1,y=1,text="",width=8,height=1,fg_bg=s_hi_box}
|
TextBox{parent=entry,x=1,y=1,text="",width=8,fg_bg=s_hi_box}
|
||||||
local pkt_addr = TextBox{parent=entry,x=1,y=2,text="@ C ??",alignment=ALIGN.CENTER,width=8,height=1,fg_bg=s_hi_box,nav_active=cpair(colors.gray,colors.black)}
|
local pkt_addr = TextBox{parent=entry,x=1,y=2,text="@ C ??",alignment=ALIGN.CENTER,width=8,fg_bg=s_hi_box,nav_active=cpair(colors.gray,colors.black)}
|
||||||
TextBox{parent=entry,x=1,y=3,text="",width=8,height=1,fg_bg=s_hi_box}
|
TextBox{parent=entry,x=1,y=3,text="",width=8,fg_bg=s_hi_box}
|
||||||
pkt_addr.register(ps, ps_prefix .. "addr", pkt_addr.set_value)
|
pkt_addr.register(ps, ps_prefix .. "addr", pkt_addr.set_value)
|
||||||
|
|
||||||
TextBox{parent=entry,x=10,y=2,text="FW:",width=3,height=1}
|
TextBox{parent=entry,x=10,y=2,text="FW:",width=3}
|
||||||
local pkt_fw_v = TextBox{parent=entry,x=14,y=2,text=" ------- ",width=20,height=1,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,height=1}
|
TextBox{parent=entry,x=35,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=40,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,height=1,fg_bg=label_fg}
|
TextBox{parent=entry,x=46,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)
|
||||||
|
|
||||||
|
@ -56,8 +56,10 @@ local function new_view(root, x, y)
|
|||||||
local blk_brn = cpair(colors.black, colors.brown)
|
local blk_brn = cpair(colors.black, colors.brown)
|
||||||
local blk_pur = cpair(colors.black, colors.purple)
|
local blk_pur = cpair(colors.black, colors.purple)
|
||||||
|
|
||||||
local facility = iocontrol.get_db().facility
|
local db = iocontrol.get_db()
|
||||||
local units = iocontrol.get_db().units
|
|
||||||
|
local facility = db.facility
|
||||||
|
local units = db.units
|
||||||
|
|
||||||
local main = Div{parent=root,width=128,height=24,x=x,y=y}
|
local main = Div{parent=root,width=128,height=24,x=x,y=y}
|
||||||
|
|
||||||
@ -105,11 +107,11 @@ local function new_view(root, x, y)
|
|||||||
fac_rad_h.register(facility.ps, "as_radiation", fac_rad_h.update)
|
fac_rad_h.register(facility.ps, "as_radiation", fac_rad_h.update)
|
||||||
gen_fault.register(facility.ps, "as_gen_fault", gen_fault.update)
|
gen_fault.register(facility.ps, "as_gen_fault", gen_fault.update)
|
||||||
|
|
||||||
TextBox{parent=main,y=23,text="Radiation",height=1,width=13,fg_bg=style.label}
|
TextBox{parent=main,y=23,text="Radiation",width=13,fg_bg=style.label}
|
||||||
local radiation = RadIndicator{parent=main,label="",format="%9.3f",lu_colors=lu_cpair,width=13,fg_bg=s_field}
|
local radiation = RadIndicator{parent=main,label="",format="%9.3f",lu_colors=lu_cpair,width=13,fg_bg=s_field}
|
||||||
radiation.register(facility.ps, "radiation", radiation.update)
|
radiation.register(facility.ps, "radiation", radiation.update)
|
||||||
|
|
||||||
TextBox{parent=main,x=15,y=23,text="Linked RTUs",height=1,width=11,fg_bg=style.label}
|
TextBox{parent=main,x=15,y=23,text="Linked RTUs",width=11,fg_bg=style.label}
|
||||||
local rtu_count = DataIndicator{parent=main,x=15,y=24,label="",format="%11d",value=0,lu_colors=lu_cpair,width=11,fg_bg=s_field}
|
local rtu_count = DataIndicator{parent=main,x=15,y=24,label="",format="%11d",value=0,lu_colors=lu_cpair,width=11,fg_bg=s_field}
|
||||||
rtu_count.register(facility.ps, "rtu_count", rtu_count.update)
|
rtu_count.register(facility.ps, "rtu_count", rtu_count.update)
|
||||||
|
|
||||||
@ -141,22 +143,22 @@ local function new_view(root, x, y)
|
|||||||
|
|
||||||
local chg_target = Div{parent=targets,x=9,y=6,width=23,height=3,fg_bg=s_hi_box}
|
local chg_target = Div{parent=targets,x=9,y=6,width=23,height=3,fg_bg=s_hi_box}
|
||||||
local c_target = SpinboxNumeric{parent=chg_target,x=2,y=1,whole_num_precision=15,fractional_precision=0,min=0,arrow_fg_bg=arrow_fg_bg,arrow_disable=style.theme.disabled}
|
local c_target = SpinboxNumeric{parent=chg_target,x=2,y=1,whole_num_precision=15,fractional_precision=0,min=0,arrow_fg_bg=arrow_fg_bg,arrow_disable=style.theme.disabled}
|
||||||
TextBox{parent=chg_target,x=18,y=2,text="MFE",fg_bg=style.theme.label_fg}
|
TextBox{parent=chg_target,x=18,y=2,text="M"..db.energy_label,fg_bg=style.theme.label_fg}
|
||||||
local cur_charge = DataIndicator{parent=targets,x=9,y=9,label="",format="%19d",value=0,unit="MFE",commas=true,lu_colors=black,width=23,fg_bg=blk_brn}
|
local cur_charge = DataIndicator{parent=targets,x=9,y=9,label="",format="%19d",value=0,unit="M"..db.energy_label,commas=true,lu_colors=black,width=23,fg_bg=blk_brn}
|
||||||
|
|
||||||
c_target.register(facility.ps, "process_charge_target", c_target.set_value)
|
c_target.register(facility.ps, "process_charge_target", c_target.set_value)
|
||||||
cur_charge.register(facility.induction_ps_tbl[1], "energy", function (j) cur_charge.update(util.joules_to_fe(j) / 1000000) end)
|
cur_charge.register(facility.induction_ps_tbl[1], "avg_charge", function (fe) cur_charge.update(db.energy_convert_from_fe(fe) / 1000000) end)
|
||||||
|
|
||||||
local gen_tag = Div{parent=targets,x=1,y=11,width=8,height=4,fg_bg=blk_pur}
|
local gen_tag = Div{parent=targets,x=1,y=11,width=8,height=4,fg_bg=blk_pur}
|
||||||
TextBox{parent=gen_tag,x=2,y=2,text="Gen. Target",width=7,height=2}
|
TextBox{parent=gen_tag,x=2,y=2,text="Gen. Target",width=7,height=2}
|
||||||
|
|
||||||
local gen_target = Div{parent=targets,x=9,y=11,width=23,height=3,fg_bg=s_hi_box}
|
local gen_target = Div{parent=targets,x=9,y=11,width=23,height=3,fg_bg=s_hi_box}
|
||||||
local g_target = SpinboxNumeric{parent=gen_target,x=8,y=1,whole_num_precision=9,fractional_precision=0,min=0,arrow_fg_bg=arrow_fg_bg,arrow_disable=style.theme.disabled}
|
local g_target = SpinboxNumeric{parent=gen_target,x=8,y=1,whole_num_precision=9,fractional_precision=0,min=0,arrow_fg_bg=arrow_fg_bg,arrow_disable=style.theme.disabled}
|
||||||
TextBox{parent=gen_target,x=18,y=2,text="kFE/t",fg_bg=style.theme.label_fg}
|
TextBox{parent=gen_target,x=18,y=2,text="k"..db.energy_label.."/t",fg_bg=style.theme.label_fg}
|
||||||
local cur_gen = DataIndicator{parent=targets,x=9,y=14,label="",format="%17d",value=0,unit="kFE/t",commas=true,lu_colors=black,width=23,fg_bg=blk_brn}
|
local cur_gen = DataIndicator{parent=targets,x=9,y=14,label="",format="%17d",value=0,unit="k"..db.energy_label.."/t",commas=true,lu_colors=black,width=23,fg_bg=blk_brn}
|
||||||
|
|
||||||
g_target.register(facility.ps, "process_gen_target", g_target.set_value)
|
g_target.register(facility.ps, "process_gen_target", g_target.set_value)
|
||||||
cur_gen.register(facility.induction_ps_tbl[1], "last_input", function (j) cur_gen.update(util.round(util.joules_to_fe(j) / 1000)) end)
|
cur_gen.register(facility.induction_ps_tbl[1], "last_input", function (j) cur_gen.update(util.round(db.energy_convert(j) / 1000)) end)
|
||||||
|
|
||||||
-----------------
|
-----------------
|
||||||
-- unit limits --
|
-- unit limits --
|
||||||
@ -190,7 +192,7 @@ local function new_view(root, x, y)
|
|||||||
|
|
||||||
local lim_ctl = Div{parent=limit_div,x=9,y=_y,width=14,height=3,fg_bg=s_hi_box}
|
local lim_ctl = Div{parent=limit_div,x=9,y=_y,width=14,height=3,fg_bg=s_hi_box}
|
||||||
local lim = SpinboxNumeric{parent=lim_ctl,x=2,y=1,whole_num_precision=4,fractional_precision=1,min=0.1,arrow_fg_bg=arrow_fg_bg,arrow_disable=style.theme.disabled,fg_bg=lim_fg_bg}
|
local lim = SpinboxNumeric{parent=lim_ctl,x=2,y=1,whole_num_precision=4,fractional_precision=1,min=0.1,arrow_fg_bg=arrow_fg_bg,arrow_disable=style.theme.disabled,fg_bg=lim_fg_bg}
|
||||||
TextBox{parent=lim_ctl,x=9,y=2,text="mB/t",width=4,height=1,fg_bg=label_fg}
|
TextBox{parent=lim_ctl,x=9,y=2,text="mB/t",width=4,fg_bg=label_fg}
|
||||||
|
|
||||||
local cur_burn = DataIndicator{parent=limit_div,x=9,y=_y+3,label="",format="%7.1f",value=0,unit="mB/t",commas=false,lu_colors=cpair(cur_lu,cur_lu),width=14,fg_bg=cur_fg_bg}
|
local cur_burn = DataIndicator{parent=limit_div,x=9,y=_y+3,label="",format="%7.1f",value=0,unit="mB/t",commas=false,lu_colors=cpair(cur_lu,cur_lu),width=14,fg_bg=cur_fg_bg}
|
||||||
|
|
||||||
@ -249,8 +251,8 @@ local function new_view(root, x, y)
|
|||||||
mode.register(facility.ps, "process_mode", mode.set_value)
|
mode.register(facility.ps, "process_mode", mode.set_value)
|
||||||
|
|
||||||
local u_stat = Rectangle{parent=proc,border=border(1,colors.gray,true),thin=true,width=31,height=4,x=1,y=16,fg_bg=bw_fg_bg}
|
local u_stat = Rectangle{parent=proc,border=border(1,colors.gray,true),thin=true,width=31,height=4,x=1,y=16,fg_bg=bw_fg_bg}
|
||||||
local stat_line_1 = TextBox{parent=u_stat,x=1,y=1,text="UNKNOWN",width=31,height=1,alignment=ALIGN.CENTER,fg_bg=bw_fg_bg}
|
local stat_line_1 = TextBox{parent=u_stat,x=1,y=1,text="UNKNOWN",width=31,alignment=ALIGN.CENTER,fg_bg=bw_fg_bg}
|
||||||
local stat_line_2 = TextBox{parent=u_stat,x=1,y=2,text="awaiting data...",width=31,height=1,alignment=ALIGN.CENTER,fg_bg=cpair(colors.gray,colors.white)}
|
local stat_line_2 = TextBox{parent=u_stat,x=1,y=2,text="awaiting data...",width=31,alignment=ALIGN.CENTER,fg_bg=cpair(colors.gray,colors.white)}
|
||||||
|
|
||||||
stat_line_1.register(facility.ps, "status_line_1", stat_line_1.set_value)
|
stat_line_1.register(facility.ps, "status_line_1", stat_line_1.set_value)
|
||||||
stat_line_2.register(facility.ps, "status_line_2", stat_line_2.set_value)
|
stat_line_2.register(facility.ps, "status_line_2", stat_line_2.set_value)
|
||||||
@ -262,7 +264,10 @@ local function new_view(root, x, y)
|
|||||||
local limits = {}
|
local limits = {}
|
||||||
for i = 1, #rate_limits do limits[i] = rate_limits[i].get_value() end
|
for i = 1, #rate_limits do limits[i] = rate_limits[i].get_value() end
|
||||||
|
|
||||||
process.save(mode.get_value(), b_target.get_value(), c_target.get_value(), g_target.get_value(), limits)
|
process.save(mode.get_value(), b_target.get_value(),
|
||||||
|
db.energy_convert_to_fe(c_target.get_value()),
|
||||||
|
db.energy_convert_to_fe(g_target.get_value()),
|
||||||
|
limits)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- start automatic control after saving process control settings
|
-- start automatic control after saving process control settings
|
||||||
@ -320,7 +325,7 @@ local function new_view(root, x, y)
|
|||||||
for i = 1, facility.num_units do
|
for i = 1, facility.num_units do
|
||||||
local unit = units[i] ---@type ioctl_unit
|
local unit = units[i] ---@type ioctl_unit
|
||||||
|
|
||||||
TextBox{parent=waste_status,y=i,text="U"..i.." Waste",width=8,height=1}
|
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.waste.states_abbrv,value=1,min_width=6}
|
||||||
|
|
||||||
@ -332,8 +337,8 @@ local function new_view(root, x, y)
|
|||||||
|
|
||||||
local cutout_fg_bg = cpair(style.theme.bg, colors.brown)
|
local cutout_fg_bg = cpair(style.theme.bg, colors.brown)
|
||||||
|
|
||||||
TextBox{parent=waste_sel,text=" ",width=21,height=1,x=1,y=1,fg_bg=cutout_fg_bg}
|
TextBox{parent=waste_sel,text=" ",width=21,x=1,y=1,fg_bg=cutout_fg_bg}
|
||||||
TextBox{parent=waste_sel,text="WASTE PRODUCTION",alignment=ALIGN.CENTER,width=21,height=1,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.waste.states,value=1,min_width=17}
|
||||||
@ -341,31 +346,25 @@ local function new_view(root, x, y)
|
|||||||
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.waste.options,callback=process.set_process_waste,radio_colors=cpair(style.theme.accent_dark,style.theme.accent_light),select_color=colors.brown}
|
||||||
local pu_fallback = Checkbox{parent=rect,x=2,y=7,label="Pu Fallback",callback=process.set_pu_fallback,box_fg_bg=cpair(colors.green,style.theme.checkbox_bg)}
|
|
||||||
|
|
||||||
waste_prod.register(facility.ps, "process_waste_product", waste_prod.set_value)
|
waste_prod.register(facility.ps, "process_waste_product", waste_prod.set_value)
|
||||||
pu_fallback.register(facility.ps, "process_pu_fallback", pu_fallback.set_value)
|
|
||||||
|
|
||||||
local fb_active = IndicatorLight{parent=rect,x=2,y=9,label="Fallback Active",colors=ind_wht}
|
local fb_active = IndicatorLight{parent=rect,x=2,y=7,label="Fallback Active",colors=ind_wht}
|
||||||
|
local sps_disabled = IndicatorLight{parent=rect,x=2,y=8,label="SPS Disabled LC",colors=ind_yel}
|
||||||
|
|
||||||
fb_active.register(facility.ps, "pu_fallback_active", fb_active.update)
|
fb_active.register(facility.ps, "pu_fallback_active", fb_active.update)
|
||||||
|
sps_disabled.register(facility.ps, "sps_disabled_low_power", sps_disabled.update)
|
||||||
|
|
||||||
TextBox{parent=rect,x=2,y=11,text="Plutonium Rate",height=1,width=17,fg_bg=style.label}
|
local pu_fallback = Checkbox{parent=rect,x=2,y=10,label="Pu Fallback",callback=process.set_pu_fallback,box_fg_bg=cpair(colors.brown,style.theme.checkbox_bg)}
|
||||||
local pu_rate = DataIndicator{parent=rect,x=2,label="",unit="mB/t",format="%12.2f",value=0,lu_colors=lu_cpair,fg_bg=s_field,width=17}
|
|
||||||
|
|
||||||
TextBox{parent=rect,x=2,y=14,text="Polonium Rate",height=1,width=17,fg_bg=style.label}
|
TextBox{parent=rect,x=2,y=12,height=3,text="Switch to Pu when SNAs cannot keep up with waste.",fg_bg=style.label}
|
||||||
local po_rate = DataIndicator{parent=rect,x=2,label="",unit="mB/t",format="%12.2f",value=0,lu_colors=lu_cpair,fg_bg=s_field,width=17}
|
|
||||||
|
|
||||||
TextBox{parent=rect,x=2,y=17,text="Antimatter Rate",height=1,width=17,fg_bg=style.label}
|
local lc_sps = Checkbox{parent=rect,x=2,y=16,label="Low Charge SPS",callback=process.set_sps_low_power,box_fg_bg=cpair(colors.brown,style.theme.checkbox_bg)}
|
||||||
local am_rate = DataIndicator{parent=rect,x=2,label="",unit="\xb5B/t",format="%12d",value=0,lu_colors=lu_cpair,fg_bg=s_field,width=17}
|
|
||||||
|
|
||||||
pu_rate.register(facility.ps, "pu_rate", pu_rate.update)
|
TextBox{parent=rect,x=2,y=18,height=3,text="Use SPS at low charge, otherwise switches to Po.",fg_bg=style.label}
|
||||||
po_rate.register(facility.ps, "po_rate", po_rate.update)
|
|
||||||
am_rate.register(facility.ps, "am_rate", am_rate.update)
|
|
||||||
|
|
||||||
local sna_count = DataIndicator{parent=rect,x=2,y=20,label="Linked SNAs:",format="%4d",value=0,lu_colors=lu_cpair,width=17}
|
pu_fallback.register(facility.ps, "process_pu_fallback", pu_fallback.set_value)
|
||||||
|
lc_sps.register(facility.ps, "process_sps_low_power", lc_sps.set_value)
|
||||||
sna_count.register(facility.ps, "sna_count", sna_count.update)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return new_view
|
return new_view
|
||||||
|
@ -41,10 +41,10 @@ local function new_view(root, x, y, ps)
|
|||||||
|
|
||||||
local reactor_fills = Rectangle{parent=root,border=border(1, colors.gray, true),width=24,height=7,x=(x + 29),y=y}
|
local reactor_fills = Rectangle{parent=root,border=border(1, colors.gray, true),width=24,height=7,x=(x + 29),y=y}
|
||||||
|
|
||||||
TextBox{parent=reactor_fills,text="FUEL",x=2,y=1,height=1,fg_bg=text_fg}
|
TextBox{parent=reactor_fills,text="FUEL",x=2,y=1,fg_bg=text_fg}
|
||||||
TextBox{parent=reactor_fills,text="COOL",x=2,y=2,height=1,fg_bg=text_fg}
|
TextBox{parent=reactor_fills,text="COOL",x=2,y=2,fg_bg=text_fg}
|
||||||
TextBox{parent=reactor_fills,text="HCOOL",x=2,y=4,height=1,fg_bg=text_fg}
|
TextBox{parent=reactor_fills,text="HCOOL",x=2,y=4,fg_bg=text_fg}
|
||||||
TextBox{parent=reactor_fills,text="WASTE",x=2,y=5,height=1,fg_bg=text_fg}
|
TextBox{parent=reactor_fills,text="WASTE",x=2,y=5,fg_bg=text_fg}
|
||||||
|
|
||||||
local fuel = HorizontalBar{parent=reactor_fills,x=8,y=1,show_percent=true,bar_fg_bg=cpair(style.theme.fuel_color,colors.gray),height=1,width=14}
|
local fuel = HorizontalBar{parent=reactor_fills,x=8,y=1,show_percent=true,bar_fg_bg=cpair(style.theme.fuel_color,colors.gray),height=1,width=14}
|
||||||
local ccool = HorizontalBar{parent=reactor_fills,x=8,y=2,show_percent=true,bar_fg_bg=cpair(colors.blue,colors.gray),height=1,width=14}
|
local ccool = HorizontalBar{parent=reactor_fills,x=8,y=2,show_percent=true,bar_fg_bg=cpair(colors.blue,colors.gray),height=1,width=14}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
local util = require("scada-common.util")
|
local iocontrol = require("coordinator.iocontrol")
|
||||||
|
|
||||||
local style = require("coordinator.ui.style")
|
local style = require("coordinator.ui.style")
|
||||||
|
|
||||||
@ -24,21 +24,23 @@ local function new_view(root, x, y, ps)
|
|||||||
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
|
||||||
|
|
||||||
|
local db = iocontrol.get_db()
|
||||||
|
|
||||||
local turbine = Rectangle{parent=root,border=border(1,colors.gray,true),width=23,height=7,x=x,y=y}
|
local turbine = Rectangle{parent=root,border=border(1,colors.gray,true),width=23,height=7,x=x,y=y}
|
||||||
|
|
||||||
local status = StateIndicator{parent=turbine,x=7,y=1,states=style.turbine.states,value=1,min_width=12}
|
local status = StateIndicator{parent=turbine,x=7,y=1,states=style.turbine.states,value=1,min_width=12}
|
||||||
local prod_rate = PowerIndicator{parent=turbine,x=5,y=3,lu_colors=lu_col,label="",format="%10.2f",value=0,rate=true,width=16,fg_bg=text_fg}
|
local prod_rate = PowerIndicator{parent=turbine,x=5,y=3,lu_colors=lu_col,label="",unit=db.energy_label,format="%10.2f",value=0,rate=true,width=16,fg_bg=text_fg}
|
||||||
local flow_rate = DataIndicator{parent=turbine,x=5,y=4,lu_colors=lu_col,label="",unit="mB/t",format="%10.0f",value=0,commas=true,width=16,fg_bg=text_fg}
|
local flow_rate = DataIndicator{parent=turbine,x=5,y=4,lu_colors=lu_col,label="",unit="mB/t",format="%10.0f",value=0,commas=true,width=16,fg_bg=text_fg}
|
||||||
|
|
||||||
status.register(ps, "computed_status", status.update)
|
status.register(ps, "computed_status", status.update)
|
||||||
prod_rate.register(ps, "prod_rate", function (val) prod_rate.update(util.joules_to_fe(val)) end)
|
prod_rate.register(ps, "prod_rate", function (val) prod_rate.update(db.energy_convert(val)) end)
|
||||||
flow_rate.register(ps, "steam_input_rate", flow_rate.update)
|
flow_rate.register(ps, "steam_input_rate", flow_rate.update)
|
||||||
|
|
||||||
local steam = VerticalBar{parent=turbine,x=2,y=1,fg_bg=cpair(colors.white,colors.gray),height=4,width=1}
|
local steam = VerticalBar{parent=turbine,x=2,y=1,fg_bg=cpair(colors.white,colors.gray),height=4,width=1}
|
||||||
local energy = VerticalBar{parent=turbine,x=3,y=1,fg_bg=cpair(colors.green,colors.gray),height=4,width=1}
|
local energy = VerticalBar{parent=turbine,x=3,y=1,fg_bg=cpair(colors.green,colors.gray),height=4,width=1}
|
||||||
|
|
||||||
TextBox{parent=turbine,text="S",x=2,y=5,height=1,width=1,fg_bg=text_fg}
|
TextBox{parent=turbine,text="S",x=2,y=5,width=1,fg_bg=text_fg}
|
||||||
TextBox{parent=turbine,text="E",x=3,y=5,height=1,width=1,fg_bg=text_fg}
|
TextBox{parent=turbine,text="E",x=3,y=5,width=1,fg_bg=text_fg}
|
||||||
|
|
||||||
steam.register(ps, "steam_fill", steam.update)
|
steam.register(ps, "steam_fill", steam.update)
|
||||||
energy.register(ps, "energy_fill", energy.update)
|
energy.register(ps, "energy_fill", energy.update)
|
||||||
|
@ -71,7 +71,7 @@ local function init(parent, id)
|
|||||||
local b_ps = unit.boiler_ps_tbl
|
local b_ps = unit.boiler_ps_tbl
|
||||||
local t_ps = unit.turbine_ps_tbl
|
local t_ps = unit.turbine_ps_tbl
|
||||||
|
|
||||||
TextBox{parent=main,text="Reactor Unit #" .. id,alignment=ALIGN.CENTER,height=1,fg_bg=style.theme.header}
|
TextBox{parent=main,text="Reactor Unit #" .. id,alignment=ALIGN.CENTER,fg_bg=style.theme.header}
|
||||||
|
|
||||||
-----------------------------
|
-----------------------------
|
||||||
-- main stats and core map --
|
-- main stats and core map --
|
||||||
@ -81,20 +81,20 @@ local function init(parent, id)
|
|||||||
core_map.register(u_ps, "temp", core_map.update)
|
core_map.register(u_ps, "temp", core_map.update)
|
||||||
core_map.register(u_ps, "size", function (s) core_map.resize(s[1], s[2]) end)
|
core_map.register(u_ps, "size", function (s) core_map.resize(s[1], s[2]) end)
|
||||||
|
|
||||||
TextBox{parent=main,x=12,y=22,text="Heating Rate",height=1,width=12,fg_bg=style.label}
|
TextBox{parent=main,x=12,y=22,text="Heating Rate",width=12,fg_bg=style.label}
|
||||||
local heating_r = DataIndicator{parent=main,x=12,label="",format="%14.0f",value=0,unit="mB/t",commas=true,lu_colors=lu_cpair,width=19,fg_bg=s_field}
|
local heating_r = DataIndicator{parent=main,x=12,label="",format="%14.0f",value=0,unit="mB/t",commas=true,lu_colors=lu_cpair,width=19,fg_bg=s_field}
|
||||||
heating_r.register(u_ps, "heating_rate", heating_r.update)
|
heating_r.register(u_ps, "heating_rate", heating_r.update)
|
||||||
|
|
||||||
TextBox{parent=main,x=12,y=25,text="Commanded Burn Rate",height=1,width=19,fg_bg=style.label}
|
TextBox{parent=main,x=12,y=25,text="Commanded Burn Rate",width=19,fg_bg=style.label}
|
||||||
local burn_r = DataIndicator{parent=main,x=12,label="",format="%14.2f",value=0,unit="mB/t",lu_colors=lu_cpair,width=19,fg_bg=s_field}
|
local burn_r = DataIndicator{parent=main,x=12,label="",format="%14.2f",value=0,unit="mB/t",lu_colors=lu_cpair,width=19,fg_bg=s_field}
|
||||||
burn_r.register(u_ps, "burn_rate", burn_r.update)
|
burn_r.register(u_ps, "burn_rate", burn_r.update)
|
||||||
|
|
||||||
TextBox{parent=main,text="F",x=2,y=22,width=1,height=1,fg_bg=style.label}
|
TextBox{parent=main,text="F",x=2,y=22,width=1,fg_bg=style.label}
|
||||||
TextBox{parent=main,text="C",x=4,y=22,width=1,height=1,fg_bg=style.label}
|
TextBox{parent=main,text="C",x=4,y=22,width=1,fg_bg=style.label}
|
||||||
TextBox{parent=main,text="\x1a",x=6,y=24,width=1,height=1,fg_bg=style.label}
|
TextBox{parent=main,text="\x1a",x=6,y=24,width=1,fg_bg=style.label}
|
||||||
TextBox{parent=main,text="\x1a",x=6,y=25,width=1,height=1,fg_bg=style.label}
|
TextBox{parent=main,text="\x1a",x=6,y=25,width=1,fg_bg=style.label}
|
||||||
TextBox{parent=main,text="H",x=8,y=22,width=1,height=1,fg_bg=style.label}
|
TextBox{parent=main,text="H",x=8,y=22,width=1,fg_bg=style.label}
|
||||||
TextBox{parent=main,text="W",x=10,y=22,width=1,height=1,fg_bg=style.label}
|
TextBox{parent=main,text="W",x=10,y=22,width=1,fg_bg=style.label}
|
||||||
|
|
||||||
local fuel = VerticalBar{parent=main,x=2,y=23,fg_bg=cpair(style.theme.fuel_color,colors.gray),height=4,width=1}
|
local fuel = VerticalBar{parent=main,x=2,y=23,fg_bg=cpair(style.theme.fuel_color,colors.gray),height=4,width=1}
|
||||||
local ccool = VerticalBar{parent=main,x=4,y=23,fg_bg=cpair(colors.blue,colors.gray),height=4,width=1}
|
local ccool = VerticalBar{parent=main,x=4,y=23,fg_bg=cpair(colors.blue,colors.gray),height=4,width=1}
|
||||||
@ -122,20 +122,20 @@ local function init(parent, id)
|
|||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
TextBox{parent=main,x=32,y=22,text="Core Temp",height=1,width=9,fg_bg=style.label}
|
TextBox{parent=main,x=32,y=22,text="Core Temp",width=9,fg_bg=style.label}
|
||||||
local fmt = util.trinary(string.len(db.temp_label) == 2, "%10.2f", "%11.2f")
|
local fmt = util.trinary(string.len(db.temp_label) == 2, "%10.2f", "%11.2f")
|
||||||
local core_temp = DataIndicator{parent=main,x=32,label="",format=fmt,value=0,commas=true,unit=db.temp_label,lu_colors=lu_cpair,width=13,fg_bg=s_field}
|
local core_temp = DataIndicator{parent=main,x=32,label="",format=fmt,value=0,commas=true,unit=db.temp_label,lu_colors=lu_cpair,width=13,fg_bg=s_field}
|
||||||
core_temp.register(u_ps, "temp", function (t) core_temp.update(db.temp_convert(t)) end)
|
core_temp.register(u_ps, "temp", function (t) core_temp.update(db.temp_convert(t)) end)
|
||||||
|
|
||||||
TextBox{parent=main,x=32,y=25,text="Burn Rate",height=1,width=9,fg_bg=style.label}
|
TextBox{parent=main,x=32,y=25,text="Burn Rate",width=9,fg_bg=style.label}
|
||||||
local act_burn_r = DataIndicator{parent=main,x=32,label="",format="%8.2f",value=0,unit="mB/t",lu_colors=lu_cpair,width=13,fg_bg=s_field}
|
local act_burn_r = DataIndicator{parent=main,x=32,label="",format="%8.2f",value=0,unit="mB/t",lu_colors=lu_cpair,width=13,fg_bg=s_field}
|
||||||
act_burn_r.register(u_ps, "act_burn_rate", act_burn_r.update)
|
act_burn_r.register(u_ps, "act_burn_rate", act_burn_r.update)
|
||||||
|
|
||||||
TextBox{parent=main,x=32,y=28,text="Damage",height=1,width=6,fg_bg=style.label}
|
TextBox{parent=main,x=32,y=28,text="Damage",width=6,fg_bg=style.label}
|
||||||
local damage_p = DataIndicator{parent=main,x=32,label="",format="%11.0f",value=0,unit="%",lu_colors=lu_cpair,width=13,fg_bg=s_field}
|
local damage_p = DataIndicator{parent=main,x=32,label="",format="%11.0f",value=0,unit="%",lu_colors=lu_cpair,width=13,fg_bg=s_field}
|
||||||
damage_p.register(u_ps, "damage", damage_p.update)
|
damage_p.register(u_ps, "damage", damage_p.update)
|
||||||
|
|
||||||
TextBox{parent=main,x=32,y=31,text="Radiation",height=1,width=21,fg_bg=style.label}
|
TextBox{parent=main,x=32,y=31,text="Radiation",width=21,fg_bg=style.label}
|
||||||
local radiation = RadIndicator{parent=main,x=32,label="",format="%9.3f",lu_colors=lu_cpair,width=13,fg_bg=s_field}
|
local radiation = RadIndicator{parent=main,x=32,label="",format="%9.3f",lu_colors=lu_cpair,width=13,fg_bg=s_field}
|
||||||
radiation.register(u_ps, "radiation", radiation.update)
|
radiation.register(u_ps, "radiation", radiation.update)
|
||||||
|
|
||||||
@ -144,8 +144,8 @@ local function init(parent, id)
|
|||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
local u_stat = Rectangle{parent=main,border=border(1,colors.gray,true),thin=true,width=33,height=4,x=46,y=3,fg_bg=bw_fg_bg}
|
local u_stat = Rectangle{parent=main,border=border(1,colors.gray,true),thin=true,width=33,height=4,x=46,y=3,fg_bg=bw_fg_bg}
|
||||||
local stat_line_1 = TextBox{parent=u_stat,x=1,y=1,text="UNKNOWN",width=33,height=1,alignment=ALIGN.CENTER,fg_bg=bw_fg_bg}
|
local stat_line_1 = TextBox{parent=u_stat,x=1,y=1,text="UNKNOWN",width=33,alignment=ALIGN.CENTER,fg_bg=bw_fg_bg}
|
||||||
local stat_line_2 = TextBox{parent=u_stat,x=1,y=2,text="awaiting data...",width=33,height=1,alignment=ALIGN.CENTER,fg_bg=gry_wht}
|
local stat_line_2 = TextBox{parent=u_stat,x=1,y=2,text="awaiting data...",width=33,alignment=ALIGN.CENTER,fg_bg=gry_wht}
|
||||||
|
|
||||||
stat_line_1.register(u_ps, "U_StatusLine1", stat_line_1.set_value)
|
stat_line_1.register(u_ps, "U_StatusLine1", stat_line_1.set_value)
|
||||||
stat_line_2.register(u_ps, "U_StatusLine2", stat_line_2.set_value)
|
stat_line_2.register(u_ps, "U_StatusLine2", stat_line_2.set_value)
|
||||||
@ -183,7 +183,7 @@ local function init(parent, id)
|
|||||||
local rad_wrn = IndicatorLight{parent=annunciator,label="Radiation Warning",colors=ind_yel}
|
local rad_wrn = IndicatorLight{parent=annunciator,label="Radiation Warning",colors=ind_yel}
|
||||||
local r_rtrip = IndicatorLight{parent=annunciator,label="RCP Trip",colors=ind_red}
|
local r_rtrip = IndicatorLight{parent=annunciator,label="RCP Trip",colors=ind_red}
|
||||||
local r_cflow = IndicatorLight{parent=annunciator,label="RCS Flow Low",colors=ind_yel}
|
local r_cflow = IndicatorLight{parent=annunciator,label="RCS Flow Low",colors=ind_yel}
|
||||||
local r_clow = IndicatorLight{parent=annunciator,label="Coolant Level Low",colors=ind_yel}
|
local r_clow = IndicatorLight{parent=annunciator,label="Coolant Level Low",colors=ind_yel}
|
||||||
local r_temp = IndicatorLight{parent=annunciator,label="Reactor Temp. High",colors=ind_red}
|
local r_temp = IndicatorLight{parent=annunciator,label="Reactor Temp. High",colors=ind_red}
|
||||||
local r_rhdt = IndicatorLight{parent=annunciator,label="Reactor High Delta T",colors=ind_yel}
|
local r_rhdt = IndicatorLight{parent=annunciator,label="Reactor High Delta T",colors=ind_yel}
|
||||||
local r_firl = IndicatorLight{parent=annunciator,label="Fuel Input Rate Low",colors=ind_yel}
|
local r_firl = IndicatorLight{parent=annunciator,label="Fuel Input Rate Low",colors=ind_yel}
|
||||||
@ -205,7 +205,7 @@ local function init(parent, id)
|
|||||||
|
|
||||||
-- RPS annunciator panel
|
-- RPS annunciator panel
|
||||||
|
|
||||||
TextBox{parent=main,text="REACTOR PROTECTION SYSTEM",fg_bg=cpair(colors.black,colors.cyan),alignment=ALIGN.CENTER,width=33,height=1,x=46,y=8}
|
TextBox{parent=main,text="REACTOR PROTECTION SYSTEM",fg_bg=cpair(colors.black,colors.cyan),alignment=ALIGN.CENTER,width=33,x=46,y=8}
|
||||||
local rps = Rectangle{parent=main,border=border(1,colors.cyan,true),thin=true,width=33,height=12,x=46,y=9}
|
local rps = Rectangle{parent=main,border=border(1,colors.cyan,true),thin=true,width=33,height=12,x=46,y=9}
|
||||||
local rps_annunc = Div{parent=rps,width=31,height=10,x=2,y=1}
|
local rps_annunc = Div{parent=rps,width=31,height=10,x=2,y=1}
|
||||||
|
|
||||||
@ -233,7 +233,7 @@ local function init(parent, id)
|
|||||||
|
|
||||||
-- cooling annunciator panel
|
-- cooling annunciator panel
|
||||||
|
|
||||||
TextBox{parent=main,text="REACTOR COOLANT SYSTEM",fg_bg=cpair(colors.black,colors.blue),alignment=ALIGN.CENTER,width=33,height=1,x=46,y=22}
|
TextBox{parent=main,text="REACTOR COOLANT SYSTEM",fg_bg=cpair(colors.black,colors.blue),alignment=ALIGN.CENTER,width=33,x=46,y=22}
|
||||||
local rcs = Rectangle{parent=main,border=border(1,colors.blue,true),thin=true,width=33,height=24,x=46,y=23}
|
local rcs = Rectangle{parent=main,border=border(1,colors.blue,true),thin=true,width=33,height=24,x=46,y=23}
|
||||||
local rcs_annunc = Div{parent=rcs,width=27,height=22,x=3,y=1}
|
local rcs_annunc = Div{parent=rcs,width=27,height=22,x=3,y=1}
|
||||||
local rcs_tags = Div{parent=rcs,width=2,height=16,x=1,y=7}
|
local rcs_tags = Div{parent=rcs,width=2,height=16,x=1,y=7}
|
||||||
@ -265,11 +265,11 @@ local function init(parent, id)
|
|||||||
if unit.num_boilers > 0 then
|
if unit.num_boilers > 0 then
|
||||||
if available_space > 0 then _add_space() end
|
if available_space > 0 then _add_space() end
|
||||||
|
|
||||||
TextBox{parent=rcs_tags,x=1,text="B1",width=2,height=1,fg_bg=hc_text}
|
TextBox{parent=rcs_tags,x=1,text="B1",width=2,fg_bg=hc_text}
|
||||||
local b1_wll = IndicatorLight{parent=rcs_annunc,label="Water Level Low",colors=ind_red}
|
local b1_wll = IndicatorLight{parent=rcs_annunc,label="Water Level Low",colors=ind_red}
|
||||||
b1_wll.register(b_ps[1], "WaterLevelLow", b1_wll.update)
|
b1_wll.register(b_ps[1], "WaterLevelLow", b1_wll.update)
|
||||||
|
|
||||||
TextBox{parent=rcs_tags,text="B1",width=2,height=1,fg_bg=hc_text}
|
TextBox{parent=rcs_tags,text="B1",width=2,fg_bg=hc_text}
|
||||||
local b1_hr = IndicatorLight{parent=rcs_annunc,label="Heating Rate Low",colors=ind_yel}
|
local b1_hr = IndicatorLight{parent=rcs_annunc,label="Heating Rate Low",colors=ind_yel}
|
||||||
b1_hr.register(b_ps[1], "HeatingRateLow", b1_hr.update)
|
b1_hr.register(b_ps[1], "HeatingRateLow", b1_hr.update)
|
||||||
end
|
end
|
||||||
@ -281,11 +281,11 @@ local function init(parent, id)
|
|||||||
_add_space()
|
_add_space()
|
||||||
end
|
end
|
||||||
|
|
||||||
TextBox{parent=rcs_tags,text="B2",width=2,height=1,fg_bg=hc_text}
|
TextBox{parent=rcs_tags,text="B2",width=2,fg_bg=hc_text}
|
||||||
local b2_wll = IndicatorLight{parent=rcs_annunc,label="Water Level Low",colors=ind_red}
|
local b2_wll = IndicatorLight{parent=rcs_annunc,label="Water Level Low",colors=ind_red}
|
||||||
b2_wll.register(b_ps[2], "WaterLevelLow", b2_wll.update)
|
b2_wll.register(b_ps[2], "WaterLevelLow", b2_wll.update)
|
||||||
|
|
||||||
TextBox{parent=rcs_tags,text="B2",width=2,height=1,fg_bg=hc_text}
|
TextBox{parent=rcs_tags,text="B2",width=2,fg_bg=hc_text}
|
||||||
local b2_hr = IndicatorLight{parent=rcs_annunc,label="Heating Rate Low",colors=ind_yel}
|
local b2_hr = IndicatorLight{parent=rcs_annunc,label="Heating Rate Low",colors=ind_yel}
|
||||||
b2_hr.register(b_ps[2], "HeatingRateLow", b2_hr.update)
|
b2_hr.register(b_ps[2], "HeatingRateLow", b2_hr.update)
|
||||||
end
|
end
|
||||||
@ -294,19 +294,19 @@ local function init(parent, id)
|
|||||||
|
|
||||||
if available_space > 1 then _add_space() end
|
if available_space > 1 then _add_space() end
|
||||||
|
|
||||||
TextBox{parent=rcs_tags,text="T1",width=2,height=1,fg_bg=hc_text}
|
TextBox{parent=rcs_tags,text="T1",width=2,fg_bg=hc_text}
|
||||||
local t1_sdo = TriIndicatorLight{parent=rcs_annunc,label="Steam Relief Valve Open",c1=ind_bkg,c2=ind_yel.fgd,c3=ind_red.fgd}
|
local t1_sdo = TriIndicatorLight{parent=rcs_annunc,label="Steam Relief Valve Open",c1=ind_bkg,c2=ind_yel.fgd,c3=ind_red.fgd}
|
||||||
t1_sdo.register(t_ps[1], "SteamDumpOpen", t1_sdo.update)
|
t1_sdo.register(t_ps[1], "SteamDumpOpen", t1_sdo.update)
|
||||||
|
|
||||||
TextBox{parent=rcs_tags,text="T1",width=2,height=1,fg_bg=hc_text}
|
TextBox{parent=rcs_tags,text="T1",width=2,fg_bg=hc_text}
|
||||||
local t1_tos = IndicatorLight{parent=rcs_annunc,label="Turbine Over Speed",colors=ind_red}
|
local t1_tos = IndicatorLight{parent=rcs_annunc,label="Turbine Over Speed",colors=ind_red}
|
||||||
t1_tos.register(t_ps[1], "TurbineOverSpeed", t1_tos.update)
|
t1_tos.register(t_ps[1], "TurbineOverSpeed", t1_tos.update)
|
||||||
|
|
||||||
TextBox{parent=rcs_tags,text="T1",width=2,height=1,fg_bg=hc_text}
|
TextBox{parent=rcs_tags,text="T1",width=2,fg_bg=hc_text}
|
||||||
local t1_gtrp = IndicatorLight{parent=rcs_annunc,label="Generator Trip",colors=ind_yel,flash=true,period=period.BLINK_250_MS}
|
local t1_gtrp = IndicatorLight{parent=rcs_annunc,label="Generator Trip",colors=ind_yel,flash=true,period=period.BLINK_250_MS}
|
||||||
t1_gtrp.register(t_ps[1], "GeneratorTrip", t1_gtrp.update)
|
t1_gtrp.register(t_ps[1], "GeneratorTrip", t1_gtrp.update)
|
||||||
|
|
||||||
TextBox{parent=rcs_tags,text="T1",width=2,height=1,fg_bg=hc_text}
|
TextBox{parent=rcs_tags,text="T1",width=2,fg_bg=hc_text}
|
||||||
local t1_trp = IndicatorLight{parent=rcs_annunc,label="Turbine Trip",colors=ind_red,flash=true,period=period.BLINK_250_MS}
|
local t1_trp = IndicatorLight{parent=rcs_annunc,label="Turbine Trip",colors=ind_red,flash=true,period=period.BLINK_250_MS}
|
||||||
t1_trp.register(t_ps[1], "TurbineTrip", t1_trp.update)
|
t1_trp.register(t_ps[1], "TurbineTrip", t1_trp.update)
|
||||||
|
|
||||||
@ -315,19 +315,19 @@ local function init(parent, id)
|
|||||||
_add_space()
|
_add_space()
|
||||||
end
|
end
|
||||||
|
|
||||||
TextBox{parent=rcs_tags,text="T2",width=2,height=1,fg_bg=hc_text}
|
TextBox{parent=rcs_tags,text="T2",width=2,fg_bg=hc_text}
|
||||||
local t2_sdo = TriIndicatorLight{parent=rcs_annunc,label="Steam Relief Valve Open",c1=ind_bkg,c2=ind_yel.fgd,c3=ind_red.fgd}
|
local t2_sdo = TriIndicatorLight{parent=rcs_annunc,label="Steam Relief Valve Open",c1=ind_bkg,c2=ind_yel.fgd,c3=ind_red.fgd}
|
||||||
t2_sdo.register(t_ps[2], "SteamDumpOpen", t2_sdo.update)
|
t2_sdo.register(t_ps[2], "SteamDumpOpen", t2_sdo.update)
|
||||||
|
|
||||||
TextBox{parent=rcs_tags,text="T2",width=2,height=1,fg_bg=hc_text}
|
TextBox{parent=rcs_tags,text="T2",width=2,fg_bg=hc_text}
|
||||||
local t2_tos = IndicatorLight{parent=rcs_annunc,label="Turbine Over Speed",colors=ind_red}
|
local t2_tos = IndicatorLight{parent=rcs_annunc,label="Turbine Over Speed",colors=ind_red}
|
||||||
t2_tos.register(t_ps[2], "TurbineOverSpeed", t2_tos.update)
|
t2_tos.register(t_ps[2], "TurbineOverSpeed", t2_tos.update)
|
||||||
|
|
||||||
TextBox{parent=rcs_tags,text="T2",width=2,height=1,fg_bg=hc_text}
|
TextBox{parent=rcs_tags,text="T2",width=2,fg_bg=hc_text}
|
||||||
local t2_gtrp = IndicatorLight{parent=rcs_annunc,label="Generator Trip",colors=ind_yel,flash=true,period=period.BLINK_250_MS}
|
local t2_gtrp = IndicatorLight{parent=rcs_annunc,label="Generator Trip",colors=ind_yel,flash=true,period=period.BLINK_250_MS}
|
||||||
t2_gtrp.register(t_ps[2], "GeneratorTrip", t2_gtrp.update)
|
t2_gtrp.register(t_ps[2], "GeneratorTrip", t2_gtrp.update)
|
||||||
|
|
||||||
TextBox{parent=rcs_tags,text="T2",width=2,height=1,fg_bg=hc_text}
|
TextBox{parent=rcs_tags,text="T2",width=2,fg_bg=hc_text}
|
||||||
local t2_trp = IndicatorLight{parent=rcs_annunc,label="Turbine Trip",colors=ind_red,flash=true,period=period.BLINK_250_MS}
|
local t2_trp = IndicatorLight{parent=rcs_annunc,label="Turbine Trip",colors=ind_red,flash=true,period=period.BLINK_250_MS}
|
||||||
t2_trp.register(t_ps[2], "TurbineTrip", t2_trp.update)
|
t2_trp.register(t_ps[2], "TurbineTrip", t2_trp.update)
|
||||||
end
|
end
|
||||||
@ -335,23 +335,25 @@ local function init(parent, id)
|
|||||||
if unit.num_turbines > 2 then
|
if unit.num_turbines > 2 then
|
||||||
if available_space > 3 then _add_space() end
|
if available_space > 3 then _add_space() end
|
||||||
|
|
||||||
TextBox{parent=rcs_tags,text="T3",width=2,height=1,fg_bg=hc_text}
|
TextBox{parent=rcs_tags,text="T3",width=2,fg_bg=hc_text}
|
||||||
local t3_sdo = TriIndicatorLight{parent=rcs_annunc,label="Steam Relief Valve Open",c1=ind_bkg,c2=ind_yel.fgd,c3=ind_red.fgd}
|
local t3_sdo = TriIndicatorLight{parent=rcs_annunc,label="Steam Relief Valve Open",c1=ind_bkg,c2=ind_yel.fgd,c3=ind_red.fgd}
|
||||||
t3_sdo.register(t_ps[3], "SteamDumpOpen", t3_sdo.update)
|
t3_sdo.register(t_ps[3], "SteamDumpOpen", t3_sdo.update)
|
||||||
|
|
||||||
TextBox{parent=rcs_tags,text="T3",width=2,height=1,fg_bg=hc_text}
|
TextBox{parent=rcs_tags,text="T3",width=2,fg_bg=hc_text}
|
||||||
local t3_tos = IndicatorLight{parent=rcs_annunc,label="Turbine Over Speed",colors=ind_red}
|
local t3_tos = IndicatorLight{parent=rcs_annunc,label="Turbine Over Speed",colors=ind_red}
|
||||||
t3_tos.register(t_ps[3], "TurbineOverSpeed", t3_tos.update)
|
t3_tos.register(t_ps[3], "TurbineOverSpeed", t3_tos.update)
|
||||||
|
|
||||||
TextBox{parent=rcs_tags,text="T3",width=2,height=1,fg_bg=hc_text}
|
TextBox{parent=rcs_tags,text="T3",width=2,fg_bg=hc_text}
|
||||||
local t3_gtrp = IndicatorLight{parent=rcs_annunc,label="Generator Trip",colors=ind_yel,flash=true,period=period.BLINK_250_MS}
|
local t3_gtrp = IndicatorLight{parent=rcs_annunc,label="Generator Trip",colors=ind_yel,flash=true,period=period.BLINK_250_MS}
|
||||||
t3_gtrp.register(t_ps[3], "GeneratorTrip", t3_gtrp.update)
|
t3_gtrp.register(t_ps[3], "GeneratorTrip", t3_gtrp.update)
|
||||||
|
|
||||||
TextBox{parent=rcs_tags,text="T3",width=2,height=1,fg_bg=hc_text}
|
TextBox{parent=rcs_tags,text="T3",width=2,fg_bg=hc_text}
|
||||||
local t3_trp = IndicatorLight{parent=rcs_annunc,label="Turbine Trip",colors=ind_red,flash=true,period=period.BLINK_250_MS}
|
local t3_trp = IndicatorLight{parent=rcs_annunc,label="Turbine Trip",colors=ind_red,flash=true,period=period.BLINK_250_MS}
|
||||||
t3_trp.register(t_ps[3], "TurbineTrip", t3_trp.update)
|
t3_trp.register(t_ps[3], "TurbineTrip", t3_trp.update)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
util.nop()
|
||||||
|
|
||||||
----------------------
|
----------------------
|
||||||
-- reactor controls --
|
-- reactor controls --
|
||||||
----------------------
|
----------------------
|
||||||
@ -392,7 +394,7 @@ local function init(parent, id)
|
|||||||
|
|
||||||
reset.register(u_ps, "rps_tripped", function (active) if active then reset.enable() else reset.disable() end end)
|
reset.register(u_ps, "rps_tripped", function (active) if active then reset.enable() else reset.disable() end end)
|
||||||
|
|
||||||
TextBox{parent=main,text="WASTE PROCESSING",fg_bg=cpair(colors.black,colors.brown),alignment=ALIGN.CENTER,width=33,height=1,x=46,y=48}
|
TextBox{parent=main,text="WASTE PROCESSING",fg_bg=cpair(colors.black,colors.brown),alignment=ALIGN.CENTER,width=33,x=46,y=48}
|
||||||
local waste_proc = Rectangle{parent=main,border=border(1,colors.brown,true),thin=true,width=33,height=3,x=46,y=49}
|
local waste_proc = Rectangle{parent=main,border=border(1,colors.brown,true),thin=true,width=33,height=3,x=46,y=49}
|
||||||
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}
|
||||||
|
|
||||||
@ -472,15 +474,15 @@ local function init(parent, id)
|
|||||||
|
|
||||||
-- color tags
|
-- color tags
|
||||||
|
|
||||||
TextBox{parent=alarm_panel,x=5,y=13,text="\x95",width=1,height=1,fg_bg=cpair(s_hi_bright.bkg,colors.cyan)}
|
TextBox{parent=alarm_panel,x=5,y=13,text="\x95",width=1,fg_bg=cpair(s_hi_bright.bkg,colors.cyan)}
|
||||||
TextBox{parent=alarm_panel,x=5,text="\x95",width=1,height=1,fg_bg=cpair(s_hi_bright.bkg,colors.blue)}
|
TextBox{parent=alarm_panel,x=5,text="\x95",width=1,fg_bg=cpair(s_hi_bright.bkg,colors.blue)}
|
||||||
TextBox{parent=alarm_panel,x=5,text="\x95",width=1,height=1,fg_bg=cpair(s_hi_bright.bkg,colors.blue)}
|
TextBox{parent=alarm_panel,x=5,text="\x95",width=1,fg_bg=cpair(s_hi_bright.bkg,colors.blue)}
|
||||||
|
|
||||||
--------------------------------
|
--------------------------------
|
||||||
-- automatic control settings --
|
-- automatic control settings --
|
||||||
--------------------------------
|
--------------------------------
|
||||||
|
|
||||||
TextBox{parent=main,text="AUTO CTRL",fg_bg=cpair(colors.black,colors.purple),alignment=ALIGN.CENTER,width=13,height=1,x=32,y=36}
|
TextBox{parent=main,text="AUTO CTRL",fg_bg=cpair(colors.black,colors.purple),alignment=ALIGN.CENTER,width=13,x=32,y=36}
|
||||||
local auto_ctl = Rectangle{parent=main,border=border(1,colors.purple,true),thin=true,width=13,height=15,x=32,y=37}
|
local auto_ctl = Rectangle{parent=main,border=border(1,colors.purple,true),thin=true,width=13,height=15,x=32,y=37}
|
||||||
local auto_div = Div{parent=auto_ctl,width=13,height=15,x=1,y=1}
|
local auto_div = Div{parent=auto_ctl,width=13,height=15,x=1,y=1}
|
||||||
|
|
||||||
@ -497,8 +499,8 @@ local function init(parent, id)
|
|||||||
|
|
||||||
auto_div.line_break()
|
auto_div.line_break()
|
||||||
|
|
||||||
TextBox{parent=auto_div,text="Prio. Group",height=1,width=11,fg_bg=style.label}
|
TextBox{parent=auto_div,text="Prio. Group",width=11,fg_bg=style.label}
|
||||||
local auto_grp = TextBox{parent=auto_div,text="Manual",height=1,width=11,fg_bg=s_field}
|
local auto_grp = TextBox{parent=auto_div,text="Manual",width=11,fg_bg=s_field}
|
||||||
|
|
||||||
auto_grp.register(u_ps, "auto_group", auto_grp.set_value)
|
auto_grp.register(u_ps, "auto_group", auto_grp.set_value)
|
||||||
|
|
||||||
|
@ -73,10 +73,10 @@ local function make(parent, x, y, wide, unit)
|
|||||||
------------------
|
------------------
|
||||||
|
|
||||||
local reactor = Rectangle{parent=root,x=1,y=1,border=border(1,colors.gray,true),width=19,height=5,fg_bg=wh_gray}
|
local reactor = Rectangle{parent=root,x=1,y=1,border=border(1,colors.gray,true),width=19,height=5,fg_bg=wh_gray}
|
||||||
TextBox{parent=reactor,y=1,text="FISSION REACTOR",alignment=ALIGN.CENTER,height=1}
|
TextBox{parent=reactor,y=1,text="FISSION REACTOR",alignment=ALIGN.CENTER}
|
||||||
TextBox{parent=reactor,y=3,text="UNIT #"..unit.unit_id,alignment=ALIGN.CENTER,height=1}
|
TextBox{parent=reactor,y=3,text="UNIT #"..unit.unit_id,alignment=ALIGN.CENTER}
|
||||||
TextBox{parent=root,x=19,y=2,text="\x1b \x80 \x1a",width=1,height=3,fg_bg=lg_gray}
|
TextBox{parent=root,x=19,y=2,text="\x1b \x80 \x1a",width=1,height=3,fg_bg=lg_gray}
|
||||||
TextBox{parent=root,x=3,y=5,text="\x19",width=1,height=1,fg_bg=lg_gray}
|
TextBox{parent=root,x=3,y=5,text="\x19",width=1,fg_bg=lg_gray}
|
||||||
|
|
||||||
local rc_pipes = {}
|
local rc_pipes = {}
|
||||||
|
|
||||||
@ -113,8 +113,8 @@ local function make(parent, x, y, wide, unit)
|
|||||||
hc_rate.register(unit.unit_ps, "heating_rate", hc_rate.update)
|
hc_rate.register(unit.unit_ps, "heating_rate", hc_rate.update)
|
||||||
|
|
||||||
local boiler = Rectangle{parent=root,x=_wide(47,40),y=1,border=border(1,colors.gray,true),width=19,height=5,fg_bg=wh_gray}
|
local boiler = Rectangle{parent=root,x=_wide(47,40),y=1,border=border(1,colors.gray,true),width=19,height=5,fg_bg=wh_gray}
|
||||||
TextBox{parent=boiler,y=1,text="THERMO-ELECTRIC",alignment=ALIGN.CENTER,height=1}
|
TextBox{parent=boiler,y=1,text="THERMO-ELECTRIC",alignment=ALIGN.CENTER}
|
||||||
TextBox{parent=boiler,y=3,text=util.trinary(unit.num_boilers>1,"BOILERS","BOILER"),alignment=ALIGN.CENTER,height=1}
|
TextBox{parent=boiler,y=3,text=util.trinary(unit.num_boilers>1,"BOILERS","BOILER"),alignment=ALIGN.CENTER}
|
||||||
TextBox{parent=root,x=_wide(47,40),y=2,text="\x1b \x80 \x1a",width=1,height=3,fg_bg=lg_gray}
|
TextBox{parent=root,x=_wide(47,40),y=2,text="\x1b \x80 \x1a",width=1,height=3,fg_bg=lg_gray}
|
||||||
TextBox{parent=root,x=_wide(65,58),y=2,text="\x1b \x80 \x1a",width=1,height=3,fg_bg=lg_gray}
|
TextBox{parent=root,x=_wide(65,58),y=2,text="\x1b \x80 \x1a",width=1,height=3,fg_bg=lg_gray}
|
||||||
|
|
||||||
@ -132,13 +132,13 @@ local function make(parent, x, y, wide, unit)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local turbine = Rectangle{parent=root,x=_wide(93,79),y=1,border=border(1,colors.gray,true),width=19,height=5,fg_bg=wh_gray}
|
local turbine = Rectangle{parent=root,x=_wide(93,79),y=1,border=border(1,colors.gray,true),width=19,height=5,fg_bg=wh_gray}
|
||||||
TextBox{parent=turbine,y=1,text="STEAM TURBINE",alignment=ALIGN.CENTER,height=1}
|
TextBox{parent=turbine,y=1,text="STEAM TURBINE",alignment=ALIGN.CENTER}
|
||||||
TextBox{parent=turbine,y=3,text=util.trinary(unit.num_turbines>1,"GENERATORS","GENERATOR"),alignment=ALIGN.CENTER,height=1}
|
TextBox{parent=turbine,y=3,text=util.trinary(unit.num_turbines>1,"GENERATORS","GENERATOR"),alignment=ALIGN.CENTER}
|
||||||
TextBox{parent=root,x=_wide(93,79),y=2,text="\x1b \x80 \x1a",width=1,height=3,fg_bg=lg_gray}
|
TextBox{parent=root,x=_wide(93,79),y=2,text="\x1b \x80 \x1a",width=1,height=3,fg_bg=lg_gray}
|
||||||
|
|
||||||
for i = 1, unit.num_turbines do
|
for i = 1, unit.num_turbines do
|
||||||
local ry = 1 + (2 * (i - 1)) + prv_yo
|
local ry = 1 + (2 * (i - 1)) + prv_yo
|
||||||
TextBox{parent=root,x=_wide(125,103),y=ry,text="\x10\x11\x7f",fg_bg=text_c,width=3,height=1}
|
TextBox{parent=root,x=_wide(125,103),y=ry,text="\x10\x11\x7f",fg_bg=text_c,width=3}
|
||||||
local state = TriIndicatorLight{parent=root,x=_wide(129,107),y=ry,label=v_names[i+4],c1=style.ind_bkg,c2=style.ind_yel.fgd,c3=style.ind_red.fgd}
|
local state = TriIndicatorLight{parent=root,x=_wide(129,107),y=ry,label=v_names[i+4],c1=style.ind_bkg,c2=style.ind_yel.fgd,c3=style.ind_red.fgd}
|
||||||
state.register(unit.turbine_ps_tbl[i], "SteamDumpOpen", state.update)
|
state.register(unit.turbine_ps_tbl[i], "SteamDumpOpen", state.update)
|
||||||
end
|
end
|
||||||
@ -172,7 +172,7 @@ local function make(parent, x, y, wide, unit)
|
|||||||
PipeNetwork{parent=waste,x=1,y=1,pipes=waste_pipes,bg=style.theme.bg}
|
PipeNetwork{parent=waste,x=1,y=1,pipes=waste_pipes,bg=style.theme.bg}
|
||||||
|
|
||||||
local function _valve(vx, vy, n)
|
local function _valve(vx, vy, n)
|
||||||
TextBox{parent=waste,x=vx,y=vy,text="\x10\x11",fg_bg=text_c,width=2,height=1}
|
TextBox{parent=waste,x=vx,y=vy,text="\x10\x11",fg_bg=text_c,width=2}
|
||||||
local conn = IndicatorLight{parent=waste,x=vx-3,y=vy+1,label=v_names[n],colors=ind_grn}
|
local conn = IndicatorLight{parent=waste,x=vx-3,y=vy+1,label=v_names[n],colors=ind_grn}
|
||||||
local open = IndicatorLight{parent=waste,x=vx-3,y=vy+2,label="OPEN",colors=ind_wht}
|
local open = IndicatorLight{parent=waste,x=vx-3,y=vy+2,label="OPEN",colors=ind_wht}
|
||||||
conn.register(unit.unit_ps, util.c("V_", v_fields[n], "_conn"), conn.update)
|
conn.register(unit.unit_ps, util.c("V_", v_fields[n], "_conn"), conn.update)
|
||||||
@ -181,8 +181,8 @@ local function make(parent, x, y, wide, unit)
|
|||||||
|
|
||||||
local function _machine(mx, my, name)
|
local function _machine(mx, my, name)
|
||||||
local l = string.len(name) + 2
|
local l = string.len(name) + 2
|
||||||
TextBox{parent=waste,x=mx,y=my,text=string.rep("\x8f",l),alignment=ALIGN.CENTER,fg_bg=cpair(style.theme.bg,style.theme.header.bkg),width=l,height=1}
|
TextBox{parent=waste,x=mx,y=my,text=string.rep("\x8f",l),alignment=ALIGN.CENTER,fg_bg=cpair(style.theme.bg,style.theme.header.bkg),width=l}
|
||||||
TextBox{parent=waste,x=mx,y=my+1,text=name,alignment=ALIGN.CENTER,fg_bg=style.theme.header,width=l,height=1}
|
TextBox{parent=waste,x=mx,y=my+1,text=name,alignment=ALIGN.CENTER,fg_bg=style.theme.header,width=l}
|
||||||
end
|
end
|
||||||
|
|
||||||
local waste_rate = DataIndicator{parent=waste,x=1,y=3,lu_colors=lu_c,label="",unit="mB/t",format="%7.2f",value=0,width=12,fg_bg=s_field}
|
local waste_rate = DataIndicator{parent=waste,x=1,y=3,lu_colors=lu_c,label="",unit="mB/t",format="%7.2f",value=0,width=12,fg_bg=s_field}
|
||||||
@ -209,7 +209,7 @@ local function make(parent, x, y, wide, unit)
|
|||||||
_machine(_wide(97, 83), 4, "PRC [Po] \x1a");
|
_machine(_wide(97, 83), 4, "PRC [Po] \x1a");
|
||||||
_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,height=1,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=7,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}
|
||||||
|
@ -44,7 +44,7 @@ local function make(parent, x, y, unit)
|
|||||||
local root = Div{parent=parent,x=x,y=y,width=80,height=height}
|
local root = Div{parent=parent,x=x,y=y,width=80,height=height}
|
||||||
|
|
||||||
-- unit header message
|
-- unit header message
|
||||||
TextBox{parent=root,text="Unit #"..unit.unit_id,alignment=ALIGN.CENTER,height=1,fg_bg=style.theme.header}
|
TextBox{parent=root,text="Unit #"..unit.unit_id,alignment=ALIGN.CENTER,fg_bg=style.theme.header}
|
||||||
|
|
||||||
-------------
|
-------------
|
||||||
-- REACTOR --
|
-- REACTOR --
|
||||||
|
@ -49,9 +49,9 @@ local function init(main)
|
|||||||
local tank_list = facility.tank_list
|
local tank_list = facility.tank_list
|
||||||
|
|
||||||
-- window header message
|
-- window header message
|
||||||
local header = TextBox{parent=main,y=1,text="Facility Coolant and Waste Flow Monitor",alignment=ALIGN.CENTER,height=1,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}
|
||||||
-- max length example: "01:23:45 AM - Wednesday, September 28 2022"
|
-- max length example: "01:23:45 AM - Wednesday, September 28 2022"
|
||||||
local datetime = TextBox{parent=main,x=(header.get_width()-42),y=1,text="",alignment=ALIGN.RIGHT,width=42,height=1,fg_bg=style.theme.header}
|
local datetime = TextBox{parent=main,x=(header.get_width()-42),y=1,text="",alignment=ALIGN.RIGHT,width=42,fg_bg=style.theme.header}
|
||||||
|
|
||||||
datetime.register(facility.ps, "date_time", datetime.set_value)
|
datetime.register(facility.ps, "date_time", datetime.set_value)
|
||||||
|
|
||||||
@ -250,6 +250,7 @@ local function init(main)
|
|||||||
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, #water_pipes == 0, units[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.cyan, true, true))
|
||||||
|
util.nop()
|
||||||
end
|
end
|
||||||
|
|
||||||
PipeNetwork{parent=main,x=139,y=15,pipes=po_pipes,bg=style.theme.bg}
|
PipeNetwork{parent=main,x=139,y=15,pipes=po_pipes,bg=style.theme.bg}
|
||||||
@ -264,7 +265,7 @@ local function init(main)
|
|||||||
if tank_defs[i] > 0 then
|
if tank_defs[i] > 0 then
|
||||||
local vy = 3 + y_ofs(i)
|
local vy = 3 + y_ofs(i)
|
||||||
|
|
||||||
TextBox{parent=main,x=12,y=vy,text="\x10\x11",fg_bg=text_col,width=2,height=1}
|
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 * 5),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}
|
||||||
@ -291,21 +292,21 @@ local function init(main)
|
|||||||
|
|
||||||
local tank = Div{parent=main,x=3,y=7+y_offset,width=20,height=14}
|
local tank = Div{parent=main,x=3,y=7+y_offset,width=20,height=14}
|
||||||
|
|
||||||
TextBox{parent=tank,text=" ",height=1,x=1,y=1,fg_bg=style.lg_gray}
|
TextBox{parent=tank,text=" ",x=1,y=1,fg_bg=style.lg_gray}
|
||||||
TextBox{parent=tank,text="DYNAMIC TANK "..id,alignment=ALIGN.CENTER,height=1,fg_bg=style.wh_gray}
|
TextBox{parent=tank,text="DYNAMIC TANK "..id,alignment=ALIGN.CENTER,fg_bg=style.wh_gray}
|
||||||
|
|
||||||
local tank_box = Rectangle{parent=tank,border=border(1,colors.gray,true),width=20,height=12}
|
local tank_box = Rectangle{parent=tank,border=border(1,colors.gray,true),width=20,height=12}
|
||||||
|
|
||||||
local status = StateIndicator{parent=tank_box,x=3,y=1,states=style.dtank.states,value=1,min_width=14}
|
local status = StateIndicator{parent=tank_box,x=3,y=1,states=style.dtank.states,value=1,min_width=14}
|
||||||
|
|
||||||
TextBox{parent=tank_box,x=2,y=3,text="Fill",height=1,width=10,fg_bg=style.label}
|
TextBox{parent=tank_box,x=2,y=3,text="Fill",width=10,fg_bg=style.label}
|
||||||
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",height=1,width=11,fg_bg=style.label}
|
TextBox{parent=tank_box,x=2,y=6,text="Water Level",width=11,fg_bg=style.label}
|
||||||
local level = HorizontalBar{parent=tank_box,x=2,y=7,bar_fg_bg=cpair(colors.blue,colors.gray),height=1,width=16}
|
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=9,text="In/Out Mode",height=1,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}
|
||||||
local can_empty = IndicatorLight{parent=tank_box,x=10,y=10,label="EMPTY",colors=style.ind_wht}
|
local can_empty = IndicatorLight{parent=tank_box,x=10,y=10,label="EMPTY",colors=style.ind_wht}
|
||||||
|
|
||||||
@ -335,14 +336,16 @@ local function init(main)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
util.nop()
|
||||||
|
|
||||||
---------
|
---------
|
||||||
-- SPS --
|
-- SPS --
|
||||||
---------
|
---------
|
||||||
|
|
||||||
local sps = Div{parent=main,x=140,y=3,height=12}
|
local sps = Div{parent=main,x=140,y=3,height=12}
|
||||||
|
|
||||||
TextBox{parent=sps,text=" ",width=24,height=1,x=1,y=1,fg_bg=style.lg_gray}
|
TextBox{parent=sps,text=" ",width=24,x=1,y=1,fg_bg=style.lg_gray}
|
||||||
TextBox{parent=sps,text="SPS",alignment=ALIGN.CENTER,width=24,height=1,fg_bg=wh_gray}
|
TextBox{parent=sps,text="SPS",alignment=ALIGN.CENTER,width=24,fg_bg=wh_gray}
|
||||||
|
|
||||||
local sps_box = Rectangle{parent=sps,border=border(1,colors.gray,true),width=24,height=10}
|
local sps_box = Rectangle{parent=sps,border=border(1,colors.gray,true),width=24,height=10}
|
||||||
|
|
||||||
@ -350,12 +353,12 @@ local function init(main)
|
|||||||
|
|
||||||
status.register(facility.sps_ps_tbl[1], "computed_status", status.update)
|
status.register(facility.sps_ps_tbl[1], "computed_status", status.update)
|
||||||
|
|
||||||
TextBox{parent=sps_box,x=2,y=3,text="Input Rate",height=1,width=10,fg_bg=style.label}
|
TextBox{parent=sps_box,x=2,y=3,text="Input Rate",width=10,fg_bg=style.label}
|
||||||
local sps_in = DataIndicator{parent=sps_box,x=2,label="",format="%15.2f",value=0,unit="mB/t",lu_colors=lu_col,width=20,fg_bg=s_field}
|
local sps_in = DataIndicator{parent=sps_box,x=2,label="",format="%15.2f",value=0,unit="mB/t",lu_colors=lu_col,width=20,fg_bg=s_field}
|
||||||
|
|
||||||
sps_in.register(facility.ps, "po_am_rate", sps_in.update)
|
sps_in.register(facility.ps, "po_am_rate", sps_in.update)
|
||||||
|
|
||||||
TextBox{parent=sps_box,x=2,y=6,text="Production Rate",height=1,width=15,fg_bg=style.label}
|
TextBox{parent=sps_box,x=2,y=6,text="Production Rate",width=15,fg_bg=style.label}
|
||||||
local sps_rate = DataIndicator{parent=sps_box,x=2,label="",format="%15d",value=0,unit="\xb5B/t",lu_colors=lu_col,width=20,fg_bg=s_field}
|
local sps_rate = DataIndicator{parent=sps_box,x=2,label="",format="%15d",value=0,unit="\xb5B/t",lu_colors=lu_col,width=20,fg_bg=s_field}
|
||||||
|
|
||||||
sps_rate.register(facility.sps_ps_tbl[1], "process_rate", function (r) sps_rate.update(r * 1000) end)
|
sps_rate.register(facility.sps_ps_tbl[1], "process_rate", function (r) sps_rate.update(r * 1000) end)
|
||||||
@ -364,13 +367,13 @@ local function init(main)
|
|||||||
-- statistics --
|
-- statistics --
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
TextBox{parent=main,x=145,y=16,text="RAW WASTE",alignment=ALIGN.CENTER,width=19,height=1,fg_bg=wh_gray}
|
TextBox{parent=main,x=145,y=16,text="RAW WASTE",alignment=ALIGN.CENTER,width=19,fg_bg=wh_gray}
|
||||||
local raw_waste = Rectangle{parent=main,x=145,y=17,border=border(1,colors.gray,true),width=19,height=3,thin=true,fg_bg=s_hi_bright}
|
local raw_waste = Rectangle{parent=main,x=145,y=17,border=border(1,colors.gray,true),width=19,height=3,thin=true,fg_bg=s_hi_bright}
|
||||||
local sum_raw_waste = DataIndicator{parent=raw_waste,lu_colors=lu_c_d,label="SUM",unit="mB/t",format="%8.2f",value=0,width=17}
|
local sum_raw_waste = DataIndicator{parent=raw_waste,lu_colors=lu_c_d,label="SUM",unit="mB/t",format="%8.2f",value=0,width=17}
|
||||||
|
|
||||||
sum_raw_waste.register(facility.ps, "burn_sum", sum_raw_waste.update)
|
sum_raw_waste.register(facility.ps, "burn_sum", sum_raw_waste.update)
|
||||||
|
|
||||||
TextBox{parent=main,x=145,y=21,text="PROC. WASTE",alignment=ALIGN.CENTER,width=19,height=1,fg_bg=wh_gray}
|
TextBox{parent=main,x=145,y=21,text="PROC. WASTE",alignment=ALIGN.CENTER,width=19,fg_bg=wh_gray}
|
||||||
local pr_waste = Rectangle{parent=main,x=145,y=22,border=border(1,colors.gray,true),width=19,height=5,thin=true,fg_bg=s_hi_bright}
|
local pr_waste = Rectangle{parent=main,x=145,y=22,border=border(1,colors.gray,true),width=19,height=5,thin=true,fg_bg=s_hi_bright}
|
||||||
local pu = DataIndicator{parent=pr_waste,lu_colors=lu_c_d,label="Pu",unit="mB/t",format="%9.3f",value=0,width=17}
|
local pu = DataIndicator{parent=pr_waste,lu_colors=lu_c_d,label="Pu",unit="mB/t",format="%9.3f",value=0,width=17}
|
||||||
local po = DataIndicator{parent=pr_waste,lu_colors=lu_c_d,label="Po",unit="mB/t",format="%9.2f",value=0,width=17}
|
local po = DataIndicator{parent=pr_waste,lu_colors=lu_c_d,label="Po",unit="mB/t",format="%9.2f",value=0,width=17}
|
||||||
@ -380,7 +383,7 @@ local function init(main)
|
|||||||
po.register(facility.ps, "po_rate", po.update)
|
po.register(facility.ps, "po_rate", po.update)
|
||||||
popl.register(facility.ps, "po_pl_rate", popl.update)
|
popl.register(facility.ps, "po_pl_rate", popl.update)
|
||||||
|
|
||||||
TextBox{parent=main,x=145,y=28,text="SPENT WASTE",alignment=ALIGN.CENTER,width=19,height=1,fg_bg=wh_gray}
|
TextBox{parent=main,x=145,y=28,text="SPENT WASTE",alignment=ALIGN.CENTER,width=19,fg_bg=wh_gray}
|
||||||
local sp_waste = Rectangle{parent=main,x=145,y=29,border=border(1,colors.gray,true),width=19,height=3,thin=true,fg_bg=s_hi_bright}
|
local sp_waste = Rectangle{parent=main,x=145,y=29,border=border(1,colors.gray,true),width=19,height=3,thin=true,fg_bg=s_hi_bright}
|
||||||
local sum_sp_waste = DataIndicator{parent=sp_waste,lu_colors=lu_c_d,label="SUM",unit="mB/t",format="%8.3f",value=0,width=17}
|
local sum_sp_waste = DataIndicator{parent=sp_waste,lu_colors=lu_c_d,label="SUM",unit="mB/t",format="%8.3f",value=0,width=17}
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ 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
|
||||||
|
|
||||||
TextBox{parent=panel,y=1,text="SCADA COORDINATOR",alignment=ALIGN.CENTER,height=1,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,12 +61,12 @@ 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,colors.gray}}
|
local network = RGBLED{parent=system,label="NETWORK",colors={colors.green,colors.red,colors.orange,colors.yellow,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
|
||||||
local nt_lnk = LEDPair{parent=system,label="NT LINKED",off=colors.red_off,c1=colors.red,c2=colors.green}
|
local nt_lnk = LEDPair{parent=system,label="NT LINKED",off=style.fp_ind_bkg,c1=colors.red,c2=colors.green}
|
||||||
local nt_ver = LEDPair{parent=system,label="NT VERSION",off=colors.red_off,c1=colors.red,c2=colors.green}
|
local nt_ver = LEDPair{parent=system,label="NT VERSION",off=style.fp_ind_bkg,c1=colors.red,c2=colors.green}
|
||||||
|
|
||||||
nt_lnk.register(ps, "link_state", function (state)
|
nt_lnk.register(ps, "link_state", function (state)
|
||||||
local value = 2
|
local value = 2
|
||||||
@ -100,9 +100,17 @@ local function init(panel, num_units)
|
|||||||
local speaker = LED{parent=system,label="SPEAKER",colors=led_grn}
|
local speaker = LED{parent=system,label="SPEAKER",colors=led_grn}
|
||||||
speaker.register(ps, "has_speaker", speaker.update)
|
speaker.register(ps, "has_speaker", speaker.update)
|
||||||
|
|
||||||
|
system.line_break()
|
||||||
|
|
||||||
|
local rt_main = LED{parent=system,label="RT MAIN",colors=led_grn}
|
||||||
|
local rt_render = LED{parent=system,label="RT RENDER",colors=led_grn}
|
||||||
|
|
||||||
|
rt_main.register(ps, "routine__main", rt_main.update)
|
||||||
|
rt_render.register(ps, "routine__render", rt_render.update)
|
||||||
|
|
||||||
---@diagnostic disable-next-line: undefined-field
|
---@diagnostic disable-next-line: undefined-field
|
||||||
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,height=1,text=comp_id,fg_bg=style.fp.disabled_fg}
|
TextBox{parent=system,x=9,y=4,width=6,text=comp_id,fg_bg=style.fp.disabled_fg}
|
||||||
|
|
||||||
local monitors = Div{parent=main_page,width=16,height=17,x=18,y=2}
|
local monitors = Div{parent=main_page,width=16,height=17,x=18,y=2}
|
||||||
|
|
||||||
@ -124,8 +132,8 @@ local function init(panel, num_units)
|
|||||||
--
|
--
|
||||||
|
|
||||||
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=3,x=1,y=16,fg_bg=style.fp.disabled_fg}
|
||||||
local fw_v = TextBox{parent=about,x=1,y=1,text="FW: v00.00.00",alignment=ALIGN.LEFT,height=1}
|
local fw_v = TextBox{parent=about,x=1,y=1,text="FW: v00.00.00"}
|
||||||
local comms_v = TextBox{parent=about,x=1,y=2,text="NT: v00.00.00",alignment=ALIGN.LEFT,height=1}
|
local comms_v = TextBox{parent=about,x=1,y=2,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)
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
-- Main SCADA Coordinator GUI
|
-- Main SCADA Coordinator GUI
|
||||||
--
|
--
|
||||||
|
|
||||||
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
local iocontrol = require("coordinator.iocontrol")
|
local iocontrol = require("coordinator.iocontrol")
|
||||||
|
|
||||||
local style = require("coordinator.ui.style")
|
local style = require("coordinator.ui.style")
|
||||||
@ -27,10 +29,10 @@ local function init(main)
|
|||||||
local units = iocontrol.get_db().units
|
local units = iocontrol.get_db().units
|
||||||
|
|
||||||
-- window header message
|
-- window header message
|
||||||
local header = TextBox{parent=main,y=1,text="Nuclear Generation Facility SCADA Coordinator",alignment=ALIGN.CENTER,height=1,fg_bg=s_header}
|
local header = TextBox{parent=main,y=1,text="Nuclear Generation Facility SCADA Coordinator",alignment=ALIGN.CENTER,fg_bg=s_header}
|
||||||
local ping = DataIndicator{parent=main,x=1,y=1,label="SVTT",format="%d",value=0,unit="ms",lu_colors=style.lg_white,width=12,fg_bg=s_header}
|
local ping = DataIndicator{parent=main,x=1,y=1,label="SVTT",format="%d",value=0,unit="ms",lu_colors=style.lg_white,width=12,fg_bg=s_header}
|
||||||
-- max length example: "01:23:45 AM - Wednesday, September 28 2022"
|
-- max length example: "01:23:45 AM - Wednesday, September 28 2022"
|
||||||
local datetime = TextBox{parent=main,x=(header.get_width()-42),y=1,text="",alignment=ALIGN.RIGHT,width=42,height=1,fg_bg=s_header}
|
local datetime = TextBox{parent=main,x=(header.get_width()-42),y=1,text="",alignment=ALIGN.RIGHT,width=42,fg_bg=s_header}
|
||||||
|
|
||||||
ping.register(facility.ps, "sv_ping", ping.update)
|
ping.register(facility.ps, "sv_ping", ping.update)
|
||||||
datetime.register(facility.ps, "date_time", datetime.set_value)
|
datetime.register(facility.ps, "date_time", datetime.set_value)
|
||||||
@ -53,6 +55,8 @@ local function init(main)
|
|||||||
|
|
||||||
cnc_y_start = cnc_y_start + row_1_height + 1
|
cnc_y_start = cnc_y_start + row_1_height + 1
|
||||||
|
|
||||||
|
util.nop()
|
||||||
|
|
||||||
if facility.num_units >= 3 then
|
if facility.num_units >= 3 then
|
||||||
-- base offset 3, spacing 1, max height of units 1 and 2
|
-- base offset 3, spacing 1, max height of units 1 and 2
|
||||||
local row_2_offset = cnc_y_start
|
local row_2_offset = cnc_y_start
|
||||||
@ -64,6 +68,8 @@ local function init(main)
|
|||||||
uo_4 = unit_overview(main, 84, row_2_offset, units[4])
|
uo_4 = unit_overview(main, 84, row_2_offset, units[4])
|
||||||
cnc_y_start = math.max(cnc_y_start, row_2_offset + uo_4.get_height() + 1)
|
cnc_y_start = math.max(cnc_y_start, row_2_offset + uo_4.get_height() + 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
util.nop()
|
||||||
end
|
end
|
||||||
|
|
||||||
-- command & control
|
-- command & control
|
||||||
@ -73,12 +79,14 @@ local function init(main)
|
|||||||
|
|
||||||
assert(cnc_bottom_align_start >= cnc_y_start, "main display not of sufficient vertical resolution (add an additional row of monitors)")
|
assert(cnc_bottom_align_start >= cnc_y_start, "main display not of sufficient vertical resolution (add an additional row of monitors)")
|
||||||
|
|
||||||
TextBox{parent=main,y=cnc_bottom_align_start,text=string.rep("\x8c", header.get_width()),alignment=ALIGN.CENTER,height=1,fg_bg=style.lg_gray}
|
TextBox{parent=main,y=cnc_bottom_align_start,text=string.rep("\x8c", header.get_width()),alignment=ALIGN.CENTER,fg_bg=style.lg_gray}
|
||||||
|
|
||||||
cnc_bottom_align_start = cnc_bottom_align_start + 2
|
cnc_bottom_align_start = cnc_bottom_align_start + 2
|
||||||
|
|
||||||
process_ctl(main, 2, cnc_bottom_align_start)
|
process_ctl(main, 2, cnc_bottom_align_start)
|
||||||
|
|
||||||
|
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_data_tbl[1], facility.induction_ps_tbl[1])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -88,17 +88,19 @@ style.theme = smooth_stone
|
|||||||
---@param fp FP_THEME front panel theme
|
---@param fp FP_THEME front panel theme
|
||||||
---@param color_mode COLOR_MODE the color mode to use
|
---@param color_mode COLOR_MODE the color mode to use
|
||||||
function style.set_themes(main, fp, color_mode)
|
function style.set_themes(main, fp, color_mode)
|
||||||
local colorblind = color_mode ~= themes.COLOR_MODE.STANDARD
|
local colorblind = color_mode ~= themes.COLOR_MODE.STANDARD and color_mode ~= themes.COLOR_MODE.STD_ON_BLACK
|
||||||
|
local gray_ind_off = color_mode == themes.COLOR_MODE.STANDARD or color_mode == themes.COLOR_MODE.BLUE_IND
|
||||||
|
|
||||||
style.ind_bkg = colors.gray
|
style.ind_bkg = colors.gray
|
||||||
style.ind_hi_box_bg = util.trinary(colorblind, colors.black, colors.gray)
|
style.fp_ind_bkg = util.trinary(gray_ind_off, colors.gray, colors.black)
|
||||||
|
style.ind_hi_box_bg = util.trinary(gray_ind_off, colors.gray, colors.black)
|
||||||
|
|
||||||
if main == themes.UI_THEME.SMOOTH_STONE then
|
if main == themes.UI_THEME.SMOOTH_STONE then
|
||||||
style.theme = smooth_stone
|
style.theme = smooth_stone
|
||||||
style.ind_bkg = util.trinary(colorblind, colors.black, colors.gray)
|
style.ind_bkg = util.trinary(gray_ind_off, colors.gray, colors.black)
|
||||||
elseif main == themes.UI_THEME.DEEPSLATE then
|
elseif main == themes.UI_THEME.DEEPSLATE then
|
||||||
style.theme = deepslate
|
style.theme = deepslate
|
||||||
style.ind_hi_box_bg = util.trinary(colorblind, colors.black, colors.lightGray)
|
style.ind_hi_box_bg = util.trinary(gray_ind_off, colors.lightGray, colors.black)
|
||||||
end
|
end
|
||||||
|
|
||||||
style.colorblind = colorblind
|
style.colorblind = colorblind
|
||||||
|
@ -7,7 +7,7 @@ local flasher = require("graphics.flasher")
|
|||||||
|
|
||||||
local core = {}
|
local core = {}
|
||||||
|
|
||||||
core.version = "2.2.2"
|
core.version = "2.3.3"
|
||||||
|
|
||||||
core.flasher = flasher
|
core.flasher = flasher
|
||||||
core.events = events
|
core.events = events
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
-- 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")
|
||||||
@ -49,9 +50,11 @@ local element = {}
|
|||||||
---|indicator_light_args
|
---|indicator_light_args
|
||||||
---|power_indicator_args
|
---|power_indicator_args
|
||||||
---|rad_indicator_args
|
---|rad_indicator_args
|
||||||
|
---|signal_bar_args
|
||||||
---|state_indicator_args
|
---|state_indicator_args
|
||||||
---|tristate_indicator_light_args
|
---|tristate_indicator_light_args
|
||||||
---|vbar_args
|
---|vbar_args
|
||||||
|
---|app_multipane_args
|
||||||
---|colormap_args
|
---|colormap_args
|
||||||
---|displaybox_args
|
---|displaybox_args
|
||||||
---|div_args
|
---|div_args
|
||||||
@ -80,9 +83,10 @@ end
|
|||||||
-- a base graphics element, should not be created on its own
|
-- a base graphics element, should not be created on its own
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
---@param args graphics_args arguments
|
---@param args graphics_args arguments
|
||||||
|
---@param constraint? function apply a dimensional constraint based on proposed dimensions function(frame) -> width, height
|
||||||
---@param child_offset_x? integer mouse event offset x
|
---@param child_offset_x? integer mouse event offset x
|
||||||
---@param child_offset_y? integer mouse event offset y
|
---@param child_offset_y? integer mouse event offset y
|
||||||
function element.new(args, child_offset_x, child_offset_y)
|
function element.new(args, constraint, child_offset_x, child_offset_y)
|
||||||
local self = {
|
local self = {
|
||||||
id = nil, ---@type element_id|nil
|
id = nil, ---@type element_id|nil
|
||||||
is_root = args.parent == nil,
|
is_root = args.parent == nil,
|
||||||
@ -196,6 +200,9 @@ function element.new(args, child_offset_x, child_offset_y)
|
|||||||
---@param offset_y integer y offset for mouse events
|
---@param offset_y integer y offset for mouse events
|
||||||
---@param next_y integer next line if no y was provided
|
---@param next_y integer next line if no y was provided
|
||||||
function protected.prepare_template(offset_x, offset_y, next_y)
|
function protected.prepare_template(offset_x, offset_y, next_y)
|
||||||
|
-- don't auto incrememnt y if inheriting height, that would cause an assertion
|
||||||
|
next_y = util.trinary(args.height == nil and constraint == nil, 1, next_y)
|
||||||
|
|
||||||
-- record offsets in case there is a reposition
|
-- record offsets in case there is a reposition
|
||||||
self.offset_x = offset_x
|
self.offset_x = offset_x
|
||||||
self.offset_y = offset_y
|
self.offset_y = offset_y
|
||||||
@ -221,6 +228,13 @@ function element.new(args, child_offset_x, child_offset_y)
|
|||||||
local w, h = self.p_window.getSize()
|
local w, h = self.p_window.getSize()
|
||||||
f.w = math.min(f.w, w - (f.x - 1))
|
f.w = math.min(f.w, w - (f.x - 1))
|
||||||
f.h = math.min(f.h, h - (f.y - 1))
|
f.h = math.min(f.h, h - (f.y - 1))
|
||||||
|
|
||||||
|
if type(constraint) == "function" then
|
||||||
|
-- constrain per provided constraint function (can only get smaller than available space)
|
||||||
|
w, h = constraint(f)
|
||||||
|
f.w = math.min(f.w, w)
|
||||||
|
f.h = math.min(f.h, h)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- check frame
|
-- check frame
|
||||||
@ -490,7 +504,10 @@ function element.new(args, 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
|
||||||
|
|
||||||
|
109
graphics/elements/appmultipane.lua
Normal file
109
graphics/elements/appmultipane.lua
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
-- App Page Multi-Pane Display Graphics Element
|
||||||
|
|
||||||
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
|
local core = require("graphics.core")
|
||||||
|
local element = require("graphics.element")
|
||||||
|
local events = require("graphics.events")
|
||||||
|
|
||||||
|
local MOUSE_CLICK = core.events.MOUSE_CLICK
|
||||||
|
|
||||||
|
---@class app_multipane_args
|
||||||
|
---@field panes table panes to swap between
|
||||||
|
---@field nav_colors cpair on/off colors (a/b respectively) for page navigator
|
||||||
|
---@field scroll_nav boolean? true to allow scrolling to change the active pane
|
||||||
|
---@field drag_nav boolean? true to allow mouse dragging to change the active pane (on mouse up)
|
||||||
|
---@field callback function? function to call when pane is changed by mouse interaction
|
||||||
|
---@field parent graphics_element
|
||||||
|
---@field id? string element id
|
||||||
|
---@field x? integer 1 if omitted
|
||||||
|
---@field y? integer auto incremented if omitted
|
||||||
|
---@field width? integer parent width if omitted
|
||||||
|
---@field height? integer parent height if omitted
|
||||||
|
---@field gframe? graphics_frame frame instead of x/y/width/height
|
||||||
|
---@field fg_bg? cpair foreground/background colors
|
||||||
|
---@field hidden? boolean true to hide on initial draw
|
||||||
|
|
||||||
|
-- new app multipane element
|
||||||
|
---@nodiscard
|
||||||
|
---@param args app_multipane_args
|
||||||
|
---@return graphics_element element, element_id id
|
||||||
|
local function multipane(args)
|
||||||
|
element.assert(type(args.panes) == "table", "panes is a required field")
|
||||||
|
|
||||||
|
-- create new graphics element base object
|
||||||
|
local e = element.new(args)
|
||||||
|
|
||||||
|
e.value = 1
|
||||||
|
|
||||||
|
local nav_x_start = math.floor((e.frame.w / 2) - (#args.panes / 2)) + 1
|
||||||
|
local nav_x_end = math.floor((e.frame.w / 2) - (#args.panes / 2)) + #args.panes
|
||||||
|
|
||||||
|
-- show the selected pane
|
||||||
|
function e.redraw()
|
||||||
|
for i = 1, #args.panes do args.panes[i].hide() end
|
||||||
|
args.panes[e.value].show()
|
||||||
|
|
||||||
|
-- draw page indicator dots
|
||||||
|
for i = 1, #args.panes do
|
||||||
|
e.w_set_cur(nav_x_start + (i - 1), e.frame.h)
|
||||||
|
e.w_set_fgd(util.trinary(i == e.value, args.nav_colors.color_a, args.nav_colors.color_b))
|
||||||
|
e.w_write("\x07")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- handle mouse interaction
|
||||||
|
---@param event mouse_interaction mouse event
|
||||||
|
function e.handle_mouse(event)
|
||||||
|
local initial = e.value
|
||||||
|
|
||||||
|
if e.enabled then
|
||||||
|
if event.current.y == e.frame.h and event.current.x >= nav_x_start and event.current.x <= nav_x_end then
|
||||||
|
local id = event.current.x - nav_x_start + 1
|
||||||
|
|
||||||
|
if event.type == MOUSE_CLICK.TAP then
|
||||||
|
e.set_value(id)
|
||||||
|
elseif event.type == MOUSE_CLICK.UP then
|
||||||
|
e.set_value(id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if args.scroll_nav then
|
||||||
|
if event.type == events.MOUSE_CLICK.SCROLL_DOWN then
|
||||||
|
e.set_value(e.value + 1)
|
||||||
|
elseif event.type == events.MOUSE_CLICK.SCROLL_UP then
|
||||||
|
e.set_value(e.value - 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if args.drag_nav then
|
||||||
|
local x1, x2 = event.initial.x, event.current.x
|
||||||
|
if event.type == events.MOUSE_CLICK.UP and e.in_frame_bounds(x1, event.initial.y) and e.in_frame_bounds(x1, event.current.y) then
|
||||||
|
if x2 > x1 then
|
||||||
|
e.set_value(e.value - 1)
|
||||||
|
elseif x2 < x1 then
|
||||||
|
e.set_value(e.value + 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if e.value ~= initial and type(args.callback) == "function" then args.callback(e.value) end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- select which pane is shown
|
||||||
|
---@param value integer pane to show
|
||||||
|
function e.set_value(value)
|
||||||
|
if (e.value ~= value) and (value > 0) and (value <= #args.panes) then
|
||||||
|
e.value = value
|
||||||
|
e.redraw()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- initial draw
|
||||||
|
e.redraw()
|
||||||
|
|
||||||
|
return e.complete()
|
||||||
|
end
|
||||||
|
|
||||||
|
return multipane
|
@ -30,7 +30,7 @@ local function app_button(args)
|
|||||||
element.assert(type(args.app_fg_bg) == "table", "app_fg_bg is a required field")
|
element.assert(type(args.app_fg_bg) == "table", "app_fg_bg is a required field")
|
||||||
|
|
||||||
args.height = 4
|
args.height = 4
|
||||||
args.width = 5
|
args.width = 7
|
||||||
|
|
||||||
-- create new graphics element base object
|
-- create new graphics element base object
|
||||||
local e = element.new(args)
|
local e = element.new(args)
|
||||||
@ -46,7 +46,7 @@ local function app_button(args)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- draw icon
|
-- draw icon
|
||||||
e.w_set_cur(1, 1)
|
e.w_set_cur(2, 1)
|
||||||
e.w_set_fgd(fgd)
|
e.w_set_fgd(fgd)
|
||||||
e.w_set_bkg(bkg)
|
e.w_set_bkg(bkg)
|
||||||
e.w_write("\x9f\x83\x83\x83")
|
e.w_write("\x9f\x83\x83\x83")
|
||||||
@ -55,16 +55,16 @@ local function app_button(args)
|
|||||||
e.w_write("\x90")
|
e.w_write("\x90")
|
||||||
e.w_set_fgd(fgd)
|
e.w_set_fgd(fgd)
|
||||||
e.w_set_bkg(bkg)
|
e.w_set_bkg(bkg)
|
||||||
e.w_set_cur(1, 2)
|
e.w_set_cur(2, 2)
|
||||||
e.w_write("\x95 ")
|
e.w_write("\x95 ")
|
||||||
e.w_set_fgd(bkg)
|
e.w_set_fgd(bkg)
|
||||||
e.w_set_bkg(fgd)
|
e.w_set_bkg(fgd)
|
||||||
e.w_write("\x95")
|
e.w_write("\x95")
|
||||||
e.w_set_cur(1, 3)
|
e.w_set_cur(2, 3)
|
||||||
e.w_write("\x82\x8f\x8f\x8f\x81")
|
e.w_write("\x82\x8f\x8f\x8f\x81")
|
||||||
|
|
||||||
-- write the icon text
|
-- write the icon text
|
||||||
e.w_set_cur(3, 2)
|
e.w_set_cur(4, 2)
|
||||||
e.w_set_fgd(fgd)
|
e.w_set_fgd(fgd)
|
||||||
e.w_set_bkg(bkg)
|
e.w_set_bkg(bkg)
|
||||||
e.w_write(args.text)
|
e.w_write(args.text)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
-- Button Graphics Element
|
-- Button Graphics Element
|
||||||
|
|
||||||
local tcd = require("scada-common.tcd")
|
local tcd = require("scada-common.tcd")
|
||||||
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
local core = require("graphics.core")
|
local core = require("graphics.core")
|
||||||
local element = require("graphics.element")
|
local element = require("graphics.element")
|
||||||
@ -21,7 +22,6 @@ local KEY_CLICK = core.events.KEY_CLICK
|
|||||||
---@field id? string element id
|
---@field id? string element id
|
||||||
---@field x? integer 1 if omitted
|
---@field x? integer 1 if omitted
|
||||||
---@field y? integer auto incremented if omitted
|
---@field y? integer auto incremented if omitted
|
||||||
---@field height? integer parent height if omitted
|
|
||||||
---@field fg_bg? cpair foreground/background colors
|
---@field fg_bg? cpair foreground/background colors
|
||||||
---@field hidden? boolean true to hide on initial draw
|
---@field hidden? boolean true to hide on initial draw
|
||||||
|
|
||||||
@ -38,29 +38,40 @@ local function push_button(args)
|
|||||||
|
|
||||||
-- set automatic settings
|
-- set automatic settings
|
||||||
args.can_focus = true
|
args.can_focus = true
|
||||||
args.height = 1
|
|
||||||
args.min_width = args.min_width or 0
|
args.min_width = args.min_width or 0
|
||||||
args.width = math.max(text_width, args.min_width)
|
args.width = math.max(text_width, args.min_width)
|
||||||
|
|
||||||
-- create new graphics element base object
|
-- provide a constraint condition to element creation to prefer a single line button
|
||||||
local e = element.new(args)
|
---@param frame graphics_frame
|
||||||
|
local function constrain(frame)
|
||||||
local h_pad = 1
|
return frame.w, math.max(1, #util.strwrap(args.text, frame.w))
|
||||||
local v_pad = math.floor(e.frame.h / 2) + 1
|
|
||||||
|
|
||||||
if alignment == ALIGN.CENTER then
|
|
||||||
h_pad = math.floor((e.frame.w - text_width) / 2) + 1
|
|
||||||
elseif alignment == ALIGN.RIGHT then
|
|
||||||
h_pad = (e.frame.w - text_width) + 1
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- create new graphics element base object
|
||||||
|
local e = element.new(args, constrain)
|
||||||
|
|
||||||
|
local text_lines = util.strwrap(args.text, e.frame.w)
|
||||||
|
|
||||||
-- draw the button
|
-- draw the button
|
||||||
function e.redraw()
|
function e.redraw()
|
||||||
e.window.clear()
|
e.window.clear()
|
||||||
|
|
||||||
-- write the button text
|
for i = 1, #text_lines do
|
||||||
e.w_set_cur(h_pad, v_pad)
|
if i > e.frame.h then break end
|
||||||
e.w_write(args.text)
|
|
||||||
|
local len = string.len(text_lines[i])
|
||||||
|
|
||||||
|
-- use cursor position to align this line
|
||||||
|
if alignment == ALIGN.CENTER then
|
||||||
|
e.w_set_cur(math.floor((e.frame.w - len) / 2) + 1, i)
|
||||||
|
elseif alignment == ALIGN.RIGHT then
|
||||||
|
e.w_set_cur((e.frame.w - len) + 1, i)
|
||||||
|
else
|
||||||
|
e.w_set_cur(1, i)
|
||||||
|
end
|
||||||
|
|
||||||
|
e.w_write(text_lines[i])
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- draw the button as pressed (if active_fg_bg set)
|
-- draw the button as pressed (if active_fg_bg set)
|
||||||
@ -109,7 +120,9 @@ local function push_button(args)
|
|||||||
if event.type == KEY_CLICK.DOWN then
|
if event.type == KEY_CLICK.DOWN then
|
||||||
if event.key == keys.space or event.key == keys.enter or event.key == keys.numPadEnter then
|
if event.key == keys.space or event.key == keys.enter or event.key == keys.numPadEnter then
|
||||||
args.callback()
|
args.callback()
|
||||||
e.defocus()
|
-- visualize click without unfocusing
|
||||||
|
show_unpressed()
|
||||||
|
if args.active_fg_bg ~= nil then tcd.dispatch(0.25, show_pressed) end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -8,13 +8,7 @@ local element = require("graphics.element")
|
|||||||
|
|
||||||
local MOUSE_CLICK = core.events.MOUSE_CLICK
|
local MOUSE_CLICK = core.events.MOUSE_CLICK
|
||||||
|
|
||||||
---@class sidebar_tab
|
|
||||||
---@field char string character identifier
|
|
||||||
---@field color cpair tab colors (fg/bg)
|
|
||||||
|
|
||||||
---@class sidebar_args
|
---@class sidebar_args
|
||||||
---@field tabs table sidebar tab options
|
|
||||||
---@field callback function function to call on tab change
|
|
||||||
---@field parent graphics_element
|
---@field parent graphics_element
|
||||||
---@field id? string element id
|
---@field id? string element id
|
||||||
---@field x? integer 1 if omitted
|
---@field x? integer 1 if omitted
|
||||||
@ -27,21 +21,16 @@ local MOUSE_CLICK = core.events.MOUSE_CLICK
|
|||||||
---@param args sidebar_args
|
---@param args sidebar_args
|
||||||
---@return graphics_element element, element_id id
|
---@return graphics_element element, element_id id
|
||||||
local function sidebar(args)
|
local function sidebar(args)
|
||||||
element.assert(type(args.tabs) == "table", "tabs is a required field")
|
|
||||||
element.assert(#args.tabs > 0, "at least one tab is required")
|
|
||||||
element.assert(type(args.callback) == "function", "callback is a required field")
|
|
||||||
|
|
||||||
args.width = 3
|
args.width = 3
|
||||||
|
|
||||||
-- create new graphics element base object
|
-- create new graphics element base object
|
||||||
local e = element.new(args)
|
local e = element.new(args)
|
||||||
|
|
||||||
element.assert(e.frame.h >= (#args.tabs * 3), "height insufficent to display all tabs")
|
|
||||||
|
|
||||||
-- default to 1st tab
|
-- default to 1st tab
|
||||||
e.value = 1
|
e.value = 1
|
||||||
|
|
||||||
local was_pressed = false
|
local was_pressed = false
|
||||||
|
local tabs = {}
|
||||||
|
|
||||||
-- show the button state
|
-- show the button state
|
||||||
---@param pressed? boolean if the currently selected tab should appear as actively pressed
|
---@param pressed? boolean if the currently selected tab should appear as actively pressed
|
||||||
@ -51,10 +40,18 @@ local function sidebar(args)
|
|||||||
was_pressed = pressed
|
was_pressed = pressed
|
||||||
pressed_idx = pressed_idx or e.value
|
pressed_idx = pressed_idx or e.value
|
||||||
|
|
||||||
for i = 1, #args.tabs do
|
-- clear
|
||||||
local tab = args.tabs[i] ---@type sidebar_tab
|
e.w_set_fgd(e.fg_bg.fgd)
|
||||||
|
e.w_set_bkg(e.fg_bg.bkg)
|
||||||
|
for y = 1, e.frame.h do
|
||||||
|
e.w_set_cur(1, y)
|
||||||
|
e.w_write(" ")
|
||||||
|
end
|
||||||
|
|
||||||
local y = ((i - 1) * 3) + 1
|
-- draw tabs
|
||||||
|
for i = 1, #tabs do
|
||||||
|
local tab = tabs[i] ---@type sidebar_tab
|
||||||
|
local y = tab.y_start
|
||||||
|
|
||||||
e.w_set_cur(1, y)
|
e.w_set_cur(1, y)
|
||||||
|
|
||||||
@ -66,13 +63,29 @@ local function sidebar(args)
|
|||||||
e.w_set_bkg(tab.color.bkg)
|
e.w_set_bkg(tab.color.bkg)
|
||||||
end
|
end
|
||||||
|
|
||||||
e.w_write(" ")
|
if tab.tall then
|
||||||
e.w_set_cur(1, y + 1)
|
e.w_write(" ")
|
||||||
if e.value == i then
|
e.w_set_cur(1, y + 1)
|
||||||
e.w_write(" " .. tab.char .. "\x10")
|
end
|
||||||
else e.w_write(" " .. tab.char .. " ") end
|
|
||||||
e.w_set_cur(1, y + 2)
|
e.w_write(tab.label)
|
||||||
e.w_write(" ")
|
|
||||||
|
if tab.tall then
|
||||||
|
e.w_set_cur(1, y + 2)
|
||||||
|
e.w_write(" ")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- determine which tab was pressed
|
||||||
|
---@param y integer y coordinate
|
||||||
|
local function find_tab(y)
|
||||||
|
for i = 1, #tabs do
|
||||||
|
local tab = tabs[i] ---@type sidebar_tab
|
||||||
|
|
||||||
|
if y >= tab.y_start and y <= tab.y_end then
|
||||||
|
return i
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -81,23 +94,25 @@ local function sidebar(args)
|
|||||||
function e.handle_mouse(event)
|
function e.handle_mouse(event)
|
||||||
-- determine what was pressed
|
-- determine what was pressed
|
||||||
if e.enabled then
|
if e.enabled then
|
||||||
local cur_idx = math.ceil(event.current.y / 3)
|
local cur_idx = find_tab(event.current.y)
|
||||||
local ini_idx = math.ceil(event.initial.y / 3)
|
local ini_idx = find_tab(event.initial.y)
|
||||||
|
local tab = tabs[cur_idx]
|
||||||
|
|
||||||
if args.tabs[cur_idx] ~= nil then
|
-- handle press if a callback was provided
|
||||||
|
if tab ~= nil and type(tab.callback) == "function" then
|
||||||
if event.type == MOUSE_CLICK.TAP then
|
if event.type == MOUSE_CLICK.TAP then
|
||||||
e.value = cur_idx
|
e.value = cur_idx
|
||||||
draw(true)
|
draw(true)
|
||||||
-- show as unpressed in 0.25 seconds
|
-- show as unpressed in 0.25 seconds
|
||||||
tcd.dispatch(0.25, function () draw(false) end)
|
tcd.dispatch(0.25, function () draw(false) end)
|
||||||
args.callback(e.value)
|
tab.callback()
|
||||||
elseif event.type == MOUSE_CLICK.DOWN then
|
elseif event.type == MOUSE_CLICK.DOWN then
|
||||||
draw(true, cur_idx)
|
draw(true, cur_idx)
|
||||||
elseif event.type == MOUSE_CLICK.UP then
|
elseif event.type == MOUSE_CLICK.UP then
|
||||||
if cur_idx == ini_idx and e.in_frame_bounds(event.current.x, event.current.y) then
|
if cur_idx == ini_idx and e.in_frame_bounds(event.current.x, event.current.y) then
|
||||||
e.value = cur_idx
|
e.value = cur_idx
|
||||||
draw(false)
|
draw(false)
|
||||||
args.callback(e.value)
|
tab.callback()
|
||||||
else draw(false) end
|
else draw(false) end
|
||||||
end
|
end
|
||||||
elseif event.type == MOUSE_CLICK.UP then
|
elseif event.type == MOUSE_CLICK.UP then
|
||||||
@ -113,6 +128,35 @@ local function sidebar(args)
|
|||||||
draw(false)
|
draw(false)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- update the sidebar navigation options
|
||||||
|
---@param items table sidebar entries
|
||||||
|
function e.on_update(items)
|
||||||
|
local next_y = 1
|
||||||
|
|
||||||
|
tabs = {}
|
||||||
|
|
||||||
|
for i = 1, #items do
|
||||||
|
local item = items[i]
|
||||||
|
local height = util.trinary(item.tall, 3, 1)
|
||||||
|
|
||||||
|
---@class sidebar_tab
|
||||||
|
local entry = {
|
||||||
|
y_start = next_y, ---@type integer
|
||||||
|
y_end = next_y + height - 1, ---@type integer
|
||||||
|
tall = item.tall, ---@type boolean
|
||||||
|
label = item.label, ---@type string
|
||||||
|
color = item.color, ---@type cpair
|
||||||
|
callback = item.callback ---@type function|nil
|
||||||
|
}
|
||||||
|
|
||||||
|
next_y = next_y + height
|
||||||
|
|
||||||
|
tabs[i] = entry
|
||||||
|
end
|
||||||
|
|
||||||
|
draw()
|
||||||
|
end
|
||||||
|
|
||||||
-- element redraw
|
-- element redraw
|
||||||
e.redraw = draw
|
e.redraw = draw
|
||||||
|
|
||||||
|
@ -65,7 +65,7 @@ local function switch_button(args)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- set the value
|
-- set the value (does not call the callback)
|
||||||
---@param val boolean new value
|
---@param val boolean new value
|
||||||
function e.set_value(val)
|
function e.set_value(val)
|
||||||
e.value = val
|
e.value = val
|
||||||
|
@ -9,7 +9,7 @@ local element = require("graphics.element")
|
|||||||
---@class icon_indicator_args
|
---@class icon_indicator_args
|
||||||
---@field label string indicator label
|
---@field label string indicator label
|
||||||
---@field states table state color and symbol table
|
---@field states table state color and symbol table
|
||||||
---@field value? integer default state, defaults to 1
|
---@field value? integer|boolean default state, defaults to 1 (true = 2, false = 1)
|
||||||
---@field min_label_width? integer label length if omitted
|
---@field min_label_width? integer label length if omitted
|
||||||
---@field parent graphics_element
|
---@field parent graphics_element
|
||||||
---@field id? string element id
|
---@field id? string element id
|
||||||
@ -33,6 +33,7 @@ local function icon(args)
|
|||||||
local e = element.new(args)
|
local e = element.new(args)
|
||||||
|
|
||||||
e.value = args.value or 1
|
e.value = args.value or 1
|
||||||
|
if e.value == true then e.value = 2 end
|
||||||
|
|
||||||
-- state blit strings
|
-- state blit strings
|
||||||
local state_blit_cmds = {}
|
local state_blit_cmds = {}
|
||||||
@ -47,8 +48,11 @@ local function icon(args)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- on state change
|
-- on state change
|
||||||
---@param new_state integer indicator state
|
---@param new_state integer|boolean indicator state
|
||||||
function e.on_update(new_state)
|
function e.on_update(new_state)
|
||||||
|
new_state = new_state or 1
|
||||||
|
if new_state == true then new_state = 2 end
|
||||||
|
|
||||||
local blit_cmd = state_blit_cmds[new_state]
|
local blit_cmd = state_blit_cmds[new_state]
|
||||||
e.value = new_state
|
e.value = new_state
|
||||||
e.w_set_cur(1, 1)
|
e.w_set_cur(1, 1)
|
||||||
@ -56,7 +60,7 @@ local function icon(args)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- set indicator state
|
-- set indicator state
|
||||||
---@param val integer indicator state
|
---@param val integer|boolean indicator state
|
||||||
function e.set_value(val) e.on_update(val) end
|
function e.set_value(val) e.on_update(val) end
|
||||||
|
|
||||||
-- element redraw
|
-- element redraw
|
||||||
|
@ -6,6 +6,7 @@ local element = require("graphics.element")
|
|||||||
|
|
||||||
---@class power_indicator_args
|
---@class power_indicator_args
|
||||||
---@field label string indicator label
|
---@field label string indicator label
|
||||||
|
---@field unit string energy unit
|
||||||
---@field format string power format override (lua string format)
|
---@field format string power format override (lua string format)
|
||||||
---@field rate boolean? whether to append /t to the end (power per tick)
|
---@field rate boolean? whether to append /t to the end (power per tick)
|
||||||
---@field lu_colors? cpair label foreground color (a), unit foreground color (b)
|
---@field lu_colors? cpair label foreground color (a), unit foreground color (b)
|
||||||
@ -23,6 +24,8 @@ local element = require("graphics.element")
|
|||||||
---@param args power_indicator_args
|
---@param args power_indicator_args
|
||||||
---@return graphics_element element, element_id id
|
---@return graphics_element element, element_id id
|
||||||
local function power(args)
|
local function power(args)
|
||||||
|
element.assert(type(args.label) == "string", "label is a required field")
|
||||||
|
element.assert(type(args.unit) == "string", "unit is a required field")
|
||||||
element.assert(type(args.value) == "number", "value is a required field")
|
element.assert(type(args.value) == "number", "value is a required field")
|
||||||
element.assert(util.is_int(args.width), "width is a required field")
|
element.assert(util.is_int(args.width), "width is a required field")
|
||||||
|
|
||||||
@ -40,7 +43,7 @@ local function power(args)
|
|||||||
function e.on_update(value)
|
function e.on_update(value)
|
||||||
e.value = value
|
e.value = value
|
||||||
|
|
||||||
local data_str, unit = util.power_format(value, false, args.format)
|
local data_str, unit = util.power_format(value, args.unit, false, args.format)
|
||||||
|
|
||||||
-- write data
|
-- write data
|
||||||
e.w_set_cur(data_start, 1)
|
e.w_set_cur(data_start, 1)
|
||||||
@ -53,14 +56,13 @@ local function power(args)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- append per tick if rate is set
|
-- append per tick if rate is set
|
||||||
-- add space to FE so we don't end up with FEE (after having kFE for example)
|
|
||||||
if args.rate == true then
|
if args.rate == true then
|
||||||
unit = unit .. "/t"
|
unit = unit .. "/t"
|
||||||
if unit == "FE/t" then unit = "FE/t " end
|
|
||||||
else
|
|
||||||
if unit == "FE" then unit = "FE " end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- add space to unit so we don't end up with something like FEE after having kFE
|
||||||
|
unit = util.strminw(unit, 5)
|
||||||
|
|
||||||
e.w_write(" " .. unit)
|
e.w_write(" " .. unit)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
85
graphics/elements/indicators/signal.lua
Normal file
85
graphics/elements/indicators/signal.lua
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
-- Signal Bars Graphics Element
|
||||||
|
|
||||||
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
|
local element = require("graphics.element")
|
||||||
|
|
||||||
|
---@class signal_bar_args
|
||||||
|
---@field compact? boolean true to use a single character (works better against edges that extend out colors)
|
||||||
|
---@field colors_low_med? cpair color a for low signal quality, color b for medium signal quality
|
||||||
|
---@field disconnect_color? color color for the 'x' on disconnect
|
||||||
|
---@field parent graphics_element
|
||||||
|
---@field id? string element id
|
||||||
|
---@field x? integer 1 if omitted
|
||||||
|
---@field y? integer auto incremented if omitted
|
||||||
|
---@field fg_bg? cpair foreground/background colors (foreground is used for high signal quality)
|
||||||
|
---@field hidden? boolean true to hide on initial draw
|
||||||
|
|
||||||
|
-- new signal bar
|
||||||
|
---@nodiscard
|
||||||
|
---@param args signal_bar_args
|
||||||
|
---@return graphics_element element, element_id id
|
||||||
|
local function signal_bar(args)
|
||||||
|
args.height = 1
|
||||||
|
args.width = util.trinary(args.compact, 1, 2)
|
||||||
|
|
||||||
|
-- create new graphics element base object
|
||||||
|
local e = element.new(args)
|
||||||
|
|
||||||
|
e.value = 0
|
||||||
|
|
||||||
|
local blit_bkg = args.fg_bg.blit_bkg
|
||||||
|
local blit_0, blit_1, blit_2, blit_3 = args.fg_bg.blit_fgd, args.fg_bg.blit_fgd, args.fg_bg.blit_fgd, args.fg_bg.blit_fgd
|
||||||
|
|
||||||
|
if type(args.colors_low_med) == "table" then
|
||||||
|
blit_1 = args.colors_low_med.blit_a or blit_1
|
||||||
|
blit_2 = args.colors_low_med.blit_b or blit_2
|
||||||
|
end
|
||||||
|
|
||||||
|
if util.is_int(args.disconnect_color) then blit_0 = colors.toBlit(args.disconnect_color) end
|
||||||
|
|
||||||
|
-- on state change (0 = offline, 1 through 3 = low to high signal)
|
||||||
|
---@param new_state integer signal state
|
||||||
|
function e.on_update(new_state)
|
||||||
|
e.value = new_state
|
||||||
|
e.redraw()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- set signal state (0 = offline, 1 through 3 = low to high signal)
|
||||||
|
---@param val integer signal state
|
||||||
|
function e.set_value(val) e.on_update(val) end
|
||||||
|
|
||||||
|
-- draw label and signal bar
|
||||||
|
function e.redraw()
|
||||||
|
e.w_set_cur(1, 1)
|
||||||
|
|
||||||
|
if args.compact then
|
||||||
|
if e.value == 1 then
|
||||||
|
e.w_blit("\x90", blit_1, blit_bkg)
|
||||||
|
elseif e.value == 2 then
|
||||||
|
e.w_blit("\x94", blit_2, blit_bkg)
|
||||||
|
elseif e.value == 3 then
|
||||||
|
e.w_blit("\x95", blit_3, blit_bkg)
|
||||||
|
else
|
||||||
|
e.w_blit("x", blit_0, blit_bkg)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if e.value == 1 then
|
||||||
|
e.w_blit("\x9f ", blit_bkg .. blit_bkg, blit_1 .. blit_bkg)
|
||||||
|
elseif e.value == 2 then
|
||||||
|
e.w_blit("\x9f\x94", blit_bkg .. blit_2, blit_2 .. blit_bkg)
|
||||||
|
elseif e.value == 3 then
|
||||||
|
e.w_blit("\x9f\x81", blit_bkg .. blit_bkg, blit_3 .. blit_3)
|
||||||
|
else
|
||||||
|
e.w_blit(" x", blit_0 .. blit_0, blit_bkg .. blit_bkg)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- initial draw
|
||||||
|
e.redraw()
|
||||||
|
|
||||||
|
return e.complete()
|
||||||
|
end
|
||||||
|
|
||||||
|
return signal_bar
|
@ -1,5 +1,6 @@
|
|||||||
-- 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")
|
||||||
@ -152,6 +153,7 @@ local function listbox(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
|
||||||
@ -210,6 +212,7 @@ local function listbox(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
|
||||||
|
|
||||||
@ -219,10 +222,12 @@ local function listbox(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
|
||||||
|
@ -45,7 +45,7 @@ local function rectangle(args)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- create new graphics element base object
|
-- create new graphics element base object
|
||||||
local e = element.new(args, offset_x, offset_y)
|
local e = element.new(args, nil, offset_x, offset_y)
|
||||||
|
|
||||||
-- create content window for child elements
|
-- create content window for child elements
|
||||||
e.content_window = window.create(e.window, 1 + offset_x, 1 + offset_y, e.frame.w - (2 * offset_x), e.frame.h - (2 * offset_y))
|
e.content_window = window.create(e.window, 1 + offset_x, 1 + offset_y, e.frame.w - (2 * offset_x), e.frame.h - (2 * offset_y))
|
||||||
|
@ -10,12 +10,13 @@ local ALIGN = core.ALIGN
|
|||||||
---@class textbox_args
|
---@class textbox_args
|
||||||
---@field text string text to show
|
---@field text string text to show
|
||||||
---@field alignment? ALIGN text alignment, left by default
|
---@field alignment? ALIGN text alignment, left by default
|
||||||
|
---@field anchor? boolean true to use this as an anchor, making it focusable
|
||||||
---@field parent graphics_element
|
---@field parent graphics_element
|
||||||
---@field id? string element id
|
---@field id? string element id
|
||||||
---@field x? integer 1 if omitted
|
---@field x? integer 1 if omitted
|
||||||
---@field y? integer auto incremented if omitted
|
---@field y? integer auto incremented if omitted
|
||||||
---@field width? integer parent width if omitted
|
---@field width? integer parent width if omitted
|
||||||
---@field height? integer parent height if omitted
|
---@field height? integer minimum necessary height for wrapped text if omitted
|
||||||
---@field gframe? graphics_frame frame instead of x/y/width/height
|
---@field gframe? graphics_frame frame instead of x/y/width/height
|
||||||
---@field fg_bg? cpair foreground/background colors
|
---@field fg_bg? cpair foreground/background colors
|
||||||
---@field hidden? boolean true to hide on initial draw
|
---@field hidden? boolean true to hide on initial draw
|
||||||
@ -26,8 +27,22 @@ local ALIGN = core.ALIGN
|
|||||||
local function textbox(args)
|
local function textbox(args)
|
||||||
element.assert(type(args.text) == "string", "text is a required field")
|
element.assert(type(args.text) == "string", "text is a required field")
|
||||||
|
|
||||||
|
if args.anchor == true then args.can_focus = true end
|
||||||
|
|
||||||
|
-- provide a constraint condition to element creation to prevent an pointlessly tall text box
|
||||||
|
---@param frame graphics_frame
|
||||||
|
local function constrain(frame)
|
||||||
|
local new_height = math.max(1, #util.strwrap(args.text, frame.w))
|
||||||
|
|
||||||
|
if args.height then
|
||||||
|
new_height = math.max(frame.h, new_height)
|
||||||
|
end
|
||||||
|
|
||||||
|
return frame.w, new_height
|
||||||
|
end
|
||||||
|
|
||||||
-- create new graphics element base object
|
-- create new graphics element base object
|
||||||
local e = element.new(args)
|
local e = element.new(args, constrain)
|
||||||
|
|
||||||
e.value = args.text
|
e.value = args.text
|
||||||
|
|
||||||
@ -42,6 +57,9 @@ local function textbox(args)
|
|||||||
for i = 1, #lines do
|
for i = 1, #lines do
|
||||||
if i > e.frame.h then break end
|
if i > e.frame.h then break end
|
||||||
|
|
||||||
|
-- trim leading/trailing whitespace
|
||||||
|
lines[i] = util.trim(lines[i])
|
||||||
|
|
||||||
local len = string.len(lines[i])
|
local len = string.len(lines[i])
|
||||||
|
|
||||||
-- use cursor position to align this line
|
-- use cursor position to align this line
|
||||||
|
@ -54,14 +54,21 @@ themes.COLOR_MODE = {
|
|||||||
STANDARD = 1,
|
STANDARD = 1,
|
||||||
DEUTERANOPIA = 2,
|
DEUTERANOPIA = 2,
|
||||||
PROTANOPIA = 3,
|
PROTANOPIA = 3,
|
||||||
TRITANOPIA = 4
|
TRITANOPIA = 4,
|
||||||
|
BLUE_IND = 5,
|
||||||
|
STD_ON_BLACK = 6,
|
||||||
|
BLUE_ON_BLACK = 7,
|
||||||
|
NUM_MODES = 8
|
||||||
}
|
}
|
||||||
|
|
||||||
themes.COLOR_MODE_NAMES = {
|
themes.COLOR_MODE_NAMES = {
|
||||||
"Standard",
|
"Standard",
|
||||||
"Deuteranopia",
|
"Deuteranopia",
|
||||||
"Protanopia",
|
"Protanopia",
|
||||||
"Tritanopia"
|
"Tritanopia",
|
||||||
|
"Blue for 'Good'",
|
||||||
|
"Standard + Black",
|
||||||
|
"Blue + Black"
|
||||||
}
|
}
|
||||||
|
|
||||||
-- attempts to get the string name of a color mode
|
-- attempts to get the string name of a color mode
|
||||||
@ -72,7 +79,10 @@ function themes.color_mode_name(id)
|
|||||||
if id == themes.COLOR_MODE.STANDARD or
|
if id == themes.COLOR_MODE.STANDARD or
|
||||||
id == themes.COLOR_MODE.DEUTERANOPIA or
|
id == themes.COLOR_MODE.DEUTERANOPIA or
|
||||||
id == themes.COLOR_MODE.PROTANOPIA or
|
id == themes.COLOR_MODE.PROTANOPIA or
|
||||||
id == themes.COLOR_MODE.TRITANOPIA then
|
id == themes.COLOR_MODE.TRITANOPIA or
|
||||||
|
id == themes.COLOR_MODE.BLUE_IND or
|
||||||
|
id == themes.COLOR_MODE.STD_ON_BLACK or
|
||||||
|
id == themes.COLOR_MODE.BLUE_ON_BLACK then
|
||||||
return themes.COLOR_MODE_NAMES[id]
|
return themes.COLOR_MODE_NAMES[id]
|
||||||
else return nil end
|
else return nil end
|
||||||
end
|
end
|
||||||
@ -147,6 +157,26 @@ themes.sandstone = {
|
|||||||
{ c = colors.yellow_off, hex = 0x141414 },
|
{ c = colors.yellow_off, hex = 0x141414 },
|
||||||
{ c = colors.red, hex = 0xff0000 },
|
{ c = colors.red, hex = 0xff0000 },
|
||||||
{ c = colors.red_off, hex = 0x141414 }
|
{ c = colors.red_off, hex = 0x141414 }
|
||||||
|
},
|
||||||
|
-- blue indicators
|
||||||
|
{
|
||||||
|
{ c = colors.green, hex = 0x1081ff },
|
||||||
|
{ c = colors.green_hc, hex = 0x1081ff },
|
||||||
|
{ c = colors.green_off, hex = 0x053466 },
|
||||||
|
},
|
||||||
|
-- standard, black backgrounds
|
||||||
|
{
|
||||||
|
{ c = colors.green_off, hex = 0x141414 },
|
||||||
|
{ c = colors.yellow_off, hex = 0x141414 },
|
||||||
|
{ c = colors.red_off, hex = 0x141414 }
|
||||||
|
},
|
||||||
|
-- blue indicators, black backgrounds
|
||||||
|
{
|
||||||
|
{ c = colors.green, hex = 0x1081ff },
|
||||||
|
{ c = colors.green_hc, hex = 0x1081ff },
|
||||||
|
{ c = colors.green_off, hex = 0x141414 },
|
||||||
|
{ c = colors.yellow_off, hex = 0x141414 },
|
||||||
|
{ c = colors.red_off, hex = 0x141414 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -180,8 +210,8 @@ themes.basalt = {
|
|||||||
{ c = colors.white, hex = 0xbfbfbf },
|
{ c = colors.white, hex = 0xbfbfbf },
|
||||||
{ c = colors.lightGray, hex = 0x848794 },
|
{ c = colors.lightGray, hex = 0x848794 },
|
||||||
{ c = colors.gray, hex = 0x5c5f68 },
|
{ c = colors.gray, hex = 0x5c5f68 },
|
||||||
{ c = colors.black, hex = 0x262626 },
|
{ c = colors.black, hex = 0x333333 },
|
||||||
{ c = colors.red_off, hex = 0x653839 }
|
{ c = colors.red_off, hex = 0x512d2d }
|
||||||
},
|
},
|
||||||
|
|
||||||
color_modes = {
|
color_modes = {
|
||||||
@ -216,6 +246,26 @@ themes.basalt = {
|
|||||||
{ c = colors.yellow_off, hex = 0x333333 },
|
{ c = colors.yellow_off, hex = 0x333333 },
|
||||||
{ c = colors.red, hex = 0xdf4949 },
|
{ c = colors.red, hex = 0xdf4949 },
|
||||||
{ c = colors.red_off, hex = 0x333333 }
|
{ c = colors.red_off, hex = 0x333333 }
|
||||||
|
},
|
||||||
|
-- blue indicators
|
||||||
|
{
|
||||||
|
{ c = colors.green, hex = 0x65aeff },
|
||||||
|
{ c = colors.green_hc, hex = 0x99c9ff },
|
||||||
|
{ c = colors.green_off, hex = 0x365e8a },
|
||||||
|
},
|
||||||
|
-- standard, black backgrounds
|
||||||
|
{
|
||||||
|
{ c = colors.green_off, hex = 0x333333 },
|
||||||
|
{ c = colors.yellow_off, hex = 0x333333 },
|
||||||
|
{ c = colors.red_off, hex = 0x333333 }
|
||||||
|
},
|
||||||
|
-- blue indicators, black backgrounds
|
||||||
|
{
|
||||||
|
{ c = colors.green, hex = 0x65aeff },
|
||||||
|
{ c = colors.green_hc, hex = 0x99c9ff },
|
||||||
|
{ c = colors.green_off, hex = 0x333333 },
|
||||||
|
{ c = colors.yellow_off, hex = 0x333333 },
|
||||||
|
{ c = colors.red_off, hex = 0x333333 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -272,19 +322,33 @@ themes.smooth_stone = {
|
|||||||
{
|
{
|
||||||
{ c = colors.blue, hex = 0x1081ff },
|
{ c = colors.blue, hex = 0x1081ff },
|
||||||
{ c = colors.yellow, hex = 0xf7c311 },
|
{ c = colors.yellow, hex = 0xf7c311 },
|
||||||
{ c = colors.red, hex = 0xfb5615 },
|
{ c = colors.red, hex = 0xfb5615 }
|
||||||
},
|
},
|
||||||
-- protanopia
|
-- protanopia
|
||||||
{
|
{
|
||||||
{ c = colors.blue, hex = 0x1081ff },
|
{ c = colors.blue, hex = 0x1081ff },
|
||||||
{ c = colors.yellow, hex = 0xf5e633 },
|
{ c = colors.yellow, hex = 0xf5e633 },
|
||||||
{ c = colors.red, hex = 0xff521a },
|
{ c = colors.red, hex = 0xff521a }
|
||||||
},
|
},
|
||||||
-- tritanopia
|
-- tritanopia
|
||||||
{
|
{
|
||||||
{ c = colors.blue, hex = 0x40cbd7 },
|
{ c = colors.blue, hex = 0x40cbd7 },
|
||||||
{ c = colors.yellow, hex = 0xffbc00 },
|
{ c = colors.yellow, hex = 0xffbc00 },
|
||||||
{ c = colors.red, hex = 0xff0000 },
|
{ c = colors.red, hex = 0xff0000 }
|
||||||
|
},
|
||||||
|
-- blue indicators
|
||||||
|
{
|
||||||
|
{ c = colors.blue, hex = 0x1081ff },
|
||||||
|
{ c = colors.yellow, hex = 0xfffc79 },
|
||||||
|
{ c = colors.red, hex = 0xdf4949 }
|
||||||
|
},
|
||||||
|
-- standard, black backgrounds
|
||||||
|
{},
|
||||||
|
-- blue indicators, black backgrounds
|
||||||
|
{
|
||||||
|
{ c = colors.blue, hex = 0x1081ff },
|
||||||
|
{ c = colors.yellow, hex = 0xfffc79 },
|
||||||
|
{ c = colors.red, hex = 0xdf4949 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -318,19 +382,33 @@ themes.deepslate = {
|
|||||||
{
|
{
|
||||||
{ c = colors.blue, hex = 0x65aeff },
|
{ c = colors.blue, hex = 0x65aeff },
|
||||||
{ c = colors.yellow, hex = 0xf7c311 },
|
{ c = colors.yellow, hex = 0xf7c311 },
|
||||||
{ c = colors.red, hex = 0xfb5615 },
|
{ c = colors.red, hex = 0xfb5615 }
|
||||||
},
|
},
|
||||||
-- protanopia
|
-- protanopia
|
||||||
{
|
{
|
||||||
{ c = colors.blue, hex = 0x65aeff },
|
{ c = colors.blue, hex = 0x65aeff },
|
||||||
{ c = colors.yellow, hex = 0xf5e633 },
|
{ c = colors.yellow, hex = 0xf5e633 },
|
||||||
{ c = colors.red, hex = 0xff8058 },
|
{ c = colors.red, hex = 0xff8058 }
|
||||||
},
|
},
|
||||||
-- tritanopia
|
-- tritanopia
|
||||||
{
|
{
|
||||||
{ c = colors.blue, hex = 0x00ecff },
|
{ c = colors.blue, hex = 0x00ecff },
|
||||||
{ c = colors.yellow, hex = 0xffbc00 },
|
{ c = colors.yellow, hex = 0xffbc00 },
|
||||||
{ c = colors.red, hex = 0xdf4949 },
|
{ c = colors.red, hex = 0xdf4949 }
|
||||||
|
},
|
||||||
|
-- blue indicators
|
||||||
|
{
|
||||||
|
{ c = colors.blue, hex = 0x65aeff },
|
||||||
|
{ c = colors.yellow, hex = 0xd9cf81 },
|
||||||
|
{ c = colors.red, hex = 0xeb6a6c }
|
||||||
|
},
|
||||||
|
-- standard, black backgrounds
|
||||||
|
{},
|
||||||
|
-- blue indicators, black backgrounds
|
||||||
|
{
|
||||||
|
{ c = colors.blue, hex = 0x65aeff },
|
||||||
|
{ c = colors.yellow, hex = 0xd9cf81 },
|
||||||
|
{ c = colors.red, hex = 0xeb6a6c }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
--
|
--
|
||||||
|
|
||||||
local log = require("scada-common.log")
|
local log = require("scada-common.log")
|
||||||
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")
|
||||||
@ -32,7 +32,10 @@ local CENTER = core.ALIGN.CENTER
|
|||||||
local RIGHT = core.ALIGN.RIGHT
|
local RIGHT = core.ALIGN.RIGHT
|
||||||
|
|
||||||
-- 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.11.3", { "Added energy scale options" } }
|
||||||
|
}
|
||||||
|
|
||||||
---@class pkt_configurator
|
---@class pkt_configurator
|
||||||
local configurator = {}
|
local configurator = {}
|
||||||
@ -73,6 +76,8 @@ local tool_ctl = {
|
|||||||
|
|
||||||
---@class pkt_config
|
---@class pkt_config
|
||||||
local tmp_cfg = {
|
local tmp_cfg = {
|
||||||
|
TempScale = 1,
|
||||||
|
EnergyScale = 1,
|
||||||
SVR_Channel = nil, ---@type integer
|
SVR_Channel = nil, ---@type integer
|
||||||
CRD_Channel = nil, ---@type integer
|
CRD_Channel = nil, ---@type integer
|
||||||
PKT_Channel = nil, ---@type integer
|
PKT_Channel = nil, ---@type integer
|
||||||
@ -91,6 +96,8 @@ 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 = {
|
||||||
|
{ "TempScale", "Temperature Scale", types.TEMP_SCALE.KELVIN },
|
||||||
|
{ "EnergyScale", "Energy Scale", types.ENERGY_SCALE.FE },
|
||||||
{ "SVR_Channel", "SVR Channel", 16240 },
|
{ "SVR_Channel", "SVR Channel", 16240 },
|
||||||
{ "CRD_Channel", "CRD Channel", 16243 },
|
{ "CRD_Channel", "CRD Channel", 16243 },
|
||||||
{ "PKT_Channel", "PKT Channel", 16244 },
|
{ "PKT_Channel", "PKT Channel", 16244 },
|
||||||
@ -121,17 +128,18 @@ local function config_view(display)
|
|||||||
---@diagnostic disable-next-line: undefined-field
|
---@diagnostic disable-next-line: undefined-field
|
||||||
local function exit() os.queueEvent("terminate") end
|
local function exit() os.queueEvent("terminate") end
|
||||||
|
|
||||||
TextBox{parent=display,y=1,text="Pocket Configurator",alignment=CENTER,height=1,fg_bg=style.header}
|
TextBox{parent=display,y=1,text="Pocket Configurator",alignment=CENTER,fg_bg=style.header}
|
||||||
|
|
||||||
local root_pane_div = Div{parent=display,x=1,y=2}
|
local root_pane_div = Div{parent=display,x=1,y=2}
|
||||||
|
|
||||||
local main_page = Div{parent=root_pane_div,x=1,y=1}
|
local main_page = Div{parent=root_pane_div,x=1,y=1}
|
||||||
|
local ui_cfg = Div{parent=root_pane_div,x=1,y=1}
|
||||||
local net_cfg = Div{parent=root_pane_div,x=1,y=1}
|
local net_cfg = Div{parent=root_pane_div,x=1,y=1}
|
||||||
local log_cfg = Div{parent=root_pane_div,x=1,y=1}
|
local log_cfg = Div{parent=root_pane_div,x=1,y=1}
|
||||||
local summary = Div{parent=root_pane_div,x=1,y=1}
|
local summary = Div{parent=root_pane_div,x=1,y=1}
|
||||||
local changelog = Div{parent=root_pane_div,x=1,y=1}
|
local changelog = Div{parent=root_pane_div,x=1,y=1}
|
||||||
|
|
||||||
local main_pane = MultiPane{parent=root_pane_div,x=1,y=1,panes={main_page,net_cfg,log_cfg,summary,changelog}}
|
local main_pane = MultiPane{parent=root_pane_div,x=1,y=1,panes={main_page,ui_cfg,net_cfg,log_cfg,summary,changelog}}
|
||||||
|
|
||||||
-- Main Page
|
-- Main Page
|
||||||
|
|
||||||
@ -148,7 +156,7 @@ local function config_view(display)
|
|||||||
tool_ctl.viewing_config = true
|
tool_ctl.viewing_config = true
|
||||||
tool_ctl.gen_summary(settings_cfg)
|
tool_ctl.gen_summary(settings_cfg)
|
||||||
tool_ctl.settings_apply.hide(true)
|
tool_ctl.settings_apply.hide(true)
|
||||||
main_pane.set_value(4)
|
main_pane.set_value(5)
|
||||||
end
|
end
|
||||||
|
|
||||||
if fs.exists("/pocket/config.lua") then
|
if fs.exists("/pocket/config.lua") then
|
||||||
@ -162,7 +170,32 @@ 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
|
||||||
|
|
||||||
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(5)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=main_page,x=14,y=18,min_width=12,text="Change Log",callback=function()main_pane.set_value(6)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
|
--#region Pocket UI
|
||||||
|
|
||||||
|
local ui_c_1 = Div{parent=ui_cfg,x=2,y=4,width=24}
|
||||||
|
|
||||||
|
TextBox{parent=ui_cfg,x=1,y=2,text=" Pocket UI",fg_bg=cpair(colors.black,colors.lime)}
|
||||||
|
|
||||||
|
TextBox{parent=ui_c_1,x=1,y=1,height=3,text="You may customize units below."}
|
||||||
|
|
||||||
|
TextBox{parent=ui_c_1,x=1,y=4,text="Temperature Scale"}
|
||||||
|
local temp_scale = RadioButton{parent=ui_c_1,x=1,y=5,default=ini_cfg.TempScale,options=types.TEMP_SCALE_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime}
|
||||||
|
|
||||||
|
TextBox{parent=ui_c_1,x=1,y=10,text="Energy Scale"}
|
||||||
|
local energy_scale = RadioButton{parent=ui_c_1,x=1,y=11,default=ini_cfg.EnergyScale,options=types.ENERGY_SCALE_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime}
|
||||||
|
|
||||||
|
local function submit_ui_opts()
|
||||||
|
tmp_cfg.TempScale = temp_scale.get_value()
|
||||||
|
tmp_cfg.EnergyScale = energy_scale.get_value()
|
||||||
|
main_pane.set_value(3)
|
||||||
|
end
|
||||||
|
|
||||||
|
PushButton{parent=ui_c_1,x=1,y=15,text="\x1b Back",callback=function()main_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
PushButton{parent=ui_c_1,x=19,y=15,text="Next \x1a",callback=submit_ui_opts,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
--#region Network
|
--#region Network
|
||||||
|
|
||||||
@ -173,24 +206,24 @@ local function config_view(display)
|
|||||||
|
|
||||||
local net_pane = MultiPane{parent=net_cfg,x=1,y=4,panes={net_c_1,net_c_2,net_c_3,net_c_4}}
|
local net_pane = MultiPane{parent=net_cfg,x=1,y=4,panes={net_c_1,net_c_2,net_c_3,net_c_4}}
|
||||||
|
|
||||||
TextBox{parent=net_cfg,x=1,y=2,height=1,text=" Network Configuration",fg_bg=cpair(colors.black,colors.lightBlue)}
|
TextBox{parent=net_cfg,x=1,y=2,text=" Network Configuration",fg_bg=cpair(colors.black,colors.lightBlue)}
|
||||||
|
|
||||||
TextBox{parent=net_c_1,x=1,y=1,height=1,text="Set network channels."}
|
TextBox{parent=net_c_1,x=1,y=1,text="Set network channels."}
|
||||||
TextBox{parent=net_c_1,x=1,y=3,height=4,text="Each of the named channels must be the same within a particular SCADA network.",fg_bg=g_lg_fg_bg}
|
TextBox{parent=net_c_1,x=1,y=3,height=4,text="Each of the named channels must be the same within a particular SCADA network.",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=net_c_1,x=1,y=8,height=1,width=18,text="Supervisor Channel"}
|
TextBox{parent=net_c_1,x=1,y=8,width=18,text="Supervisor Channel"}
|
||||||
local svr_chan = NumberField{parent=net_c_1,x=1,y=9,width=7,default=ini_cfg.SVR_Channel,min=1,max=65535,fg_bg=bw_fg_bg}
|
local svr_chan = NumberField{parent=net_c_1,x=1,y=9,width=7,default=ini_cfg.SVR_Channel,min=1,max=65535,fg_bg=bw_fg_bg}
|
||||||
TextBox{parent=net_c_1,x=9,y=9,height=4,text="[SVR_CHANNEL]",fg_bg=g_lg_fg_bg}
|
TextBox{parent=net_c_1,x=9,y=9,height=4,text="[SVR_CHANNEL]",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=net_c_1,x=1,y=10,height=1,width=19,text="Coordinator Channel"}
|
TextBox{parent=net_c_1,x=1,y=10,width=19,text="Coordinator Channel"}
|
||||||
local crd_chan = NumberField{parent=net_c_1,x=1,y=11,width=7,default=ini_cfg.CRD_Channel,min=1,max=65535,fg_bg=bw_fg_bg}
|
local crd_chan = NumberField{parent=net_c_1,x=1,y=11,width=7,default=ini_cfg.CRD_Channel,min=1,max=65535,fg_bg=bw_fg_bg}
|
||||||
TextBox{parent=net_c_1,x=9,y=11,height=4,text="[CRD_CHANNEL]",fg_bg=g_lg_fg_bg}
|
TextBox{parent=net_c_1,x=9,y=11,height=4,text="[CRD_CHANNEL]",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=net_c_1,x=1,y=12,height=1,width=14,text="Pocket Channel"}
|
TextBox{parent=net_c_1,x=1,y=12,width=14,text="Pocket Channel"}
|
||||||
local pkt_chan = NumberField{parent=net_c_1,x=1,y=13,width=7,default=ini_cfg.PKT_Channel,min=1,max=65535,fg_bg=bw_fg_bg}
|
local pkt_chan = NumberField{parent=net_c_1,x=1,y=13,width=7,default=ini_cfg.PKT_Channel,min=1,max=65535,fg_bg=bw_fg_bg}
|
||||||
TextBox{parent=net_c_1,x=9,y=13,height=4,text="[PKT_CHANNEL]",fg_bg=g_lg_fg_bg}
|
TextBox{parent=net_c_1,x=9,y=13,height=4,text="[PKT_CHANNEL]",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
local chan_err = TextBox{parent=net_c_1,x=1,y=14,height=1,width=24,text="Please set all channels.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
local chan_err = TextBox{parent=net_c_1,x=1,y=14,width=24,text="Please set all channels.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||||
|
|
||||||
local function submit_channels()
|
local function submit_channels()
|
||||||
local svr_c, crd_c, pkt_c = tonumber(svr_chan.get_value()), tonumber(crd_chan.get_value()), tonumber(pkt_chan.get_value())
|
local svr_c, crd_c, pkt_c = tonumber(svr_chan.get_value()), tonumber(crd_chan.get_value()), tonumber(pkt_chan.get_value())
|
||||||
@ -201,18 +234,18 @@ local function config_view(display)
|
|||||||
else chan_err.show() end
|
else chan_err.show() end
|
||||||
end
|
end
|
||||||
|
|
||||||
PushButton{parent=net_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=net_c_1,x=1,y=15,text="\x1b Back",callback=function()main_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
PushButton{parent=net_c_1,x=19,y=15,text="Next \x1a",callback=submit_channels,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=net_c_1,x=19,y=15,text="Next \x1a",callback=submit_channels,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=net_c_2,x=1,y=1,height=1,text="Set connection timeout."}
|
TextBox{parent=net_c_2,x=1,y=1,text="Set connection timeout."}
|
||||||
TextBox{parent=net_c_2,x=1,y=3,height=7,text="You generally should not need to modify this. On slow servers, you can try to increase this to make the system wait longer before assuming a disconnection.",fg_bg=g_lg_fg_bg}
|
TextBox{parent=net_c_2,x=1,y=3,height=7,text="You generally should not need to modify this. On slow servers, you can try to increase this to make the system wait longer before assuming a disconnection.",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=net_c_2,x=1,y=11,height=1,width=19,text="Connection Timeout"}
|
TextBox{parent=net_c_2,x=1,y=11,width=19,text="Connection Timeout"}
|
||||||
local timeout = NumberField{parent=net_c_2,x=1,y=12,width=7,default=ini_cfg.ConnTimeout,min=2,max=25,max_chars=6,max_frac_digits=2,allow_decimal=true,fg_bg=bw_fg_bg}
|
local timeout = NumberField{parent=net_c_2,x=1,y=12,width=7,default=ini_cfg.ConnTimeout,min=2,max=25,max_chars=6,max_frac_digits=2,allow_decimal=true,fg_bg=bw_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=net_c_2,x=9,y=12,height=2,text="seconds\n(default 5)",fg_bg=g_lg_fg_bg}
|
TextBox{parent=net_c_2,x=9,y=12,height=2,text="seconds\n(default 5)",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
local ct_err = TextBox{parent=net_c_2,x=1,y=14,height=1,width=24,text="Please set timeout.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
local ct_err = TextBox{parent=net_c_2,x=1,y=14,width=24,text="Please set timeout.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||||
|
|
||||||
local function submit_timeouts()
|
local function submit_timeouts()
|
||||||
local timeout_val = tonumber(timeout.get_value())
|
local timeout_val = tonumber(timeout.get_value())
|
||||||
@ -226,13 +259,13 @@ local function config_view(display)
|
|||||||
PushButton{parent=net_c_2,x=1,y=15,text="\x1b Back",callback=function()net_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=net_c_2,x=1,y=15,text="\x1b Back",callback=function()net_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
PushButton{parent=net_c_2,x=19,y=15,text="Next \x1a",callback=submit_timeouts,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=net_c_2,x=19,y=15,text="Next \x1a",callback=submit_timeouts,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=net_c_3,x=1,y=1,height=1,text="Set the trusted range."}
|
TextBox{parent=net_c_3,x=1,y=1,text="Set the trusted range."}
|
||||||
TextBox{parent=net_c_3,x=1,y=3,height=4,text="Setting this to a value larger than 0 prevents connections with devices that many blocks away.",fg_bg=g_lg_fg_bg}
|
TextBox{parent=net_c_3,x=1,y=3,height=4,text="Setting this to a value larger than 0 prevents connections with devices that many blocks away.",fg_bg=g_lg_fg_bg}
|
||||||
TextBox{parent=net_c_3,x=1,y=8,height=4,text="This is optional. You can disable this functionality by setting the value to 0.",fg_bg=g_lg_fg_bg}
|
TextBox{parent=net_c_3,x=1,y=8,height=4,text="This is optional. You can disable this functionality by setting the value to 0.",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
local range = NumberField{parent=net_c_3,x=1,y=13,width=10,default=ini_cfg.TrustedRange,min=0,max_chars=20,allow_decimal=true,fg_bg=bw_fg_bg}
|
local range = NumberField{parent=net_c_3,x=1,y=13,width=10,default=ini_cfg.TrustedRange,min=0,max_chars=20,allow_decimal=true,fg_bg=bw_fg_bg}
|
||||||
|
|
||||||
local tr_err = TextBox{parent=net_c_3,x=1,y=14,height=1,width=24,text="Set the trusted range.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
local tr_err = TextBox{parent=net_c_3,x=1,y=14,width=24,text="Set the trusted range.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||||
|
|
||||||
local function submit_tr()
|
local function submit_tr()
|
||||||
local range_val = tonumber(range.get_value())
|
local range_val = tonumber(range.get_value())
|
||||||
@ -249,7 +282,7 @@ local function config_view(display)
|
|||||||
TextBox{parent=net_c_4,x=1,y=1,height=4,text="Optionally, set the facility authentication key. Do NOT use one of your passwords."}
|
TextBox{parent=net_c_4,x=1,y=1,height=4,text="Optionally, set the facility authentication key. Do NOT use one of your passwords."}
|
||||||
TextBox{parent=net_c_4,x=1,y=6,height=6,text="This enables verifying that messages are authentic, so it is intended for security on multiplayer servers.",fg_bg=g_lg_fg_bg}
|
TextBox{parent=net_c_4,x=1,y=6,height=6,text="This enables verifying that messages are authentic, so it is intended for security on multiplayer servers.",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=net_c_4,x=1,y=12,height=1,text="Facility Auth Key"}
|
TextBox{parent=net_c_4,x=1,y=12,text="Facility Auth Key"}
|
||||||
local key, _, censor = TextField{parent=net_c_4,x=1,y=13,max_len=64,value=ini_cfg.AuthKey,width=24,height=1,fg_bg=bw_fg_bg}
|
local key, _, censor = TextField{parent=net_c_4,x=1,y=13,max_len=64,value=ini_cfg.AuthKey,width=24,height=1,fg_bg=bw_fg_bg}
|
||||||
|
|
||||||
local function censor_key(enable) censor(util.trinary(enable, "*", nil)) end
|
local function censor_key(enable) censor(util.trinary(enable, "*", nil)) end
|
||||||
@ -262,13 +295,13 @@ local function config_view(display)
|
|||||||
hide_key.set_value(true)
|
hide_key.set_value(true)
|
||||||
censor_key(true)
|
censor_key(true)
|
||||||
|
|
||||||
local key_err = TextBox{parent=net_c_4,x=1,y=14,height=1,width=24,text="Length must be > 7.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
local key_err = TextBox{parent=net_c_4,x=1,y=14,width=24,text="Length must be > 7.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||||
|
|
||||||
local function submit_auth()
|
local function submit_auth()
|
||||||
local v = key.get_value()
|
local v = key.get_value()
|
||||||
if string.len(v) == 0 or string.len(v) >= 8 then
|
if string.len(v) == 0 or string.len(v) >= 8 then
|
||||||
tmp_cfg.AuthKey = key.get_value()
|
tmp_cfg.AuthKey = key.get_value()
|
||||||
main_pane.set_value(3)
|
main_pane.set_value(4)
|
||||||
key_err.hide(true)
|
key_err.hide(true)
|
||||||
else key_err.show() end
|
else key_err.show() end
|
||||||
end
|
end
|
||||||
@ -281,20 +314,20 @@ local function config_view(display)
|
|||||||
|
|
||||||
local log_c_1 = Div{parent=log_cfg,x=2,y=4,width=24}
|
local log_c_1 = Div{parent=log_cfg,x=2,y=4,width=24}
|
||||||
|
|
||||||
TextBox{parent=log_cfg,x=1,y=2,height=1,text=" Logging Configuration",fg_bg=cpair(colors.black,colors.pink)}
|
TextBox{parent=log_cfg,x=1,y=2,text=" Logging Configuration",fg_bg=cpair(colors.black,colors.pink)}
|
||||||
|
|
||||||
TextBox{parent=log_c_1,x=1,y=1,height=1,text="Configure logging below."}
|
TextBox{parent=log_c_1,x=1,y=1,text="Configure logging below."}
|
||||||
|
|
||||||
TextBox{parent=log_c_1,x=1,y=3,height=1,text="Log File Mode"}
|
TextBox{parent=log_c_1,x=1,y=3,text="Log File Mode"}
|
||||||
local mode = RadioButton{parent=log_c_1,x=1,y=4,default=ini_cfg.LogMode+1,options={"Append on Startup","Replace on Startup"},callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.pink}
|
local mode = RadioButton{parent=log_c_1,x=1,y=4,default=ini_cfg.LogMode+1,options={"Append on Startup","Replace on Startup"},callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.pink}
|
||||||
|
|
||||||
TextBox{parent=log_c_1,x=1,y=7,height=1,text="Log File Path"}
|
TextBox{parent=log_c_1,x=1,y=7,text="Log File Path"}
|
||||||
local path = TextField{parent=log_c_1,x=1,y=8,width=24,height=1,value=ini_cfg.LogPath,max_len=128,fg_bg=bw_fg_bg}
|
local path = TextField{parent=log_c_1,x=1,y=8,width=24,height=1,value=ini_cfg.LogPath,max_len=128,fg_bg=bw_fg_bg}
|
||||||
|
|
||||||
local en_dbg = CheckBox{parent=log_c_1,x=1,y=10,default=ini_cfg.LogDebug,label="Enable Debug Messages",box_fg_bg=cpair(colors.pink,colors.black)}
|
local en_dbg = CheckBox{parent=log_c_1,x=1,y=10,default=ini_cfg.LogDebug,label="Enable Debug Messages",box_fg_bg=cpair(colors.pink,colors.black)}
|
||||||
TextBox{parent=log_c_1,x=3,y=11,height=4,text="This results in much larger log files. Use only as needed.",fg_bg=g_lg_fg_bg}
|
TextBox{parent=log_c_1,x=3,y=11,height=4,text="This results in much larger log files. Use only as needed.",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
local path_err = TextBox{parent=log_c_1,x=1,y=14,height=1,width=24,text="Provide a log file path.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
local path_err = TextBox{parent=log_c_1,x=1,y=14,width=24,text="Provide a log file path.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||||
|
|
||||||
local function submit_log()
|
local function submit_log()
|
||||||
if path.get_value() ~= "" then
|
if path.get_value() ~= "" then
|
||||||
@ -306,11 +339,11 @@ local function config_view(display)
|
|||||||
tool_ctl.viewing_config = false
|
tool_ctl.viewing_config = false
|
||||||
tool_ctl.importing_legacy = false
|
tool_ctl.importing_legacy = false
|
||||||
tool_ctl.settings_apply.show()
|
tool_ctl.settings_apply.show()
|
||||||
main_pane.set_value(4)
|
main_pane.set_value(5)
|
||||||
else path_err.show() end
|
else path_err.show() end
|
||||||
end
|
end
|
||||||
|
|
||||||
PushButton{parent=log_c_1,x=1,y=15,text="\x1b Back",callback=function()main_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=log_c_1,x=1,y=15,text="\x1b Back",callback=function()main_pane.set_value(3)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
PushButton{parent=log_c_1,x=19,y=15,text="Next \x1a",callback=submit_log,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=log_c_1,x=19,y=15,text="Next \x1a",callback=submit_log,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
--#endregion
|
--#endregion
|
||||||
@ -324,7 +357,7 @@ local function config_view(display)
|
|||||||
|
|
||||||
local sum_pane = MultiPane{parent=summary,x=1,y=4,panes={sum_c_1,sum_c_2,sum_c_3,sum_c_4}}
|
local sum_pane = MultiPane{parent=summary,x=1,y=4,panes={sum_c_1,sum_c_2,sum_c_3,sum_c_4}}
|
||||||
|
|
||||||
TextBox{parent=summary,x=1,y=2,height=1,text=" Summary",fg_bg=cpair(colors.black,colors.green)}
|
TextBox{parent=summary,x=1,y=2,text=" Summary",fg_bg=cpair(colors.black,colors.green)}
|
||||||
|
|
||||||
local setting_list = ListBox{parent=sum_c_1,x=1,y=1,height=11,width=24,scroll_height=100,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
|
local setting_list = ListBox{parent=sum_c_1,x=1,y=1,height=11,width=24,scroll_height=100,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
|
||||||
|
|
||||||
@ -335,7 +368,7 @@ local function config_view(display)
|
|||||||
tool_ctl.importing_legacy = false
|
tool_ctl.importing_legacy = false
|
||||||
tool_ctl.settings_apply.show()
|
tool_ctl.settings_apply.show()
|
||||||
else
|
else
|
||||||
main_pane.set_value(3)
|
main_pane.set_value(4)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -346,12 +379,17 @@ local function config_view(display)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function save_and_continue()
|
local function save_and_continue()
|
||||||
for k, v in pairs(tmp_cfg) do settings.set(k, v) end
|
for _, field in ipairs(fields) do
|
||||||
|
local k, v = field[1], tmp_cfg[field[1]]
|
||||||
|
if v == nil then settings.unset(k) else settings.set(k, v) end
|
||||||
|
end
|
||||||
|
|
||||||
if settings.save("/pocket.settings") then
|
if settings.save("/pocket.settings") then
|
||||||
load_settings(settings_cfg, true)
|
load_settings(settings_cfg, true)
|
||||||
load_settings(ini_cfg)
|
load_settings(ini_cfg)
|
||||||
|
|
||||||
|
try_set(temp_scale, ini_cfg.TempScale)
|
||||||
|
try_set(energy_scale, ini_cfg.EnergyScale)
|
||||||
try_set(svr_chan, ini_cfg.SVR_Channel)
|
try_set(svr_chan, ini_cfg.SVR_Channel)
|
||||||
try_set(crd_chan, ini_cfg.CRD_Channel)
|
try_set(crd_chan, ini_cfg.CRD_Channel)
|
||||||
try_set(pkt_chan, ini_cfg.PKT_Channel)
|
try_set(pkt_chan, ini_cfg.PKT_Channel)
|
||||||
@ -379,7 +417,7 @@ local function config_view(display)
|
|||||||
tool_ctl.show_key_btn = PushButton{parent=sum_c_1,x=1,y=13,min_width=17,text="Unhide Auth Key",callback=function()tool_ctl.show_auth_key()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg,dis_fg_bg=dis_fg_bg}
|
tool_ctl.show_key_btn = PushButton{parent=sum_c_1,x=1,y=13,min_width=17,text="Unhide Auth Key",callback=function()tool_ctl.show_auth_key()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg,dis_fg_bg=dis_fg_bg}
|
||||||
tool_ctl.settings_apply = PushButton{parent=sum_c_1,x=18,y=15,min_width=7,text="Apply",callback=save_and_continue,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg}
|
tool_ctl.settings_apply = PushButton{parent=sum_c_1,x=18,y=15,min_width=7,text="Apply",callback=save_and_continue,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=sum_c_2,x=1,y=1,height=1,text="Settings saved!"}
|
TextBox{parent=sum_c_2,x=1,y=1,text="Settings saved!"}
|
||||||
|
|
||||||
local function go_home()
|
local function go_home()
|
||||||
main_pane.set_value(1)
|
main_pane.set_value(1)
|
||||||
@ -410,15 +448,15 @@ local function config_view(display)
|
|||||||
|
|
||||||
local cl = Div{parent=changelog,x=2,y=4,width=24}
|
local cl = Div{parent=changelog,x=2,y=4,width=24}
|
||||||
|
|
||||||
TextBox{parent=changelog,x=1,y=2,height=1,text=" Config Change Log",fg_bg=bw_fg_bg}
|
TextBox{parent=changelog,x=1,y=2,text=" Config Change Log",fg_bg=bw_fg_bg}
|
||||||
|
|
||||||
local c_log = ListBox{parent=cl,x=1,y=1,height=13,width=24,scroll_height=100,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
|
local c_log = ListBox{parent=cl,x=1,y=1,height=13,width=24,scroll_height=100,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
|
||||||
|
|
||||||
for _, change in ipairs(changes) do
|
for _, change in ipairs(changes) do
|
||||||
TextBox{parent=c_log,text=change[1],height=1,fg_bg=bw_fg_bg}
|
TextBox{parent=c_log,text=change[1],fg_bg=bw_fg_bg}
|
||||||
for _, v in ipairs(change[2]) do
|
for _, v in ipairs(change[2]) do
|
||||||
local e = Div{parent=c_log,height=#util.strwrap(v,21)}
|
local e = Div{parent=c_log,height=#util.strwrap(v,21)}
|
||||||
TextBox{parent=e,y=1,x=1,text="- ",height=1,fg_bg=cpair(colors.gray,colors.white)}
|
TextBox{parent=e,y=1,x=1,text="- ",fg_bg=cpair(colors.gray,colors.white)}
|
||||||
TextBox{parent=e,y=1,x=3,text=v,height=e.get_height(),fg_bg=cpair(colors.gray,colors.white)}
|
TextBox{parent=e,y=1,x=3,text=v,height=e.get_height(),fg_bg=cpair(colors.gray,colors.white)}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -444,7 +482,7 @@ local function config_view(display)
|
|||||||
|
|
||||||
tool_ctl.gen_summary(tmp_cfg)
|
tool_ctl.gen_summary(tmp_cfg)
|
||||||
sum_pane.set_value(1)
|
sum_pane.set_value(1)
|
||||||
main_pane.set_value(4)
|
main_pane.set_value(5)
|
||||||
tool_ctl.importing_legacy = true
|
tool_ctl.importing_legacy = true
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -473,8 +511,15 @@ local function config_view(display)
|
|||||||
local raw = cfg[f[1]]
|
local raw = cfg[f[1]]
|
||||||
local val = util.strval(raw)
|
local val = util.strval(raw)
|
||||||
|
|
||||||
if f[1] == "AuthKey" then val = string.rep("*", string.len(val))
|
if f[1] == "AuthKey" then
|
||||||
elseif f[1] == "LogMode" then val = util.trinary(raw == log.MODE.APPEND, "append", "replace") end
|
val = string.rep("*", string.len(val))
|
||||||
|
elseif f[1] == "LogMode" then
|
||||||
|
val = util.trinary(raw == log.MODE.APPEND, "append", "replace")
|
||||||
|
elseif f[1] == "TempScale" then
|
||||||
|
val = util.strval(types.TEMP_SCALE_NAMES[raw])
|
||||||
|
elseif f[1] == "EnergyScale" then
|
||||||
|
val = util.strval(types.ENERGY_SCALE_NAMES[raw])
|
||||||
|
end
|
||||||
|
|
||||||
if val == "nil" then val = "<not set>" end
|
if val == "nil" then val = "<not set>" end
|
||||||
|
|
||||||
@ -532,9 +577,7 @@ function configurator.configure(ask_config)
|
|||||||
local event, param1, param2, param3 = util.pull_event()
|
local event, param1, param2, param3 = util.pull_event()
|
||||||
|
|
||||||
-- handle event
|
-- handle event
|
||||||
if event == "timer" then
|
if event == "mouse_click" or event == "mouse_up" or event == "mouse_drag" or event == "mouse_scroll" or event == "double_click" then
|
||||||
tcd.handle(param1)
|
|
||||||
elseif event == "mouse_click" or event == "mouse_up" or event == "mouse_drag" or event == "mouse_scroll" or event == "double_click" then
|
|
||||||
local m_e = core.events.new_mouse_event(event, param1, param2, param3)
|
local m_e = core.events.new_mouse_event(event, param1, param2, param3)
|
||||||
if m_e then display.handle_mouse(m_e) end
|
if m_e then display.handle_mouse(m_e) end
|
||||||
elseif event == "char" or event == "key" or event == "key_up" then
|
elseif event == "char" or event == "key" or event == "key_up" then
|
||||||
|
@ -2,19 +2,26 @@
|
|||||||
-- I/O Control for Pocket Integration with Supervisor & Coordinator
|
-- I/O Control for Pocket Integration with Supervisor & Coordinator
|
||||||
--
|
--
|
||||||
|
|
||||||
local psil = require("scada-common.psil")
|
local const = require("scada-common.constants")
|
||||||
|
-- local log = require("scada-common.log")
|
||||||
|
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 ALARM = types.ALARM
|
local ALARM = types.ALARM
|
||||||
|
local ALARM_STATE = types.ALARM_STATE
|
||||||
|
|
||||||
|
local ENERGY_SCALE = types.ENERGY_SCALE
|
||||||
|
local ENERGY_UNITS = types.ENERGY_SCALE_UNITS
|
||||||
|
local TEMP_SCALE = types.TEMP_SCALE
|
||||||
|
local TEMP_UNITS = types.TEMP_SCALE_UNITS
|
||||||
|
|
||||||
|
---@todo nominal trip time is ping (0ms to 10ms usually)
|
||||||
|
local WARN_TT = 40
|
||||||
|
local HIGH_TT = 80
|
||||||
|
|
||||||
local iocontrol = {}
|
local iocontrol = {}
|
||||||
|
|
||||||
---@class pocket_ioctl
|
|
||||||
local io = {
|
|
||||||
ps = psil.create()
|
|
||||||
}
|
|
||||||
|
|
||||||
---@enum POCKET_LINK_STATE
|
---@enum POCKET_LINK_STATE
|
||||||
local LINK_STATE = {
|
local LINK_STATE = {
|
||||||
UNLINKED = 0,
|
UNLINKED = 0,
|
||||||
@ -23,23 +30,25 @@ local LINK_STATE = {
|
|||||||
LINKED = 3
|
LINKED = 3
|
||||||
}
|
}
|
||||||
|
|
||||||
---@enum NAV_PAGE
|
iocontrol.LINK_STATE = LINK_STATE
|
||||||
local NAV_PAGE = {
|
|
||||||
HOME = 1,
|
---@class pocket_ioctl
|
||||||
UNITS = 2,
|
local io = {
|
||||||
REACTORS = 3,
|
version = "unknown",
|
||||||
BOILERS = 4,
|
ps = psil.create()
|
||||||
TURBINES = 5,
|
|
||||||
DIAG = 6,
|
|
||||||
D_ALARMS = 7
|
|
||||||
}
|
}
|
||||||
|
|
||||||
iocontrol.LINK_STATE = LINK_STATE
|
local config = nil ---@type pkt_config
|
||||||
iocontrol.NAV_PAGE = NAV_PAGE
|
|
||||||
|
|
||||||
-- initialize facility-independent components of pocket iocontrol
|
-- initialize facility-independent components of pocket iocontrol
|
||||||
---@param comms pocket_comms
|
---@param comms pocket_comms
|
||||||
function iocontrol.init_core(comms)
|
---@param nav pocket_nav
|
||||||
|
---@param cfg pkt_config
|
||||||
|
function iocontrol.init_core(comms, nav, cfg)
|
||||||
|
config = cfg
|
||||||
|
|
||||||
|
io.nav = nav
|
||||||
|
|
||||||
---@class pocket_ioctl_diag
|
---@class pocket_ioctl_diag
|
||||||
io.diag = {}
|
io.diag = {}
|
||||||
|
|
||||||
@ -77,28 +86,812 @@ function iocontrol.init_core(comms)
|
|||||||
tone_indicators = {} -- indicators to update from supervisor tone states
|
tone_indicators = {} -- indicators to update from supervisor tone states
|
||||||
}
|
}
|
||||||
|
|
||||||
---@class pocket_nav
|
-- API access
|
||||||
io.nav = {
|
---@class pocket_ioctl_api
|
||||||
page = NAV_PAGE.HOME, ---@type NAV_PAGE
|
io.api = {
|
||||||
sub_pages = { NAV_PAGE.HOME, NAV_PAGE.UNITS, NAV_PAGE.REACTORS, NAV_PAGE.BOILERS, NAV_PAGE.TURBINES, NAV_PAGE.DIAG },
|
get_unit = function (unit) comms.api__get_unit(unit) end
|
||||||
tasks = {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
-- add a task to be performed periodically while on a given page
|
|
||||||
---@param page NAV_PAGE page to add task to
|
|
||||||
---@param task function function to execute
|
|
||||||
function io.nav.register_task(page, task)
|
|
||||||
if io.nav.tasks[page] == nil then io.nav.tasks[page] = {} end
|
|
||||||
table.insert(io.nav.tasks[page], task)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- initialize facility-dependent components of pocket iocontrol
|
-- initialize facility-dependent components of pocket iocontrol
|
||||||
function iocontrol.init_fac() end
|
---@param conf facility_conf facility configuration
|
||||||
|
function iocontrol.init_fac(conf)
|
||||||
|
local temp_scale, energy_scale = config.TempScale, config.EnergyScale
|
||||||
|
io.temp_label = TEMP_UNITS[temp_scale]
|
||||||
|
io.energy_label = ENERGY_UNITS[energy_scale]
|
||||||
|
|
||||||
|
-- temperature unit label and conversion function (from Kelvin)
|
||||||
|
if temp_scale == TEMP_SCALE.CELSIUS then
|
||||||
|
io.temp_convert = function (t) return t - 273.15 end
|
||||||
|
elseif temp_scale == TEMP_SCALE.FAHRENHEIT then
|
||||||
|
io.temp_convert = function (t) return (1.8 * (t - 273.15)) + 32 end
|
||||||
|
elseif temp_scale == TEMP_SCALE.RANKINE then
|
||||||
|
io.temp_convert = function (t) return 1.8 * t end
|
||||||
|
else
|
||||||
|
io.temp_label = "K"
|
||||||
|
io.temp_convert = function (t) return t end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- energy unit label and conversion function (from Joules unless otherwise specified)
|
||||||
|
if energy_scale == ENERGY_SCALE.FE or energy_scale == ENERGY_SCALE.RF then
|
||||||
|
io.energy_convert = util.joules_to_fe_rf
|
||||||
|
io.energy_convert_from_fe = function (t) return t end
|
||||||
|
io.energy_convert_to_fe = function (t) return t end
|
||||||
|
else
|
||||||
|
io.energy_label = "J"
|
||||||
|
io.energy_convert = function (t) return t end
|
||||||
|
io.energy_convert_from_fe = util.fe_rf_to_joules
|
||||||
|
io.energy_convert_to_fe = util.joules_to_fe_rf
|
||||||
|
end
|
||||||
|
|
||||||
|
-- facility data structure
|
||||||
|
---@class pioctl_facility
|
||||||
|
io.facility = {
|
||||||
|
num_units = conf.num_units,
|
||||||
|
tank_mode = conf.cooling.fac_tank_mode,
|
||||||
|
tank_defs = conf.cooling.fac_tank_defs,
|
||||||
|
all_sys_ok = false,
|
||||||
|
rtu_count = 0,
|
||||||
|
|
||||||
|
auto_ready = false,
|
||||||
|
auto_active = false,
|
||||||
|
auto_ramping = false,
|
||||||
|
auto_saturated = false,
|
||||||
|
|
||||||
|
auto_scram = false,
|
||||||
|
---@type ascram_status
|
||||||
|
ascram_status = {
|
||||||
|
matrix_dc = false,
|
||||||
|
matrix_fill = false,
|
||||||
|
crit_alarm = false,
|
||||||
|
radiation = false,
|
||||||
|
gen_fault = false
|
||||||
|
},
|
||||||
|
|
||||||
|
---@type WASTE_PRODUCT
|
||||||
|
auto_current_waste_product = types.WASTE_PRODUCT.PLUTONIUM,
|
||||||
|
auto_pu_fallback_active = false,
|
||||||
|
|
||||||
|
radiation = types.new_zero_radiation_reading(),
|
||||||
|
|
||||||
|
ps = psil.create(),
|
||||||
|
|
||||||
|
induction_ps_tbl = {},
|
||||||
|
induction_data_tbl = {},
|
||||||
|
|
||||||
|
sps_ps_tbl = {},
|
||||||
|
sps_data_tbl = {},
|
||||||
|
|
||||||
|
tank_ps_tbl = {},
|
||||||
|
tank_data_tbl = {},
|
||||||
|
|
||||||
|
env_d_ps = psil.create(),
|
||||||
|
env_d_data = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
-- create induction and SPS tables (currently only 1 of each is supported)
|
||||||
|
table.insert(io.facility.induction_ps_tbl, psil.create())
|
||||||
|
table.insert(io.facility.induction_data_tbl, {})
|
||||||
|
table.insert(io.facility.sps_ps_tbl, psil.create())
|
||||||
|
table.insert(io.facility.sps_data_tbl, {})
|
||||||
|
|
||||||
|
-- determine tank information
|
||||||
|
if io.facility.tank_mode == 0 then
|
||||||
|
io.facility.tank_defs = {}
|
||||||
|
-- on facility tank mode 0, setup tank defs to match unit tank option
|
||||||
|
for i = 1, conf.num_units do
|
||||||
|
io.facility.tank_defs[i] = util.trinary(conf.cooling.r_cool[i].TankConnection, 1, 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
io.facility.tank_list = { table.unpack(io.facility.tank_defs) }
|
||||||
|
else
|
||||||
|
-- decode the layout of tanks from the connections definitions
|
||||||
|
local tank_mode = io.facility.tank_mode
|
||||||
|
local tank_defs = io.facility.tank_defs
|
||||||
|
local tank_list = { table.unpack(tank_defs) }
|
||||||
|
|
||||||
|
local function calc_fdef(start_idx, end_idx)
|
||||||
|
local first = 4
|
||||||
|
for i = start_idx, end_idx do
|
||||||
|
if io.facility.tank_defs[i] == 2 then
|
||||||
|
if i < first then first = i end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return first
|
||||||
|
end
|
||||||
|
|
||||||
|
if tank_mode == 1 then
|
||||||
|
-- (1) 1 total facility tank (A A A A)
|
||||||
|
local first_fdef = calc_fdef(1, #tank_defs)
|
||||||
|
for i = 1, #tank_defs do
|
||||||
|
if i > first_fdef and tank_defs[i] == 2 then
|
||||||
|
tank_list[i] = 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif tank_mode == 2 then
|
||||||
|
-- (2) 2 total facility tanks (A A A B)
|
||||||
|
local first_fdef = calc_fdef(1, math.min(3, #tank_defs))
|
||||||
|
for i = 1, #tank_defs do
|
||||||
|
if (i ~= 4) and (i > first_fdef) and (tank_defs[i] == 2) then
|
||||||
|
tank_list[i] = 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif tank_mode == 3 then
|
||||||
|
-- (3) 2 total facility tanks (A A B B)
|
||||||
|
for _, a in pairs({ 1, 3 }) do
|
||||||
|
local b = a + 1
|
||||||
|
if (tank_defs[a] == 2) and (tank_defs[b] == 2) then
|
||||||
|
tank_list[b] = 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif tank_mode == 4 then
|
||||||
|
-- (4) 2 total facility tanks (A B B B)
|
||||||
|
local first_fdef = calc_fdef(2, #tank_defs)
|
||||||
|
for i = 1, #tank_defs do
|
||||||
|
if (i ~= 1) and (i > first_fdef) and (tank_defs[i] == 2) then
|
||||||
|
tank_list[i] = 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif tank_mode == 5 then
|
||||||
|
-- (5) 3 total facility tanks (A A B C)
|
||||||
|
local first_fdef = calc_fdef(1, math.min(2, #tank_defs))
|
||||||
|
for i = 1, #tank_defs do
|
||||||
|
if (not (i == 3 or i == 4)) and (i > first_fdef) and (tank_defs[i] == 2) then
|
||||||
|
tank_list[i] = 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif tank_mode == 6 then
|
||||||
|
-- (6) 3 total facility tanks (A B B C)
|
||||||
|
local first_fdef = calc_fdef(2, math.min(3, #tank_defs))
|
||||||
|
for i = 1, #tank_defs do
|
||||||
|
if (not (i == 1 or i == 4)) and (i > first_fdef) and (tank_defs[i] == 2) then
|
||||||
|
tank_list[i] = 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif tank_mode == 7 then
|
||||||
|
-- (7) 3 total facility tanks (A B C C)
|
||||||
|
local first_fdef = calc_fdef(3, #tank_defs)
|
||||||
|
for i = 1, #tank_defs do
|
||||||
|
if (not (i == 1 or i == 2)) and (i > first_fdef) and (tank_defs[i] == 2) then
|
||||||
|
tank_list[i] = 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
io.facility.tank_list = tank_list
|
||||||
|
end
|
||||||
|
|
||||||
|
-- create facility tank tables
|
||||||
|
for i = 1, #io.facility.tank_list do
|
||||||
|
if io.facility.tank_list[i] == 2 then
|
||||||
|
table.insert(io.facility.tank_ps_tbl, psil.create())
|
||||||
|
table.insert(io.facility.tank_data_tbl, {})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- create unit data structures
|
||||||
|
io.units = {}
|
||||||
|
for i = 1, conf.num_units do
|
||||||
|
---@class pioctl_unit
|
||||||
|
local entry = {
|
||||||
|
unit_id = i,
|
||||||
|
connected = false,
|
||||||
|
rtu_hw = {},
|
||||||
|
|
||||||
|
num_boilers = 0,
|
||||||
|
num_turbines = 0,
|
||||||
|
num_snas = 0,
|
||||||
|
has_tank = conf.cooling.r_cool[i].TankConnection,
|
||||||
|
|
||||||
|
control_state = false,
|
||||||
|
burn_rate_cmd = 0.0,
|
||||||
|
radiation = types.new_zero_radiation_reading(),
|
||||||
|
|
||||||
|
sna_peak_rate = 0.0,
|
||||||
|
sna_max_rate = 0.0,
|
||||||
|
sna_out_rate = 0.0,
|
||||||
|
|
||||||
|
waste_mode = types.WASTE_MODE.MANUAL_PLUTONIUM,
|
||||||
|
waste_product = types.WASTE_PRODUCT.PLUTONIUM,
|
||||||
|
|
||||||
|
last_rate_change_ms = 0,
|
||||||
|
turbine_flow_stable = false,
|
||||||
|
|
||||||
|
-- auto control group
|
||||||
|
a_group = 0,
|
||||||
|
|
||||||
|
---@type alarms
|
||||||
|
alarms = { ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE },
|
||||||
|
|
||||||
|
annunciator = {}, ---@type annunciator
|
||||||
|
|
||||||
|
unit_ps = psil.create(),
|
||||||
|
reactor_data = {}, ---@type reactor_db
|
||||||
|
|
||||||
|
boiler_ps_tbl = {},
|
||||||
|
boiler_data_tbl = {},
|
||||||
|
|
||||||
|
turbine_ps_tbl = {},
|
||||||
|
turbine_data_tbl = {},
|
||||||
|
|
||||||
|
tank_ps_tbl = {},
|
||||||
|
tank_data_tbl = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
-- on other facility modes, overwrite unit TANK option with facility tank defs
|
||||||
|
if io.facility.tank_mode ~= 0 then
|
||||||
|
entry.has_tank = conf.cooling.fac_tank_defs[i] > 0
|
||||||
|
end
|
||||||
|
|
||||||
|
-- create boiler tables
|
||||||
|
for _ = 1, conf.cooling.r_cool[i].BoilerCount do
|
||||||
|
table.insert(entry.boiler_ps_tbl, psil.create())
|
||||||
|
table.insert(entry.boiler_data_tbl, {})
|
||||||
|
end
|
||||||
|
|
||||||
|
-- create turbine tables
|
||||||
|
for _ = 1, conf.cooling.r_cool[i].TurbineCount do
|
||||||
|
table.insert(entry.turbine_ps_tbl, psil.create())
|
||||||
|
table.insert(entry.turbine_data_tbl, {})
|
||||||
|
end
|
||||||
|
|
||||||
|
-- create tank tables
|
||||||
|
if io.facility.tank_defs[i] == 1 then
|
||||||
|
table.insert(entry.tank_ps_tbl, psil.create())
|
||||||
|
table.insert(entry.tank_data_tbl, {})
|
||||||
|
end
|
||||||
|
|
||||||
|
entry.num_boilers = #entry.boiler_data_tbl
|
||||||
|
entry.num_turbines = #entry.turbine_data_tbl
|
||||||
|
|
||||||
|
table.insert(io.units, entry)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- set network link state
|
-- set network link state
|
||||||
---@param state POCKET_LINK_STATE
|
---@param state POCKET_LINK_STATE
|
||||||
function iocontrol.report_link_state(state) io.ps.publish("link_state", state) end
|
---@param sv_addr integer|false|nil supervisor address if linked, nil if unchanged, false if unlinked
|
||||||
|
---@param api_addr integer|false|nil coordinator address if linked, nil if unchanged, false if unlinked
|
||||||
|
function iocontrol.report_link_state(state, sv_addr, api_addr)
|
||||||
|
io.ps.publish("link_state", state)
|
||||||
|
|
||||||
|
if state == LINK_STATE.API_LINK_ONLY or state == LINK_STATE.UNLINKED then
|
||||||
|
io.ps.publish("svr_conn_quality", 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
if state == LINK_STATE.SV_LINK_ONLY or state == LINK_STATE.UNLINKED then
|
||||||
|
io.ps.publish("crd_conn_quality", 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
if sv_addr then
|
||||||
|
io.ps.publish("sv_addr", util.c(sv_addr, ":", config.SVR_Channel))
|
||||||
|
elseif sv_addr == false then
|
||||||
|
io.ps.publish("sv_addr", "unknown (not linked)")
|
||||||
|
end
|
||||||
|
|
||||||
|
if api_addr then
|
||||||
|
io.ps.publish("api_addr", util.c(api_addr, ":", config.CRD_Channel))
|
||||||
|
elseif api_addr == false then
|
||||||
|
io.ps.publish("api_addr", "unknown (not linked)")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- show the reason the supervisor connection isn't linking
|
||||||
|
function iocontrol.report_svr_link_error(msg) io.ps.publish("svr_link_msg", msg) end
|
||||||
|
|
||||||
|
-- show the reason the coordinator api connection isn't linking
|
||||||
|
function iocontrol.report_crd_link_error(msg) io.ps.publish("api_link_msg", msg) end
|
||||||
|
|
||||||
|
-- determine supervisor connection quality (trip time)
|
||||||
|
---@param trip_time integer
|
||||||
|
function iocontrol.report_svr_tt(trip_time)
|
||||||
|
local state = 3
|
||||||
|
if trip_time > HIGH_TT then
|
||||||
|
state = 1
|
||||||
|
elseif trip_time > WARN_TT then
|
||||||
|
state = 2
|
||||||
|
end
|
||||||
|
|
||||||
|
io.ps.publish("svr_conn_quality", state)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- determine coordinator connection quality (trip time)
|
||||||
|
---@param trip_time integer
|
||||||
|
function iocontrol.report_crd_tt(trip_time)
|
||||||
|
local state = 3
|
||||||
|
if trip_time > HIGH_TT then
|
||||||
|
state = 1
|
||||||
|
elseif trip_time > WARN_TT then
|
||||||
|
state = 2
|
||||||
|
end
|
||||||
|
|
||||||
|
io.ps.publish("crd_conn_quality", state)
|
||||||
|
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]] ---@type pioctl_unit
|
||||||
|
|
||||||
|
unit.connected = data[2]
|
||||||
|
unit.rtu_hw = data[3]
|
||||||
|
unit.alarms = data[4]
|
||||||
|
|
||||||
|
--#region Annunciator
|
||||||
|
|
||||||
|
unit.annunciator = data[5]
|
||||||
|
|
||||||
|
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[6]
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
if type(unit.reactor_data.rps_status) == "table" then
|
||||||
|
for key, val in pairs(unit.reactor_data.rps_status) do
|
||||||
|
unit.unit_ps.publish(key, val)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if type(unit.reactor_data.mek_status) == "table" then
|
||||||
|
for key, val in pairs(unit.reactor_data.mek_status) do
|
||||||
|
unit.unit_ps.publish(key, val)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
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[7]
|
||||||
|
|
||||||
|
for id = 1, #unit.boiler_data_tbl do
|
||||||
|
local boiler = unit.boiler_data_tbl[id] ---@type boilerv_session_db
|
||||||
|
local ps = unit.boiler_ps_tbl[id] ---@type psil
|
||||||
|
|
||||||
|
local boiler_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[8]
|
||||||
|
|
||||||
|
for id = 1, #unit.turbine_data_tbl do
|
||||||
|
local turbine = unit.turbine_data_tbl[id] ---@type turbinev_session_db
|
||||||
|
local ps = unit.turbine_ps_tbl[id] ---@type psil
|
||||||
|
|
||||||
|
local turbine_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[9]
|
||||||
|
|
||||||
|
unit.last_rate_change_ms = data[10]
|
||||||
|
unit.turbine_flow_stable = data[11]
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
|
--#region Status Information Display
|
||||||
|
|
||||||
|
local ecam = {} -- aviation reference :) back to VATSIM I go...
|
||||||
|
|
||||||
|
-- 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
|
||||||
|
|
||||||
|
-- unit.reactor_data.rps_status = {
|
||||||
|
-- high_dmg = false,
|
||||||
|
-- high_temp = false,
|
||||||
|
-- low_cool = false,
|
||||||
|
-- ex_waste = false,
|
||||||
|
-- ex_hcool = false,
|
||||||
|
-- no_fuel = false,
|
||||||
|
-- fault = false,
|
||||||
|
-- timeout = false,
|
||||||
|
-- manual = false,
|
||||||
|
-- automatic = false,
|
||||||
|
-- sys_fail = false,
|
||||||
|
-- force_dis = false
|
||||||
|
-- }
|
||||||
|
|
||||||
|
-- if unit.reactor_data.rps_status then
|
||||||
|
-- for k, v in pairs(unit.alarms) do
|
||||||
|
-- unit.alarms[k] = ALARM_STATE.TRIPPED
|
||||||
|
-- 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
|
||||||
|
|
||||||
-- get the IO controller database
|
-- get the IO controller database
|
||||||
function iocontrol.get_db() return io end
|
function iocontrol.get_db() return io end
|
||||||
|
@ -8,11 +8,24 @@ local PROTOCOL = comms.PROTOCOL
|
|||||||
local DEVICE_TYPE = comms.DEVICE_TYPE
|
local DEVICE_TYPE = comms.DEVICE_TYPE
|
||||||
local ESTABLISH_ACK = comms.ESTABLISH_ACK
|
local ESTABLISH_ACK = comms.ESTABLISH_ACK
|
||||||
local MGMT_TYPE = comms.MGMT_TYPE
|
local MGMT_TYPE = comms.MGMT_TYPE
|
||||||
|
local CRDN_TYPE = comms.CRDN_TYPE
|
||||||
|
|
||||||
local LINK_STATE = iocontrol.LINK_STATE
|
local LINK_STATE = iocontrol.LINK_STATE
|
||||||
|
|
||||||
local pocket = {}
|
local pocket = {}
|
||||||
|
|
||||||
|
local MQ__RENDER_CMD = {
|
||||||
|
UNLOAD_SV_APPS = 1,
|
||||||
|
UNLOAD_API_APPS = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
local MQ__RENDER_DATA = {
|
||||||
|
LOAD_APP = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
pocket.MQ__RENDER_CMD = MQ__RENDER_CMD
|
||||||
|
pocket.MQ__RENDER_DATA = MQ__RENDER_DATA
|
||||||
|
|
||||||
---@type pkt_config
|
---@type pkt_config
|
||||||
local config = {}
|
local config = {}
|
||||||
|
|
||||||
@ -22,6 +35,9 @@ 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.TempScale = settings.get("TempScale")
|
||||||
|
config.EnergyScale = settings.get("EnergyScale")
|
||||||
|
|
||||||
config.SVR_Channel = settings.get("SVR_Channel")
|
config.SVR_Channel = settings.get("SVR_Channel")
|
||||||
config.CRD_Channel = settings.get("CRD_Channel")
|
config.CRD_Channel = settings.get("CRD_Channel")
|
||||||
config.PKT_Channel = settings.get("PKT_Channel")
|
config.PKT_Channel = settings.get("PKT_Channel")
|
||||||
@ -35,6 +51,11 @@ function pocket.load_config()
|
|||||||
|
|
||||||
local cfv = util.new_validator()
|
local cfv = util.new_validator()
|
||||||
|
|
||||||
|
cfv.assert_type_int(config.TempScale)
|
||||||
|
cfv.assert_range(config.TempScale, 1, 4)
|
||||||
|
cfv.assert_type_int(config.EnergyScale)
|
||||||
|
cfv.assert_range(config.EnergyScale, 1, 3)
|
||||||
|
|
||||||
cfv.assert_channel(config.SVR_Channel)
|
cfv.assert_channel(config.SVR_Channel)
|
||||||
cfv.assert_channel(config.CRD_Channel)
|
cfv.assert_channel(config.CRD_Channel)
|
||||||
cfv.assert_channel(config.PKT_Channel)
|
cfv.assert_channel(config.PKT_Channel)
|
||||||
@ -57,26 +78,310 @@ function pocket.load_config()
|
|||||||
return cfv.valid()
|
return cfv.valid()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@enum POCKET_APP_ID
|
||||||
|
local APP_ID = {
|
||||||
|
ROOT = 1,
|
||||||
|
LOADER = 2,
|
||||||
|
-- main app pages
|
||||||
|
UNITS = 3,
|
||||||
|
GUIDE = 4,
|
||||||
|
ABOUT = 5,
|
||||||
|
-- diag app page
|
||||||
|
ALARMS = 6,
|
||||||
|
-- other
|
||||||
|
DUMMY = 7,
|
||||||
|
NUM_APPS = 7
|
||||||
|
}
|
||||||
|
|
||||||
|
pocket.APP_ID = APP_ID
|
||||||
|
|
||||||
|
---@class nav_tree_page
|
||||||
|
---@field _p nav_tree_page|nil page's parent
|
||||||
|
---@field _c table page's children
|
||||||
|
---@field nav_to function function to navigate to this page
|
||||||
|
---@field switcher function|nil function to switch between children
|
||||||
|
---@field tasks table tasks to run while viewing this page
|
||||||
|
|
||||||
|
-- initialize the page navigation system
|
||||||
|
---@param smem pkt_shared_memory
|
||||||
|
function pocket.init_nav(smem)
|
||||||
|
local self = {
|
||||||
|
pane = nil, ---@type graphics_element
|
||||||
|
sidebar = nil, ---@type graphics_element
|
||||||
|
apps = {},
|
||||||
|
containers = {},
|
||||||
|
help_map = {},
|
||||||
|
help_return = nil,
|
||||||
|
loader_return = nil,
|
||||||
|
cur_app = APP_ID.ROOT
|
||||||
|
}
|
||||||
|
|
||||||
|
self.cur_page = self.root
|
||||||
|
|
||||||
|
---@class pocket_nav
|
||||||
|
local nav = {}
|
||||||
|
|
||||||
|
-- set the root pane element to switch between apps with
|
||||||
|
---@param root_pane graphics_element
|
||||||
|
function nav.set_pane(root_pane) self.pane = root_pane end
|
||||||
|
|
||||||
|
-- link sidebar element
|
||||||
|
---@param sidebar graphics_element
|
||||||
|
function nav.set_sidebar(sidebar) self.sidebar = sidebar end
|
||||||
|
|
||||||
|
-- register an app
|
||||||
|
---@param app_id POCKET_APP_ID app ID
|
||||||
|
---@param container graphics_element element that contains this app (usually a Div)
|
||||||
|
---@param pane? graphics_element multipane if this is a simple paned app, then nav_to must be a number
|
||||||
|
---@param require_sv? boolean true to specifiy if this app should be unloaded when the supervisor connection is lost
|
||||||
|
---@param require_api? boolean true to specifiy if this app should be unloaded when the api connection is lost
|
||||||
|
function nav.register_app(app_id, container, pane, require_sv, require_api)
|
||||||
|
---@class pocket_app
|
||||||
|
local app = {
|
||||||
|
loaded = false,
|
||||||
|
cur_page = nil, ---@type nav_tree_page
|
||||||
|
pane = pane,
|
||||||
|
paned_pages = {},
|
||||||
|
sidebar_items = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
app.load = function () app.loaded = true end
|
||||||
|
app.unload = function () app.loaded = false end
|
||||||
|
|
||||||
|
-- check which connections this requires (for unload)
|
||||||
|
---@return boolean requires_sv, boolean requires_api
|
||||||
|
function app.check_requires() return require_sv or false, require_api or false end
|
||||||
|
|
||||||
|
-- check if any connection is required (for load)
|
||||||
|
function app.requires_conn() return require_sv or require_api or false end
|
||||||
|
|
||||||
|
-- delayed set of the pane if it wasn't ready at the start
|
||||||
|
---@param root_pane graphics_element multipane
|
||||||
|
function app.set_root_pane(root_pane)
|
||||||
|
app.pane = root_pane
|
||||||
|
end
|
||||||
|
|
||||||
|
-- configure the sidebar
|
||||||
|
---@param items table
|
||||||
|
function app.set_sidebar(items)
|
||||||
|
app.sidebar_items = items
|
||||||
|
if self.sidebar then self.sidebar.update(items) end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- function to run on initial load into memory
|
||||||
|
---@param on_load function callback
|
||||||
|
function app.set_load(on_load)
|
||||||
|
app.load = function ()
|
||||||
|
on_load()
|
||||||
|
app.loaded = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- function to run to close out the app
|
||||||
|
---@param on_unload function callback
|
||||||
|
function app.set_unload(on_unload)
|
||||||
|
app.unload = function ()
|
||||||
|
on_unload()
|
||||||
|
app.loaded = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- if a pane was provided, this will switch between numbered pages
|
||||||
|
---@param idx integer page index
|
||||||
|
function app.switcher(idx)
|
||||||
|
if app.paned_pages[idx] then
|
||||||
|
app.paned_pages[idx].nav_to()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- create a new page entry in the app's page navigation tree
|
||||||
|
---@param parent nav_tree_page|nil a parent page or nil to set this as the root
|
||||||
|
---@param nav_to function|integer function to navigate to this page or pane index
|
||||||
|
---@return nav_tree_page new_page this new page
|
||||||
|
function app.new_page(parent, nav_to)
|
||||||
|
---@type nav_tree_page
|
||||||
|
local page = { _p = parent, _c = {}, nav_to = function () end, switcher = function () end, tasks = {} }
|
||||||
|
|
||||||
|
if parent == nil and app.cur_page == nil then
|
||||||
|
app.cur_page = page
|
||||||
|
end
|
||||||
|
|
||||||
|
if type(nav_to) == "number" then
|
||||||
|
app.paned_pages[nav_to] = page
|
||||||
|
|
||||||
|
function page.nav_to()
|
||||||
|
app.cur_page = page
|
||||||
|
if app.pane then app.pane.set_value(nav_to) end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
function page.nav_to()
|
||||||
|
app.cur_page = page
|
||||||
|
nav_to()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- switch between children
|
||||||
|
---@param id integer child ID
|
||||||
|
function page.switcher(id) if page._c[id] then page._c[id].nav_to() end end
|
||||||
|
|
||||||
|
if parent ~= nil then
|
||||||
|
table.insert(page._p._c, page)
|
||||||
|
end
|
||||||
|
|
||||||
|
return page
|
||||||
|
end
|
||||||
|
|
||||||
|
-- delete paned pages and clear the current page
|
||||||
|
function app.delete_pages()
|
||||||
|
app.paned_pages = {}
|
||||||
|
app.cur_page = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- get the currently active page
|
||||||
|
function app.get_current_page() return app.cur_page end
|
||||||
|
|
||||||
|
-- attempt to navigate up the tree
|
||||||
|
---@return boolean success true if successfully navigated up
|
||||||
|
function app.nav_up()
|
||||||
|
local parent = app.cur_page._p
|
||||||
|
if parent then parent.nav_to() end
|
||||||
|
return parent ~= nil
|
||||||
|
end
|
||||||
|
|
||||||
|
self.apps[app_id] = app
|
||||||
|
self.containers[app_id] = container
|
||||||
|
|
||||||
|
return app
|
||||||
|
end
|
||||||
|
|
||||||
|
-- open an app
|
||||||
|
---@param app_id POCKET_APP_ID
|
||||||
|
function nav.open_app(app_id)
|
||||||
|
-- reset help return on navigating out of an app
|
||||||
|
if app_id == APP_ID.ROOT then self.help_return = nil end
|
||||||
|
|
||||||
|
local app = self.apps[app_id] ---@type pocket_app
|
||||||
|
if app then
|
||||||
|
if app.requires_conn() and not smem.pkt_sys.pocket_comms.is_linked() then
|
||||||
|
-- bring up the app loader
|
||||||
|
self.loader_return = app_id
|
||||||
|
app_id = APP_ID.LOADER
|
||||||
|
app = self.apps[app_id]
|
||||||
|
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
|
||||||
|
|
||||||
|
self.cur_app = app_id
|
||||||
|
self.pane.set_value(app_id)
|
||||||
|
|
||||||
|
if #app.sidebar_items > 0 then
|
||||||
|
self.sidebar.update(app.sidebar_items)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
log.debug("tried to open unknown app")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- open the app that was blocked on connecting
|
||||||
|
function nav.on_loader_connected()
|
||||||
|
if self.loader_return then
|
||||||
|
nav.open_app(self.loader_return)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- load a given app
|
||||||
|
---@param app_id POCKET_APP_ID
|
||||||
|
function nav.load_app(app_id)
|
||||||
|
self.apps[app_id].load()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- unload api-dependent apps
|
||||||
|
function nav.unload_api()
|
||||||
|
for id, app in pairs(self.apps) do
|
||||||
|
local _, api = app.check_requires()
|
||||||
|
if app.loaded and api then
|
||||||
|
if id == self.cur_app then nav.open_app(APP_ID.ROOT) end
|
||||||
|
app.unload()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- unload supervisor-dependent apps
|
||||||
|
function nav.unload_sv()
|
||||||
|
for id, app in pairs(self.apps) do
|
||||||
|
local sv, _ = app.check_requires()
|
||||||
|
if app.loaded and sv then
|
||||||
|
if id == self.cur_app then nav.open_app(APP_ID.ROOT) end
|
||||||
|
app.unload()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- get a list of the app containers (usually Div elements)
|
||||||
|
function nav.get_containers() return self.containers end
|
||||||
|
|
||||||
|
-- get the currently active page
|
||||||
|
---@return nav_tree_page
|
||||||
|
function nav.get_current_page()
|
||||||
|
return self.apps[self.cur_app].get_current_page()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- attempt to navigate up within the active app, otherwise open home page<br>
|
||||||
|
-- except, this will go back to a prior app if leaving the help app after open_help was used
|
||||||
|
function nav.nav_up()
|
||||||
|
-- return out of help if opened with open_help
|
||||||
|
if self.help_return then
|
||||||
|
nav.open_app(self.help_return)
|
||||||
|
self.help_return = nil
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local app = self.apps[self.cur_app] ---@type pocket_app
|
||||||
|
log.debug("attempting app nav up for app " .. self.cur_app)
|
||||||
|
|
||||||
|
if not app.nav_up() then
|
||||||
|
log.debug("internal app nav up failed, going to home screen")
|
||||||
|
nav.open_app(APP_ID.ROOT)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- open the help app, to show the reference for a key
|
||||||
|
function nav.open_help(key)
|
||||||
|
self.help_return = self.cur_app
|
||||||
|
|
||||||
|
nav.open_app(APP_ID.GUIDE)
|
||||||
|
|
||||||
|
local load = self.help_map[key]
|
||||||
|
if load then load() end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- link the help map from the guide app
|
||||||
|
function nav.link_help(map) self.help_map = map end
|
||||||
|
|
||||||
|
return nav
|
||||||
|
end
|
||||||
|
|
||||||
-- pocket coordinator + supervisor communications
|
-- pocket coordinator + supervisor communications
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
---@param version string pocket version
|
---@param version string pocket version
|
||||||
---@param nic nic network interface device
|
---@param nic nic network interface device
|
||||||
---@param sv_watchdog watchdog
|
---@param sv_watchdog watchdog
|
||||||
---@param api_watchdog watchdog
|
---@param api_watchdog watchdog
|
||||||
function pocket.comms(version, nic, sv_watchdog, api_watchdog)
|
---@param nav pocket_nav
|
||||||
|
function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav)
|
||||||
local self = {
|
local self = {
|
||||||
sv = {
|
sv = {
|
||||||
linked = false,
|
linked = false,
|
||||||
addr = comms.BROADCAST,
|
addr = comms.BROADCAST,
|
||||||
seq_num = 0,
|
seq_num = util.time_ms() * 10, -- unique per peer, restarting will not re-use seq nums due to message rate
|
||||||
r_seq_num = nil, ---@type nil|integer
|
r_seq_num = nil, ---@type nil|integer
|
||||||
last_est_ack = ESTABLISH_ACK.ALLOW
|
last_est_ack = ESTABLISH_ACK.ALLOW
|
||||||
},
|
},
|
||||||
api = {
|
api = {
|
||||||
linked = false,
|
linked = false,
|
||||||
addr = comms.BROADCAST,
|
addr = comms.BROADCAST,
|
||||||
seq_num = 0,
|
seq_num = util.time_ms() * 10, -- unique per peer, restarting will not re-use seq nums due to message rate
|
||||||
r_seq_num = nil, ---@type nil|integer
|
r_seq_num = nil, ---@type nil|integer
|
||||||
last_est_ack = ESTABLISH_ACK.ALLOW
|
last_est_ack = ESTABLISH_ACK.ALLOW
|
||||||
},
|
},
|
||||||
establish_delay_counter = 0
|
establish_delay_counter = 0
|
||||||
@ -118,6 +423,20 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
|
|||||||
self.api.seq_num = self.api.seq_num + 1
|
self.api.seq_num = self.api.seq_num + 1
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- send an API packet to the coordinator
|
||||||
|
---@param msg_type CRDN_TYPE
|
||||||
|
---@param msg table
|
||||||
|
local function _send_api(msg_type, msg)
|
||||||
|
local s_pkt = comms.scada_packet()
|
||||||
|
local pkt = comms.crdn_packet()
|
||||||
|
|
||||||
|
pkt.make(msg_type, msg)
|
||||||
|
s_pkt.make(self.api.addr, self.api.seq_num, PROTOCOL.SCADA_CRDN, pkt.raw_sendable())
|
||||||
|
|
||||||
|
nic.transmit(config.CRD_Channel, config.PKT_Channel, s_pkt)
|
||||||
|
self.api.seq_num = self.api.seq_num + 1
|
||||||
|
end
|
||||||
|
|
||||||
-- attempt supervisor connection establishment
|
-- attempt supervisor connection establishment
|
||||||
local function _send_sv_establish()
|
local function _send_sv_establish()
|
||||||
_send_sv(MGMT_TYPE.ESTABLISH, { comms.version, version, DEVICE_TYPE.PKT })
|
_send_sv(MGMT_TYPE.ESTABLISH, { comms.version, version, DEVICE_TYPE.PKT })
|
||||||
@ -125,7 +444,7 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
|
|||||||
|
|
||||||
-- attempt coordinator API connection establishment
|
-- attempt coordinator API connection establishment
|
||||||
local function _send_api_establish()
|
local function _send_api_establish()
|
||||||
_send_crd(MGMT_TYPE.ESTABLISH, { comms.version, version, DEVICE_TYPE.PKT })
|
_send_crd(MGMT_TYPE.ESTABLISH, { comms.version, version, DEVICE_TYPE.PKT, comms.api_version })
|
||||||
end
|
end
|
||||||
|
|
||||||
-- keep alive ack to supervisor
|
-- keep alive ack to supervisor
|
||||||
@ -148,6 +467,7 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
|
|||||||
-- close connection to the supervisor
|
-- close connection to the supervisor
|
||||||
function public.close_sv()
|
function public.close_sv()
|
||||||
sv_watchdog.cancel()
|
sv_watchdog.cancel()
|
||||||
|
nav.unload_sv()
|
||||||
self.sv.linked = false
|
self.sv.linked = false
|
||||||
self.sv.r_seq_num = nil
|
self.sv.r_seq_num = nil
|
||||||
self.sv.addr = comms.BROADCAST
|
self.sv.addr = comms.BROADCAST
|
||||||
@ -157,6 +477,7 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
|
|||||||
-- close connection to coordinator API server
|
-- close connection to coordinator API server
|
||||||
function public.close_api()
|
function public.close_api()
|
||||||
api_watchdog.cancel()
|
api_watchdog.cancel()
|
||||||
|
nav.unload_api()
|
||||||
self.api.linked = false
|
self.api.linked = false
|
||||||
self.api.r_seq_num = nil
|
self.api.r_seq_num = nil
|
||||||
self.api.addr = comms.BROADCAST
|
self.api.addr = comms.BROADCAST
|
||||||
@ -172,7 +493,11 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
|
|||||||
-- attempt to re-link if any of the dependent links aren't active
|
-- attempt to re-link if any of the dependent links aren't active
|
||||||
function public.link_update()
|
function public.link_update()
|
||||||
if not self.sv.linked then
|
if not self.sv.linked then
|
||||||
iocontrol.report_link_state(util.trinary(self.api.linked, LINK_STATE.API_LINK_ONLY, LINK_STATE.UNLINKED))
|
if self.api.linked then
|
||||||
|
iocontrol.report_link_state(LINK_STATE.API_LINK_ONLY, false, nil)
|
||||||
|
else
|
||||||
|
iocontrol.report_link_state(LINK_STATE.UNLINKED, false, false)
|
||||||
|
end
|
||||||
|
|
||||||
if self.establish_delay_counter <= 0 then
|
if self.establish_delay_counter <= 0 then
|
||||||
_send_sv_establish()
|
_send_sv_establish()
|
||||||
@ -181,7 +506,7 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
|
|||||||
self.establish_delay_counter = self.establish_delay_counter - 1
|
self.establish_delay_counter = self.establish_delay_counter - 1
|
||||||
end
|
end
|
||||||
elseif not self.api.linked then
|
elseif not self.api.linked then
|
||||||
iocontrol.report_link_state(LINK_STATE.SV_LINK_ONLY)
|
iocontrol.report_link_state(LINK_STATE.SV_LINK_ONLY, nil, false)
|
||||||
|
|
||||||
if self.establish_delay_counter <= 0 then
|
if self.establish_delay_counter <= 0 then
|
||||||
_send_api_establish()
|
_send_api_establish()
|
||||||
@ -189,9 +514,6 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
|
|||||||
else
|
else
|
||||||
self.establish_delay_counter = self.establish_delay_counter - 1
|
self.establish_delay_counter = self.establish_delay_counter - 1
|
||||||
end
|
end
|
||||||
else
|
|
||||||
-- linked, all good!
|
|
||||||
iocontrol.report_link_state(LINK_STATE.LINKED)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -214,6 +536,11 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
|
|||||||
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 unit data
|
||||||
|
function public.api__get_unit(unit)
|
||||||
|
if self.api.linked then _send_api(CRDN_TYPE.API_GET_UNIT, { unit }) end
|
||||||
|
end
|
||||||
|
|
||||||
-- parse a packet
|
-- parse a packet
|
||||||
---@param side string
|
---@param side string
|
||||||
---@param sender integer
|
---@param sender integer
|
||||||
@ -246,6 +573,25 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
|
|||||||
return pkt
|
return pkt
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param packet mgmt_frame|crdn_frame
|
||||||
|
---@param length integer
|
||||||
|
---@param max integer?
|
||||||
|
---@return boolean
|
||||||
|
local function _check_length(packet, length, max)
|
||||||
|
local ok = util.trinary(max == nil, packet.length == length, packet.length >= length and packet.length <= (max or 0))
|
||||||
|
if not ok then
|
||||||
|
local fmt = "[comms] RX_PACKET{r_chan=%d,proto=%d,type=%d}: packet length mismatch -> expect %d != actual %d"
|
||||||
|
log.debug(util.sprintf(fmt, packet.scada_frame.remote_channel(), packet.scada_frame.protocol(), packet.type, length, packet.scada_frame.length()))
|
||||||
|
end
|
||||||
|
return ok
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param packet mgmt_frame|crdn_frame
|
||||||
|
local function _fail_type(packet)
|
||||||
|
local fmt = "[comms] RX_PACKET{r_chan=%d,proto=%d,type=%d}: unrecognized packet type"
|
||||||
|
log.debug(util.sprintf(fmt, packet.scada_frame.remote_channel(), packet.scada_frame.protocol(), packet.type))
|
||||||
|
end
|
||||||
|
|
||||||
-- handle a packet
|
-- handle a packet
|
||||||
---@param packet mgmt_frame|crdn_frame|nil
|
---@param packet mgmt_frame|crdn_frame|nil
|
||||||
function public.handle_packet(packet)
|
function public.handle_packet(packet)
|
||||||
@ -262,27 +608,42 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
|
|||||||
elseif r_chan == config.CRD_Channel then
|
elseif r_chan == config.CRD_Channel then
|
||||||
-- check sequence number
|
-- check sequence number
|
||||||
if self.api.r_seq_num == nil then
|
if self.api.r_seq_num == nil then
|
||||||
self.api.r_seq_num = packet.scada_frame.seq_num()
|
self.api.r_seq_num = packet.scada_frame.seq_num() + 1
|
||||||
elseif self.connected and ((self.api.r_seq_num + 1) ~= packet.scada_frame.seq_num()) then
|
elseif self.api.r_seq_num ~= packet.scada_frame.seq_num() then
|
||||||
log.warning("sequence out-of-order (API): last = " .. self.api.r_seq_num .. ", new = " .. packet.scada_frame.seq_num())
|
log.warning("sequence out-of-order (API): next = " .. self.api.r_seq_num .. ", new = " .. packet.scada_frame.seq_num())
|
||||||
return
|
return
|
||||||
elseif self.api.linked and (src_addr ~= self.api.addr) then
|
elseif self.api.linked and (src_addr ~= self.api.addr) then
|
||||||
log.debug("received packet from unknown computer " .. src_addr .. " while linked (API expected " .. self.api.addr ..
|
log.debug("received packet from unknown computer " .. src_addr .. " while linked (API expected " .. self.api.addr ..
|
||||||
"); channel in use by another system?")
|
"); channel in use by another system?")
|
||||||
return
|
return
|
||||||
else
|
else
|
||||||
self.api.r_seq_num = packet.scada_frame.seq_num()
|
self.api.r_seq_num = packet.scada_frame.seq_num() + 1
|
||||||
end
|
end
|
||||||
|
|
||||||
-- feed watchdog on valid sequence number
|
-- feed watchdog on valid sequence number
|
||||||
api_watchdog.feed()
|
api_watchdog.feed()
|
||||||
|
|
||||||
if protocol == PROTOCOL.SCADA_MGMT then
|
if protocol == PROTOCOL.SCADA_CRDN then
|
||||||
|
---@cast packet crdn_frame
|
||||||
|
if self.api.linked then
|
||||||
|
if packet.type == CRDN_TYPE.API_GET_FAC then
|
||||||
|
if _check_length(packet, 11) then
|
||||||
|
iocontrol.record_facility_data(packet.data)
|
||||||
|
end
|
||||||
|
elseif packet.type == CRDN_TYPE.API_GET_UNIT then
|
||||||
|
if _check_length(packet, 11) and type(packet.data[1]) == "number" and iocontrol.get_db().units[packet.data[1]] then
|
||||||
|
iocontrol.record_unit_data(packet.data)
|
||||||
|
end
|
||||||
|
else _fail_type(packet) end
|
||||||
|
else
|
||||||
|
log.debug("discarding coordinator SCADA_CRDN packet before linked")
|
||||||
|
end
|
||||||
|
elseif protocol == PROTOCOL.SCADA_MGMT then
|
||||||
---@cast packet mgmt_frame
|
---@cast packet mgmt_frame
|
||||||
if self.api.linked then
|
if self.api.linked then
|
||||||
if packet.type == MGMT_TYPE.KEEP_ALIVE then
|
if packet.type == MGMT_TYPE.KEEP_ALIVE then
|
||||||
-- keep alive request received, echo back
|
-- keep alive request received, echo back
|
||||||
if packet.length == 1 then
|
if _check_length(packet, 1) then
|
||||||
local timestamp = packet.data[1]
|
local timestamp = packet.data[1]
|
||||||
local trip_time = util.time() - timestamp
|
local trip_time = util.time() - timestamp
|
||||||
|
|
||||||
@ -290,57 +651,80 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
|
|||||||
log.warning("pocket coordinator KEEP_ALIVE trip time > 750ms (" .. trip_time .. "ms)")
|
log.warning("pocket coordinator KEEP_ALIVE trip time > 750ms (" .. trip_time .. "ms)")
|
||||||
end
|
end
|
||||||
|
|
||||||
-- log.debug("pocket coordinator RTT = " .. trip_time .. "ms")
|
-- log.debug("pocket coordinator TT = " .. trip_time .. "ms")
|
||||||
|
|
||||||
_send_api_keep_alive_ack(timestamp)
|
_send_api_keep_alive_ack(timestamp)
|
||||||
else
|
|
||||||
log.debug("coordinator SCADA keep alive packet length mismatch")
|
iocontrol.report_crd_tt(trip_time)
|
||||||
end
|
end
|
||||||
elseif packet.type == MGMT_TYPE.CLOSE then
|
elseif packet.type == MGMT_TYPE.CLOSE then
|
||||||
-- handle session close
|
-- handle session close
|
||||||
api_watchdog.cancel()
|
api_watchdog.cancel()
|
||||||
|
nav.unload_api()
|
||||||
self.api.linked = false
|
self.api.linked = false
|
||||||
self.api.r_seq_num = nil
|
self.api.r_seq_num = nil
|
||||||
self.api.addr = comms.BROADCAST
|
self.api.addr = comms.BROADCAST
|
||||||
log.info("coordinator server connection closed by remote host")
|
log.info("coordinator server connection closed by remote host")
|
||||||
else
|
else _fail_type(packet) end
|
||||||
log.debug("received unknown SCADA_MGMT packet type " .. packet.type .. " from coordinator")
|
|
||||||
end
|
|
||||||
elseif packet.type == MGMT_TYPE.ESTABLISH then
|
elseif packet.type == MGMT_TYPE.ESTABLISH then
|
||||||
-- connection with coordinator established
|
-- connection with coordinator established
|
||||||
if packet.length == 1 then
|
if _check_length(packet, 1, 2) then
|
||||||
local est_ack = packet.data[1]
|
local est_ack = packet.data[1]
|
||||||
|
|
||||||
if est_ack == ESTABLISH_ACK.ALLOW then
|
if est_ack == ESTABLISH_ACK.ALLOW then
|
||||||
log.info("coordinator connection established")
|
if packet.length == 2 then
|
||||||
self.establish_delay_counter = 0
|
local fac_config = packet.data[2]
|
||||||
self.api.linked = true
|
|
||||||
self.api.addr = src_addr
|
|
||||||
|
|
||||||
if self.sv.linked then
|
if type(fac_config) == "table" and #fac_config == 2 then
|
||||||
iocontrol.report_link_state(LINK_STATE.LINKED)
|
-- get configuration
|
||||||
|
local conf = { num_units = fac_config[1], cooling = fac_config[2] }
|
||||||
|
|
||||||
|
iocontrol.init_fac(conf)
|
||||||
|
|
||||||
|
log.info("coordinator connection established")
|
||||||
|
self.establish_delay_counter = 0
|
||||||
|
self.api.linked = true
|
||||||
|
self.api.addr = src_addr
|
||||||
|
|
||||||
|
iocontrol.report_crd_link_error("")
|
||||||
|
|
||||||
|
if self.sv.linked then
|
||||||
|
iocontrol.report_link_state(LINK_STATE.LINKED, nil, self.api.addr)
|
||||||
|
else
|
||||||
|
iocontrol.report_link_state(LINK_STATE.API_LINK_ONLY, nil, self.api.addr)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
log.debug("invalid facility configuration table received from coordinator, establish failed")
|
||||||
|
end
|
||||||
else
|
else
|
||||||
iocontrol.report_link_state(LINK_STATE.API_LINK_ONLY)
|
log.debug("received coordinator establish allow without facility configuration")
|
||||||
end
|
|
||||||
elseif est_ack == ESTABLISH_ACK.DENY then
|
|
||||||
if self.api.last_est_ack ~= est_ack then
|
|
||||||
log.info("coordinator connection denied")
|
|
||||||
end
|
|
||||||
elseif est_ack == ESTABLISH_ACK.COLLISION then
|
|
||||||
if self.api.last_est_ack ~= est_ack then
|
|
||||||
log.info("coordinator connection denied due to collision")
|
|
||||||
end
|
|
||||||
elseif est_ack == ESTABLISH_ACK.BAD_VERSION then
|
|
||||||
if self.api.last_est_ack ~= est_ack then
|
|
||||||
log.info("coordinator comms version mismatch")
|
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
log.debug("coordinator SCADA_MGMT establish packet reply unsupported")
|
if self.api.last_est_ack ~= est_ack then
|
||||||
|
if est_ack == ESTABLISH_ACK.DENY then
|
||||||
|
log.info("coordinator connection denied")
|
||||||
|
iocontrol.report_crd_link_error("denied")
|
||||||
|
elseif est_ack == ESTABLISH_ACK.COLLISION then
|
||||||
|
log.info("coordinator connection denied due to collision")
|
||||||
|
iocontrol.report_crd_link_error("collision")
|
||||||
|
elseif est_ack == ESTABLISH_ACK.BAD_VERSION then
|
||||||
|
log.info("coordinator comms version mismatch")
|
||||||
|
iocontrol.report_crd_link_error("comms version mismatch")
|
||||||
|
elseif est_ack == ESTABLISH_ACK.BAD_API_VERSION then
|
||||||
|
log.info("coordinator api version mismatch")
|
||||||
|
iocontrol.report_crd_link_error("API version mismatch")
|
||||||
|
else
|
||||||
|
log.debug("coordinator SCADA_MGMT establish packet reply unsupported")
|
||||||
|
iocontrol.report_crd_link_error("unknown reply")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- unlink
|
||||||
|
self.api.addr = comms.BROADCAST
|
||||||
|
self.api.linked = false
|
||||||
end
|
end
|
||||||
|
|
||||||
self.api.last_est_ack = est_ack
|
self.api.last_est_ack = est_ack
|
||||||
else
|
|
||||||
log.debug("coordinator SCADA_MGMT establish packet length mismatch")
|
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
log.debug("discarding coordinator non-link SCADA_MGMT packet before linked")
|
log.debug("discarding coordinator non-link SCADA_MGMT packet before linked")
|
||||||
@ -351,16 +735,16 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
|
|||||||
elseif r_chan == config.SVR_Channel then
|
elseif r_chan == config.SVR_Channel then
|
||||||
-- check sequence number
|
-- check sequence number
|
||||||
if self.sv.r_seq_num == nil then
|
if self.sv.r_seq_num == nil then
|
||||||
self.sv.r_seq_num = packet.scada_frame.seq_num()
|
self.sv.r_seq_num = packet.scada_frame.seq_num() + 1
|
||||||
elseif self.connected and ((self.sv.r_seq_num + 1) ~= packet.scada_frame.seq_num()) then
|
elseif self.sv.r_seq_num ~= packet.scada_frame.seq_num() then
|
||||||
log.warning("sequence out-of-order (SVR): last = " .. self.sv.r_seq_num .. ", new = " .. packet.scada_frame.seq_num())
|
log.warning("sequence out-of-order (SVR): next = " .. self.sv.r_seq_num .. ", new = " .. packet.scada_frame.seq_num())
|
||||||
return
|
return
|
||||||
elseif self.sv.linked and (src_addr ~= self.sv.addr) then
|
elseif self.sv.linked and (src_addr ~= self.sv.addr) then
|
||||||
log.debug("received packet from unknown computer " .. src_addr .. " while linked (SVR expected " .. self.sv.addr ..
|
log.debug("received packet from unknown computer " .. src_addr .. " while linked (SVR expected " .. self.sv.addr ..
|
||||||
"); channel in use by another system?")
|
"); channel in use by another system?")
|
||||||
return
|
return
|
||||||
else
|
else
|
||||||
self.sv.r_seq_num = packet.scada_frame.seq_num()
|
self.sv.r_seq_num = packet.scada_frame.seq_num() + 1
|
||||||
end
|
end
|
||||||
|
|
||||||
-- feed watchdog on valid sequence number
|
-- feed watchdog on valid sequence number
|
||||||
@ -372,7 +756,7 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
|
|||||||
if self.sv.linked then
|
if self.sv.linked then
|
||||||
if packet.type == MGMT_TYPE.KEEP_ALIVE then
|
if packet.type == MGMT_TYPE.KEEP_ALIVE then
|
||||||
-- keep alive request received, echo back
|
-- keep alive request received, echo back
|
||||||
if packet.length == 1 then
|
if _check_length(packet, 1) then
|
||||||
local timestamp = packet.data[1]
|
local timestamp = packet.data[1]
|
||||||
local trip_time = util.time() - timestamp
|
local trip_time = util.time() - timestamp
|
||||||
|
|
||||||
@ -380,26 +764,25 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
|
|||||||
log.warning("pocket supervisor KEEP_ALIVE trip time > 750ms (" .. trip_time .. "ms)")
|
log.warning("pocket supervisor KEEP_ALIVE trip time > 750ms (" .. trip_time .. "ms)")
|
||||||
end
|
end
|
||||||
|
|
||||||
-- log.debug("pocket supervisor RTT = " .. trip_time .. "ms")
|
-- log.debug("pocket supervisor TT = " .. trip_time .. "ms")
|
||||||
|
|
||||||
_send_sv_keep_alive_ack(timestamp)
|
_send_sv_keep_alive_ack(timestamp)
|
||||||
else
|
|
||||||
log.debug("supervisor SCADA keep alive packet length mismatch")
|
iocontrol.report_svr_tt(trip_time)
|
||||||
end
|
end
|
||||||
elseif packet.type == MGMT_TYPE.CLOSE then
|
elseif packet.type == MGMT_TYPE.CLOSE then
|
||||||
-- handle session close
|
-- handle session close
|
||||||
sv_watchdog.cancel()
|
sv_watchdog.cancel()
|
||||||
|
nav.unload_sv()
|
||||||
self.sv.linked = false
|
self.sv.linked = false
|
||||||
self.sv.r_seq_num = nil
|
self.sv.r_seq_num = nil
|
||||||
self.sv.addr = comms.BROADCAST
|
self.sv.addr = comms.BROADCAST
|
||||||
log.info("supervisor server connection closed by remote host")
|
log.info("supervisor server connection closed by remote host")
|
||||||
elseif packet.type == MGMT_TYPE.DIAG_TONE_GET then
|
elseif packet.type == MGMT_TYPE.DIAG_TONE_GET then
|
||||||
if packet.length == 8 then
|
if _check_length(packet, 8) then
|
||||||
for i = 1, #packet.data do
|
for i = 1, #packet.data do
|
||||||
diag.tone_test.tone_indicators[i].update(packet.data[i] == true)
|
diag.tone_test.tone_indicators[i].update(packet.data[i] == true)
|
||||||
end
|
end
|
||||||
else
|
|
||||||
log.debug("supervisor SCADA diag alarm states packet length mismatch")
|
|
||||||
end
|
end
|
||||||
elseif packet.type == MGMT_TYPE.DIAG_TONE_SET then
|
elseif packet.type == MGMT_TYPE.DIAG_TONE_SET then
|
||||||
if packet.length == 1 and packet.data[1] == false then
|
if packet.length == 1 and packet.data[1] == false then
|
||||||
@ -438,12 +821,10 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
|
|||||||
else
|
else
|
||||||
log.debug("supervisor SCADA diag alarm set packet length/type mismatch")
|
log.debug("supervisor SCADA diag alarm set packet length/type mismatch")
|
||||||
end
|
end
|
||||||
else
|
else _fail_type(packet) end
|
||||||
log.debug("received unknown SCADA_MGMT packet type " .. packet.type .. " from supervisor")
|
|
||||||
end
|
|
||||||
elseif packet.type == MGMT_TYPE.ESTABLISH then
|
elseif packet.type == MGMT_TYPE.ESTABLISH then
|
||||||
-- connection with supervisor established
|
-- connection with supervisor established
|
||||||
if packet.length == 1 then
|
if _check_length(packet, 1) then
|
||||||
local est_ack = packet.data[1]
|
local est_ack = packet.data[1]
|
||||||
|
|
||||||
if est_ack == ESTABLISH_ACK.ALLOW then
|
if est_ack == ESTABLISH_ACK.ALLOW then
|
||||||
@ -452,37 +833,41 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
|
|||||||
self.sv.linked = true
|
self.sv.linked = true
|
||||||
self.sv.addr = src_addr
|
self.sv.addr = src_addr
|
||||||
|
|
||||||
|
iocontrol.report_svr_link_error("")
|
||||||
|
|
||||||
if self.api.linked then
|
if self.api.linked then
|
||||||
iocontrol.report_link_state(LINK_STATE.LINKED)
|
iocontrol.report_link_state(LINK_STATE.LINKED, self.sv.addr, nil)
|
||||||
else
|
else
|
||||||
iocontrol.report_link_state(LINK_STATE.SV_LINK_ONLY)
|
iocontrol.report_link_state(LINK_STATE.SV_LINK_ONLY, self.sv.addr, nil)
|
||||||
end
|
|
||||||
elseif est_ack == ESTABLISH_ACK.DENY then
|
|
||||||
if self.sv.last_est_ack ~= est_ack then
|
|
||||||
log.info("supervisor connection denied")
|
|
||||||
end
|
|
||||||
elseif est_ack == ESTABLISH_ACK.COLLISION then
|
|
||||||
if self.sv.last_est_ack ~= est_ack then
|
|
||||||
log.info("supervisor connection denied due to collision")
|
|
||||||
end
|
|
||||||
elseif est_ack == ESTABLISH_ACK.BAD_VERSION then
|
|
||||||
if self.sv.last_est_ack ~= est_ack then
|
|
||||||
log.info("supervisor comms version mismatch")
|
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
log.debug("supervisor SCADA_MGMT establish packet reply unsupported")
|
if self.sv.last_est_ack ~= est_ack then
|
||||||
|
if est_ack == ESTABLISH_ACK.DENY then
|
||||||
|
log.info("supervisor connection denied")
|
||||||
|
iocontrol.report_svr_link_error("denied")
|
||||||
|
elseif est_ack == ESTABLISH_ACK.COLLISION then
|
||||||
|
log.info("supervisor connection denied due to collision")
|
||||||
|
iocontrol.report_svr_link_error("collision")
|
||||||
|
elseif est_ack == ESTABLISH_ACK.BAD_VERSION then
|
||||||
|
log.info("supervisor comms version mismatch")
|
||||||
|
iocontrol.report_svr_link_error("comms version mismatch")
|
||||||
|
else
|
||||||
|
log.debug("supervisor SCADA_MGMT establish packet reply unsupported")
|
||||||
|
iocontrol.report_svr_link_error("unknown reply")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- unlink
|
||||||
|
self.sv.addr = comms.BROADCAST
|
||||||
|
self.sv.linked = false
|
||||||
end
|
end
|
||||||
|
|
||||||
self.sv.last_est_ack = est_ack
|
self.sv.last_est_ack = est_ack
|
||||||
else
|
|
||||||
log.debug("supervisor SCADA_MGMT establish packet length mismatch")
|
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
log.debug("discarding supervisor non-link SCADA_MGMT packet before linked")
|
log.debug("discarding supervisor non-link SCADA_MGMT packet before linked")
|
||||||
end
|
end
|
||||||
else
|
else _fail_type(packet) end
|
||||||
log.debug("illegal packet type " .. protocol .. " from supervisor", true)
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
log.debug("received packet from unconfigured channel " .. r_chan, true)
|
log.debug("received packet from unconfigured channel " .. r_chan, true)
|
||||||
end
|
end
|
||||||
@ -497,8 +882,11 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
|
|||||||
---@nodiscard
|
---@nodiscard
|
||||||
function public.is_api_linked() return self.api.linked end
|
function public.is_api_linked() return self.api.linked end
|
||||||
|
|
||||||
|
-- check if we are still linked with the supervisor and coordinator
|
||||||
|
---@nodiscard
|
||||||
|
function public.is_linked() return self.sv.linked and self.api.linked end
|
||||||
|
|
||||||
return public
|
return public
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
return pocket
|
return pocket
|
||||||
|
@ -92,4 +92,20 @@ function renderer.handle_mouse(event)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- handle a keyboard event
|
||||||
|
---@param event key_interaction|nil
|
||||||
|
function renderer.handle_key(event)
|
||||||
|
if ui.display ~= nil and event ~= nil then
|
||||||
|
ui.display.handle_key(event)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- handle a paste event
|
||||||
|
---@param text string
|
||||||
|
function renderer.handle_paste(text)
|
||||||
|
if ui.display ~= nil then
|
||||||
|
ui.display.handle_paste(text)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
return renderer
|
return renderer
|
||||||
|
@ -2,27 +2,35 @@
|
|||||||
-- SCADA System Access on a Pocket Computer
|
-- SCADA System Access on a Pocket Computer
|
||||||
--
|
--
|
||||||
|
|
||||||
|
---@diagnostic disable-next-line: undefined-global
|
||||||
|
local _is_pocket_env = pocket or periphemu -- luacheck: ignore pocket
|
||||||
|
|
||||||
require("/initenv").init_env()
|
require("/initenv").init_env()
|
||||||
|
|
||||||
local crash = require("scada-common.crash")
|
local crash = require("scada-common.crash")
|
||||||
local log = require("scada-common.log")
|
local log = require("scada-common.log")
|
||||||
|
local mqueue = require("scada-common.mqueue")
|
||||||
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 util = require("scada-common.util")
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
local core = require("graphics.core")
|
|
||||||
|
|
||||||
local configure = require("pocket.configure")
|
local configure = require("pocket.configure")
|
||||||
local iocontrol = require("pocket.iocontrol")
|
local iocontrol = require("pocket.iocontrol")
|
||||||
local pocket = require("pocket.pocket")
|
local pocket = require("pocket.pocket")
|
||||||
local renderer = require("pocket.renderer")
|
local renderer = require("pocket.renderer")
|
||||||
|
local threads = require("pocket.threads")
|
||||||
|
|
||||||
local POCKET_VERSION = "v0.7.2-alpha"
|
local POCKET_VERSION = "v0.11.8-alpha"
|
||||||
|
|
||||||
local println = util.println
|
local println = util.println
|
||||||
local println_ts = util.println_ts
|
local println_ts = util.println_ts
|
||||||
|
|
||||||
|
-- check environment (allows Pocket or CraftOS-PC)
|
||||||
|
if not _is_pocket_env then
|
||||||
|
println("You can only use this application on a pocket computer.")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
-- get configuration
|
-- get configuration
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
@ -54,6 +62,7 @@ log.info("BOOTING pocket.startup " .. POCKET_VERSION)
|
|||||||
log.info("========================================")
|
log.info("========================================")
|
||||||
|
|
||||||
crash.set_env("pocket", POCKET_VERSION)
|
crash.set_env("pocket", POCKET_VERSION)
|
||||||
|
crash.dbg_log_env()
|
||||||
|
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
-- main application
|
-- main application
|
||||||
@ -67,10 +76,55 @@ local function main()
|
|||||||
-- mount connected devices
|
-- mount connected devices
|
||||||
ppm.mount_all()
|
ppm.mount_all()
|
||||||
|
|
||||||
|
-- record version for GUI
|
||||||
|
iocontrol.get_db().version = POCKET_VERSION
|
||||||
|
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
-- setup communications & clocks
|
-- memory allocation
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
|
|
||||||
|
-- shared memory across threads
|
||||||
|
---@class pkt_shared_memory
|
||||||
|
local __shared_memory = {
|
||||||
|
-- pocket system state flags
|
||||||
|
---@class pkt_state
|
||||||
|
pkt_state = {
|
||||||
|
ui_ok = false,
|
||||||
|
ui_error = nil,
|
||||||
|
shutdown = false
|
||||||
|
},
|
||||||
|
|
||||||
|
-- core pocket devices
|
||||||
|
pkt_dev = {
|
||||||
|
modem = ppm.get_wireless_modem()
|
||||||
|
},
|
||||||
|
|
||||||
|
-- system objects
|
||||||
|
pkt_sys = {
|
||||||
|
nic = nil, ---@type nic
|
||||||
|
pocket_comms = nil, ---@type pocket_comms
|
||||||
|
sv_wd = nil, ---@type watchdog
|
||||||
|
api_wd = nil, ---@type watchdog
|
||||||
|
nav = nil ---@type pocket_nav
|
||||||
|
},
|
||||||
|
|
||||||
|
-- message queues
|
||||||
|
q = {
|
||||||
|
mq_render = mqueue.new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
local smem_dev = __shared_memory.pkt_dev
|
||||||
|
local smem_sys = __shared_memory.pkt_sys
|
||||||
|
|
||||||
|
local pkt_state = __shared_memory.pkt_state
|
||||||
|
|
||||||
|
----------------------------------------
|
||||||
|
-- setup system
|
||||||
|
----------------------------------------
|
||||||
|
|
||||||
|
smem_sys.nav = pocket.init_nav(__shared_memory)
|
||||||
|
|
||||||
-- message authentication init
|
-- message authentication init
|
||||||
if type(config.AuthKey) == "string" and string.len(config.AuthKey) > 0 then
|
if type(config.AuthKey) == "string" and string.len(config.AuthKey) > 0 then
|
||||||
network.init_mac(config.AuthKey)
|
network.init_mac(config.AuthKey)
|
||||||
@ -79,115 +133,59 @@ local function main()
|
|||||||
iocontrol.report_link_state(iocontrol.LINK_STATE.UNLINKED)
|
iocontrol.report_link_state(iocontrol.LINK_STATE.UNLINKED)
|
||||||
|
|
||||||
-- get the communications modem
|
-- get the communications modem
|
||||||
local modem = ppm.get_wireless_modem()
|
if smem_dev.modem == nil then
|
||||||
if modem == nil then
|
|
||||||
println("startup> wireless modem not found: please craft the pocket computer with a wireless modem")
|
println("startup> wireless modem not found: please craft the pocket computer with a wireless modem")
|
||||||
log.fatal("startup> no wireless modem on startup")
|
log.fatal("startup> no wireless modem on startup")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
-- create connection watchdogs
|
-- create connection watchdogs
|
||||||
local conn_wd = {
|
smem_sys.sv_wd = util.new_watchdog(config.ConnTimeout)
|
||||||
sv = util.new_watchdog(config.ConnTimeout),
|
smem_sys.sv_wd.cancel()
|
||||||
api = util.new_watchdog(config.ConnTimeout)
|
smem_sys.api_wd = util.new_watchdog(config.ConnTimeout)
|
||||||
}
|
smem_sys.api_wd.cancel()
|
||||||
|
|
||||||
conn_wd.sv.cancel()
|
|
||||||
conn_wd.api.cancel()
|
|
||||||
|
|
||||||
log.debug("startup> conn watchdogs created")
|
log.debug("startup> conn watchdogs created")
|
||||||
|
|
||||||
-- create network interface then setup comms
|
-- create network interface then setup comms
|
||||||
local nic = network.nic(modem)
|
smem_sys.nic = network.nic(smem_dev.modem)
|
||||||
local pocket_comms = pocket.comms(POCKET_VERSION, nic, conn_wd.sv, conn_wd.api)
|
smem_sys.pocket_comms = pocket.comms(POCKET_VERSION, smem_sys.nic, smem_sys.sv_wd, smem_sys.api_wd, smem_sys.nav)
|
||||||
log.debug("startup> comms init")
|
log.debug("startup> comms init")
|
||||||
|
|
||||||
-- base loop clock (2Hz, 10 ticks)
|
|
||||||
local MAIN_CLOCK = 0.5
|
|
||||||
local loop_clock = util.new_clock(MAIN_CLOCK)
|
|
||||||
|
|
||||||
-- init I/O control
|
-- init I/O control
|
||||||
iocontrol.init_core(pocket_comms)
|
iocontrol.init_core(smem_sys.pocket_comms, smem_sys.nav, config)
|
||||||
|
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
-- start the UI
|
-- start the UI
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
|
|
||||||
local ui_ok, message = renderer.try_start_ui()
|
local ui_message
|
||||||
if not ui_ok then
|
pkt_state.ui_ok, ui_message = renderer.try_start_ui()
|
||||||
println(util.c("UI error: ", message))
|
if not pkt_state.ui_ok then
|
||||||
log.error(util.c("startup> GUI render failed with error ", message))
|
println(util.c("UI error: ", ui_message))
|
||||||
else
|
log.error(util.c("startup> GUI render failed with error ", ui_message))
|
||||||
-- start clock
|
|
||||||
loop_clock.start()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
-- main event loop
|
-- start system
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
|
|
||||||
if ui_ok then
|
if pkt_state.ui_ok then
|
||||||
-- start connection watchdogs
|
-- init threads
|
||||||
conn_wd.sv.feed()
|
local main_thread = threads.thread__main(__shared_memory)
|
||||||
conn_wd.api.feed()
|
local render_thread = threads.thread__render(__shared_memory)
|
||||||
log.debug("startup> conn watchdog started")
|
|
||||||
|
|
||||||
local io_db = iocontrol.get_db()
|
log.info("startup> completed")
|
||||||
local nav = io_db.nav
|
|
||||||
|
|
||||||
-- main event loop
|
-- run threads
|
||||||
while true do
|
parallel.waitForAll(main_thread.p_exec, render_thread.p_exec)
|
||||||
local event, param1, param2, param3, param4, param5 = util.pull_event()
|
|
||||||
|
|
||||||
-- handle event
|
|
||||||
if event == "timer" then
|
|
||||||
if loop_clock.is_clock(param1) then
|
|
||||||
-- main loop tick
|
|
||||||
|
|
||||||
-- relink if necessary
|
|
||||||
pocket_comms.link_update()
|
|
||||||
|
|
||||||
-- update any tasks for the active page
|
|
||||||
if (type(nav.tasks[nav.page]) == "table") then
|
|
||||||
for i = 1, #nav.tasks[nav.page] do
|
|
||||||
nav.tasks[nav.page][i]()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
loop_clock.start()
|
|
||||||
elseif conn_wd.sv.is_timer(param1) then
|
|
||||||
-- supervisor watchdog timeout
|
|
||||||
log.info("supervisor server timeout")
|
|
||||||
pocket_comms.close_sv()
|
|
||||||
elseif conn_wd.api.is_timer(param1) then
|
|
||||||
-- coordinator watchdog timeout
|
|
||||||
log.info("coordinator api server timeout")
|
|
||||||
pocket_comms.close_api()
|
|
||||||
else
|
|
||||||
-- a non-clock/main watchdog timer event
|
|
||||||
-- notify timer callback dispatcher
|
|
||||||
tcd.handle(param1)
|
|
||||||
end
|
|
||||||
elseif event == "modem_message" then
|
|
||||||
-- got a packet
|
|
||||||
local packet = pocket_comms.parse_packet(param1, param2, param3, param4, param5)
|
|
||||||
pocket_comms.handle_packet(packet)
|
|
||||||
elseif event == "mouse_click" or event == "mouse_up" or event == "mouse_drag" or event == "mouse_scroll" or
|
|
||||||
event == "double_click" then
|
|
||||||
-- handle a monitor touch event
|
|
||||||
renderer.handle_mouse(core.events.new_mouse_event(event, param1, param2, param3))
|
|
||||||
end
|
|
||||||
|
|
||||||
-- check for termination request
|
|
||||||
if event == "terminate" or ppm.should_terminate() then
|
|
||||||
log.info("terminate requested, closing server connections...")
|
|
||||||
pocket_comms.close()
|
|
||||||
log.info("connections closed")
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
renderer.close_ui()
|
renderer.close_ui()
|
||||||
|
|
||||||
|
if not pkt_state.ui_ok then
|
||||||
|
println(util.c("UI crashed with error: ", pkt_state.ui_error))
|
||||||
|
end
|
||||||
|
else
|
||||||
|
println_ts("UI creation failed")
|
||||||
end
|
end
|
||||||
|
|
||||||
println_ts("exited")
|
println_ts("exited")
|
||||||
|
219
pocket/threads.lua
Normal file
219
pocket/threads.lua
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
local log = require("scada-common.log")
|
||||||
|
local mqueue = require("scada-common.mqueue")
|
||||||
|
local ppm = require("scada-common.ppm")
|
||||||
|
local tcd = require("scada-common.tcd")
|
||||||
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
|
local pocket = require("pocket.pocket")
|
||||||
|
local renderer = require("pocket.renderer")
|
||||||
|
|
||||||
|
local core = require("graphics.core")
|
||||||
|
|
||||||
|
local threads = {}
|
||||||
|
|
||||||
|
local MAIN_CLOCK = 0.5 -- (2Hz, 10 ticks)
|
||||||
|
local RENDER_SLEEP = 100 -- (100ms, 2 ticks)
|
||||||
|
|
||||||
|
local MQ__RENDER_CMD = pocket.MQ__RENDER_CMD
|
||||||
|
local MQ__RENDER_DATA = pocket.MQ__RENDER_DATA
|
||||||
|
|
||||||
|
-- main thread
|
||||||
|
---@nodiscard
|
||||||
|
---@param smem pkt_shared_memory
|
||||||
|
function threads.thread__main(smem)
|
||||||
|
---@class parallel_thread
|
||||||
|
local public = {}
|
||||||
|
|
||||||
|
-- execute thread
|
||||||
|
function public.exec()
|
||||||
|
log.debug("main thread start")
|
||||||
|
|
||||||
|
local loop_clock = util.new_clock(MAIN_CLOCK)
|
||||||
|
|
||||||
|
-- start clock
|
||||||
|
loop_clock.start()
|
||||||
|
|
||||||
|
-- load in from shared memory
|
||||||
|
local pkt_state = smem.pkt_state
|
||||||
|
local pocket_comms = smem.pkt_sys.pocket_comms
|
||||||
|
local sv_wd = smem.pkt_sys.sv_wd
|
||||||
|
local api_wd = smem.pkt_sys.api_wd
|
||||||
|
local nav = smem.pkt_sys.nav
|
||||||
|
|
||||||
|
-- start connection watchdogs
|
||||||
|
sv_wd.feed()
|
||||||
|
api_wd.feed()
|
||||||
|
log.debug("startup> conn watchdogs started")
|
||||||
|
|
||||||
|
-- event loop
|
||||||
|
while true do
|
||||||
|
local event, param1, param2, param3, param4, param5 = util.pull_event()
|
||||||
|
|
||||||
|
-- handle event
|
||||||
|
if event == "timer" then
|
||||||
|
if loop_clock.is_clock(param1) then
|
||||||
|
-- main loop tick
|
||||||
|
|
||||||
|
-- relink if necessary
|
||||||
|
pocket_comms.link_update()
|
||||||
|
|
||||||
|
-- update any tasks for the active page
|
||||||
|
local page_tasks = nav.get_current_page().tasks
|
||||||
|
for i = 1, #page_tasks do page_tasks[i]() end
|
||||||
|
|
||||||
|
loop_clock.start()
|
||||||
|
elseif sv_wd.is_timer(param1) then
|
||||||
|
-- supervisor watchdog timeout
|
||||||
|
log.info("supervisor server timeout")
|
||||||
|
pocket_comms.close_sv()
|
||||||
|
elseif api_wd.is_timer(param1) then
|
||||||
|
-- coordinator watchdog timeout
|
||||||
|
log.info("coordinator api server timeout")
|
||||||
|
pocket_comms.close_api()
|
||||||
|
else
|
||||||
|
-- a non-clock/main watchdog timer event
|
||||||
|
-- notify timer callback dispatcher
|
||||||
|
tcd.handle(param1)
|
||||||
|
end
|
||||||
|
elseif event == "modem_message" then
|
||||||
|
-- got a packet
|
||||||
|
local packet = pocket_comms.parse_packet(param1, param2, param3, param4, param5)
|
||||||
|
pocket_comms.handle_packet(packet)
|
||||||
|
elseif event == "mouse_click" or event == "mouse_up" or event == "mouse_drag" or event == "mouse_scroll" or
|
||||||
|
event == "double_click" then
|
||||||
|
-- handle a mouse event
|
||||||
|
renderer.handle_mouse(core.events.new_mouse_event(event, param1, param2, param3))
|
||||||
|
elseif event == "char" or event == "key" or event == "key_up" then
|
||||||
|
-- handle a keyboard event
|
||||||
|
renderer.handle_key(core.events.new_key_event(event, param1, param2))
|
||||||
|
elseif event == "paste" then
|
||||||
|
-- handle a paste event
|
||||||
|
renderer.handle_paste(param1)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- check for termination request or UI crash
|
||||||
|
if event == "terminate" or ppm.should_terminate() then
|
||||||
|
log.info("terminate requested, main thread exiting")
|
||||||
|
pkt_state.shutdown = true
|
||||||
|
elseif not pkt_state.ui_ok then
|
||||||
|
pkt_state.shutdown = true
|
||||||
|
log.info("terminating due to fatal UI error")
|
||||||
|
end
|
||||||
|
|
||||||
|
if pkt_state.shutdown then
|
||||||
|
log.info("closing server connections...")
|
||||||
|
pocket_comms.close()
|
||||||
|
log.info("connections closed")
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- execute the thread in a protected mode, retrying it on return if not shutting down
|
||||||
|
function public.p_exec()
|
||||||
|
local pkt_state = smem.pkt_state
|
||||||
|
|
||||||
|
while not pkt_state.shutdown do
|
||||||
|
local status, result = pcall(public.exec)
|
||||||
|
if status == false then
|
||||||
|
log.fatal(util.strval(result))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- if status is true, then we are probably exiting, so this won't matter
|
||||||
|
-- this thread cannot be slept because it will miss events (namely "terminate")
|
||||||
|
if not pkt_state.shutdown then
|
||||||
|
log.info("main thread restarting now...")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return public
|
||||||
|
end
|
||||||
|
|
||||||
|
-- pocket renderer thread, tasked with long duration draws
|
||||||
|
---@nodiscard
|
||||||
|
---@param smem pkt_shared_memory
|
||||||
|
function threads.thread__render(smem)
|
||||||
|
---@class parallel_thread
|
||||||
|
local public = {}
|
||||||
|
|
||||||
|
-- execute thread
|
||||||
|
function public.exec()
|
||||||
|
log.debug("render thread start")
|
||||||
|
|
||||||
|
-- load in from shared memory
|
||||||
|
local pkt_state = smem.pkt_state
|
||||||
|
local nav = smem.pkt_sys.nav
|
||||||
|
local render_queue = smem.q.mq_render
|
||||||
|
|
||||||
|
local last_update = util.time()
|
||||||
|
|
||||||
|
-- thread loop
|
||||||
|
while true do
|
||||||
|
-- check for messages in the message queue
|
||||||
|
while render_queue.ready() and not pkt_state.shutdown do
|
||||||
|
local msg = render_queue.pop()
|
||||||
|
|
||||||
|
if msg ~= nil then
|
||||||
|
if msg.qtype == mqueue.TYPE.COMMAND then
|
||||||
|
-- received a command
|
||||||
|
if msg.message == MQ__RENDER_CMD.UNLOAD_SV_APPS then
|
||||||
|
elseif msg.message == MQ__RENDER_CMD.UNLOAD_API_APPS then
|
||||||
|
end
|
||||||
|
elseif msg.qtype == mqueue.TYPE.DATA then
|
||||||
|
-- received data
|
||||||
|
local cmd = msg.message ---@type queue_data
|
||||||
|
|
||||||
|
if cmd.key == MQ__RENDER_DATA.LOAD_APP then
|
||||||
|
log.debug("RENDER: load app " .. cmd.val)
|
||||||
|
|
||||||
|
local draw_start = util.time_ms()
|
||||||
|
|
||||||
|
pkt_state.ui_ok, pkt_state.ui_error = pcall(function () nav.load_app(cmd.val) end)
|
||||||
|
if not pkt_state.ui_ok then
|
||||||
|
log.fatal(util.c("RENDER: app load failed with error ", pkt_state.ui_error))
|
||||||
|
else
|
||||||
|
log.debug("RENDER: app loaded in " .. (util.time_ms() - draw_start) .. "ms")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif msg.qtype == mqueue.TYPE.PACKET then
|
||||||
|
-- received a packet
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- quick yield
|
||||||
|
util.nop()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- check for termination request
|
||||||
|
if pkt_state.shutdown then
|
||||||
|
log.info("render thread exiting")
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
-- delay before next check
|
||||||
|
last_update = util.adaptive_delay(RENDER_SLEEP, last_update)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- execute the thread in a protected mode, retrying it on return if not shutting down
|
||||||
|
function public.p_exec()
|
||||||
|
local pkt_state = smem.pkt_state
|
||||||
|
|
||||||
|
while not pkt_state.shutdown do
|
||||||
|
local status, result = pcall(public.exec)
|
||||||
|
if status == false then
|
||||||
|
log.fatal(util.strval(result))
|
||||||
|
end
|
||||||
|
|
||||||
|
if not pkt_state.shutdown then
|
||||||
|
log.info("render thread restarting in 5 seconds...")
|
||||||
|
util.psleep(5)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return public
|
||||||
|
end
|
||||||
|
|
||||||
|
return threads
|
@ -1,58 +1,41 @@
|
|||||||
|
--
|
||||||
|
-- Diagnostic Apps
|
||||||
|
--
|
||||||
|
|
||||||
local iocontrol = require("pocket.iocontrol")
|
local iocontrol = require("pocket.iocontrol")
|
||||||
|
local pocket = require("pocket.pocket")
|
||||||
|
|
||||||
local core = require("graphics.core")
|
local core = require("graphics.core")
|
||||||
|
|
||||||
local Div = require("graphics.elements.div")
|
local Div = require("graphics.elements.div")
|
||||||
local MultiPane = require("graphics.elements.multipane")
|
|
||||||
local TextBox = require("graphics.elements.textbox")
|
local TextBox = require("graphics.elements.textbox")
|
||||||
|
|
||||||
local IndicatorLight = require("graphics.elements.indicators.light")
|
local IndicatorLight = require("graphics.elements.indicators.light")
|
||||||
|
|
||||||
local App = require("graphics.elements.controls.app")
|
|
||||||
local Checkbox = require("graphics.elements.controls.checkbox")
|
local Checkbox = require("graphics.elements.controls.checkbox")
|
||||||
local PushButton = require("graphics.elements.controls.push_button")
|
local PushButton = require("graphics.elements.controls.push_button")
|
||||||
local SwitchButton = require("graphics.elements.controls.switch_button")
|
local SwitchButton = require("graphics.elements.controls.switch_button")
|
||||||
|
|
||||||
|
local ALIGN = core.ALIGN
|
||||||
local cpair = core.cpair
|
local cpair = core.cpair
|
||||||
|
|
||||||
local NAV_PAGE = iocontrol.NAV_PAGE
|
local APP_ID = pocket.APP_ID
|
||||||
|
|
||||||
local ALIGN = core.ALIGN
|
-- create diagnostic app pages
|
||||||
|
|
||||||
-- new diagnostics page view
|
|
||||||
---@param root graphics_element parent
|
---@param root graphics_element parent
|
||||||
local function new_view(root)
|
local function create_pages(root)
|
||||||
local db = iocontrol.get_db()
|
local db = iocontrol.get_db()
|
||||||
|
|
||||||
local main = Div{parent=root,x=1,y=1}
|
|
||||||
|
|
||||||
local diag_home = Div{parent=main,x=1,y=1}
|
|
||||||
|
|
||||||
TextBox{parent=diag_home,text="Diagnostic Apps",x=1,y=2,height=1,alignment=ALIGN.CENTER}
|
|
||||||
|
|
||||||
local alarm_test = Div{parent=main,x=1,y=1}
|
|
||||||
|
|
||||||
local panes = { diag_home, alarm_test }
|
|
||||||
|
|
||||||
local page_pane = MultiPane{parent=main,x=1,y=1,panes=panes}
|
|
||||||
|
|
||||||
local function navigate_diag()
|
|
||||||
page_pane.set_value(1)
|
|
||||||
db.nav.page = NAV_PAGE.DIAG
|
|
||||||
db.nav.sub_pages[NAV_PAGE.DIAG] = NAV_PAGE.DIAG
|
|
||||||
end
|
|
||||||
|
|
||||||
local function navigate_alarm()
|
|
||||||
page_pane.set_value(2)
|
|
||||||
db.nav.page = NAV_PAGE.D_ALARMS
|
|
||||||
db.nav.sub_pages[NAV_PAGE.DIAG] = NAV_PAGE.D_ALARMS
|
|
||||||
end
|
|
||||||
|
|
||||||
------------------------
|
------------------------
|
||||||
-- Alarm Testing Page --
|
-- Alarm Testing Page --
|
||||||
------------------------
|
------------------------
|
||||||
|
|
||||||
db.nav.register_task(NAV_PAGE.D_ALARMS, db.diag.tone_test.get_tone_states)
|
local alarm_test = Div{parent=root,x=1,y=1}
|
||||||
|
|
||||||
|
local alarm_app = db.nav.register_app(APP_ID.ALARMS, alarm_test, nil, true)
|
||||||
|
|
||||||
|
local page = alarm_app.new_page(nil, function () end)
|
||||||
|
page.tasks = { db.diag.tone_test.get_tone_states }
|
||||||
|
|
||||||
local ttest = db.diag.tone_test
|
local ttest = db.diag.tone_test
|
||||||
|
|
||||||
@ -63,15 +46,13 @@ local function new_view(root)
|
|||||||
|
|
||||||
local audio = Div{parent=alarm_test,x=1,y=1}
|
local audio = Div{parent=alarm_test,x=1,y=1}
|
||||||
|
|
||||||
TextBox{parent=audio,y=1,text="Alarm Sounder Tests",height=1,alignment=ALIGN.CENTER}
|
TextBox{parent=audio,y=1,text="Alarm Sounder Tests",alignment=ALIGN.CENTER}
|
||||||
|
|
||||||
ttest.ready_warn = TextBox{parent=audio,y=2,text="",height=1,alignment=ALIGN.CENTER,fg_bg=cpair(colors.yellow,colors.black)}
|
ttest.ready_warn = TextBox{parent=audio,y=2,text="",alignment=ALIGN.CENTER,fg_bg=cpair(colors.yellow,colors.black)}
|
||||||
|
|
||||||
PushButton{parent=audio,x=13,y=18,text="\x11 BACK",min_width=8,fg_bg=cpair(colors.black,colors.lightGray),active_fg_bg=c_wht_gray,callback=navigate_diag}
|
|
||||||
|
|
||||||
local tones = Div{parent=audio,x=2,y=3,height=10,width=8,fg_bg=cpair(colors.black,colors.yellow)}
|
local tones = Div{parent=audio,x=2,y=3,height=10,width=8,fg_bg=cpair(colors.black,colors.yellow)}
|
||||||
|
|
||||||
TextBox{parent=tones,text="Tones",height=1,alignment=ALIGN.CENTER,fg_bg=audio.get_fg_bg()}
|
TextBox{parent=tones,text="Tones",alignment=ALIGN.CENTER,fg_bg=audio.get_fg_bg()}
|
||||||
|
|
||||||
local test_btns = {}
|
local test_btns = {}
|
||||||
test_btns[1] = SwitchButton{parent=tones,text="TEST 1",min_width=8,active_fg_bg=c_wht_gray,callback=ttest.test_1}
|
test_btns[1] = SwitchButton{parent=tones,text="TEST 1",min_width=8,active_fg_bg=c_wht_gray,callback=ttest.test_1}
|
||||||
@ -94,7 +75,7 @@ local function new_view(root)
|
|||||||
|
|
||||||
local alarms = Div{parent=audio,x=11,y=3,height=15,fg_bg=cpair(colors.lightGray,colors.black)}
|
local alarms = Div{parent=audio,x=11,y=3,height=15,fg_bg=cpair(colors.lightGray,colors.black)}
|
||||||
|
|
||||||
TextBox{parent=alarms,text="Alarms (\x13)",height=1,alignment=ALIGN.CENTER,fg_bg=audio.get_fg_bg()}
|
TextBox{parent=alarms,text="Alarms (\x13)",alignment=ALIGN.CENTER,fg_bg=audio.get_fg_bg()}
|
||||||
|
|
||||||
local alarm_btns = {}
|
local alarm_btns = {}
|
||||||
alarm_btns[1] = Checkbox{parent=alarms,label="BREACH",min_width=15,box_fg_bg=c_red_gray,callback=ttest.test_breach}
|
alarm_btns[1] = Checkbox{parent=alarms,label="BREACH",min_width=15,box_fg_bg=c_red_gray,callback=ttest.test_breach}
|
||||||
@ -121,7 +102,7 @@ local function new_view(root)
|
|||||||
|
|
||||||
local states = Div{parent=audio,x=2,y=14,height=5,width=8}
|
local states = Div{parent=audio,x=2,y=14,height=5,width=8}
|
||||||
|
|
||||||
TextBox{parent=states,text="States",height=1,alignment=ALIGN.CENTER}
|
TextBox{parent=states,text="States",alignment=ALIGN.CENTER}
|
||||||
local t_1 = IndicatorLight{parent=states,label="1",colors=c_blue_gray}
|
local t_1 = IndicatorLight{parent=states,label="1",colors=c_blue_gray}
|
||||||
local t_2 = IndicatorLight{parent=states,label="2",colors=c_blue_gray}
|
local t_2 = IndicatorLight{parent=states,label="2",colors=c_blue_gray}
|
||||||
local t_3 = IndicatorLight{parent=states,label="3",colors=c_blue_gray}
|
local t_3 = IndicatorLight{parent=states,label="3",colors=c_blue_gray}
|
||||||
@ -132,16 +113,6 @@ local function new_view(root)
|
|||||||
local t_8 = IndicatorLight{parent=states,x=6,label="8",colors=c_blue_gray}
|
local t_8 = IndicatorLight{parent=states,x=6,label="8",colors=c_blue_gray}
|
||||||
|
|
||||||
ttest.tone_indicators = { t_1, t_2, t_3, t_4, t_5, t_6, t_7, t_8 }
|
ttest.tone_indicators = { t_1, t_2, t_3, t_4, t_5, t_6, t_7, t_8 }
|
||||||
|
|
||||||
--------------
|
|
||||||
-- App List --
|
|
||||||
--------------
|
|
||||||
|
|
||||||
App{parent=diag_home,x=3,y=4,text="\x0f",title="Alarm",callback=navigate_alarm,app_fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray)}
|
|
||||||
App{parent=diag_home,x=10,y=4,text="\x1e",title="LoopT",callback=function()end,app_fg_bg=cpair(colors.black,colors.cyan)}
|
|
||||||
App{parent=diag_home,x=17,y=4,text="@",title="Comps",callback=function()end,app_fg_bg=cpair(colors.black,colors.orange)}
|
|
||||||
|
|
||||||
return main
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return new_view
|
return create_pages
|
29
pocket/ui/apps/dummy_app.lua
Normal file
29
pocket/ui/apps/dummy_app.lua
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
--
|
||||||
|
-- Placeholder App
|
||||||
|
--
|
||||||
|
|
||||||
|
local iocontrol = require("pocket.iocontrol")
|
||||||
|
local pocket = require("pocket.pocket")
|
||||||
|
|
||||||
|
local core = require("graphics.core")
|
||||||
|
|
||||||
|
local Div = require("graphics.elements.div")
|
||||||
|
local TextBox = require("graphics.elements.textbox")
|
||||||
|
|
||||||
|
local APP_ID = pocket.APP_ID
|
||||||
|
|
||||||
|
-- create placeholder app page
|
||||||
|
---@param root graphics_element parent
|
||||||
|
local function create_pages(root)
|
||||||
|
local db = iocontrol.get_db()
|
||||||
|
|
||||||
|
local main = Div{parent=root,x=1,y=1}
|
||||||
|
|
||||||
|
db.nav.register_app(APP_ID.DUMMY, main).new_page(nil, function () end)
|
||||||
|
|
||||||
|
TextBox{parent=main,text="This app is not implemented yet.",x=1,y=2,alignment=core.ALIGN.CENTER}
|
||||||
|
|
||||||
|
TextBox{parent=main,text=" pretend something cool is here \x03",x=1,y=10,alignment=core.ALIGN.CENTER,fg_bg=core.cpair(colors.gray,colors.black)}
|
||||||
|
end
|
||||||
|
|
||||||
|
return create_pages
|
248
pocket/ui/apps/guide.lua
Normal file
248
pocket/ui/apps/guide.lua
Normal file
@ -0,0 +1,248 @@
|
|||||||
|
--
|
||||||
|
-- System Guide
|
||||||
|
--
|
||||||
|
|
||||||
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
|
local iocontrol = require("pocket.iocontrol")
|
||||||
|
local pocket = require("pocket.pocket")
|
||||||
|
|
||||||
|
local docs = require("pocket.ui.docs")
|
||||||
|
-- local style = require("pocket.ui.style")
|
||||||
|
|
||||||
|
local guide_section = require("pocket.ui.pages.guide_section")
|
||||||
|
|
||||||
|
local core = require("graphics.core")
|
||||||
|
|
||||||
|
local Div = require("graphics.elements.div")
|
||||||
|
local ListBox = require("graphics.elements.listbox")
|
||||||
|
local MultiPane = require("graphics.elements.multipane")
|
||||||
|
local TextBox = require("graphics.elements.textbox")
|
||||||
|
|
||||||
|
local WaitingAnim = require("graphics.elements.animations.waiting")
|
||||||
|
|
||||||
|
local PushButton = require("graphics.elements.controls.push_button")
|
||||||
|
|
||||||
|
local TextField = require("graphics.elements.form.text_field")
|
||||||
|
|
||||||
|
local ALIGN = core.ALIGN
|
||||||
|
local cpair = core.cpair
|
||||||
|
|
||||||
|
local APP_ID = pocket.APP_ID
|
||||||
|
|
||||||
|
-- local label = style.label
|
||||||
|
-- local lu_col = style.label_unit_pair
|
||||||
|
-- local text_fg = style.text_fg
|
||||||
|
|
||||||
|
-- new system guide view
|
||||||
|
---@param root graphics_element 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.GUIDE, frame)
|
||||||
|
|
||||||
|
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.cyan,colors._INHERIT)}
|
||||||
|
|
||||||
|
local load_pane = MultiPane{parent=main,x=1,y=1,panes={load_div,main}}
|
||||||
|
|
||||||
|
local btn_fg_bg = cpair(colors.cyan, colors.black)
|
||||||
|
local btn_active = cpair(colors.white, colors.black)
|
||||||
|
local btn_disable = cpair(colors.gray, colors.black)
|
||||||
|
|
||||||
|
app.set_sidebar({{ label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = function () db.nav.open_app(APP_ID.ROOT) end }})
|
||||||
|
|
||||||
|
local page_div = nil ---@type nil|graphics_element
|
||||||
|
|
||||||
|
-- load the app (create the elements)
|
||||||
|
local function load()
|
||||||
|
local list = {
|
||||||
|
{ label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = function () db.nav.open_app(APP_ID.ROOT) end },
|
||||||
|
{ label = " \x14 ", color = core.cpair(colors.black, colors.cyan), callback = function () app.switcher(1) end },
|
||||||
|
{ label = "__?", color = core.cpair(colors.black, colors.lightGray), callback = function () app.switcher(2) end }
|
||||||
|
}
|
||||||
|
|
||||||
|
app.set_sidebar(list)
|
||||||
|
|
||||||
|
page_div = Div{parent=main,y=2}
|
||||||
|
local p_width = page_div.get_width() - 2
|
||||||
|
|
||||||
|
local main_page = app.new_page(nil, 1)
|
||||||
|
local search_page = app.new_page(main_page, 2)
|
||||||
|
local use_page = app.new_page(main_page, 3)
|
||||||
|
local uis_page = app.new_page(main_page, 4)
|
||||||
|
local fps_page = app.new_page(main_page, 5)
|
||||||
|
local gls_page = app.new_page(main_page, 6)
|
||||||
|
|
||||||
|
local home = Div{parent=page_div,x=2}
|
||||||
|
local search = Div{parent=page_div,x=2}
|
||||||
|
local use = Div{parent=page_div,x=2,width=p_width}
|
||||||
|
local uis = Div{parent=page_div,x=2,width=p_width}
|
||||||
|
local fps = Div{parent=page_div,x=2,width=p_width}
|
||||||
|
local gls = Div{parent=page_div,x=2,width=p_width}
|
||||||
|
local panes = { home, search, use, uis, fps, gls }
|
||||||
|
|
||||||
|
local doc_map = {}
|
||||||
|
local search_db = {}
|
||||||
|
|
||||||
|
---@class _guide_section_constructor_data
|
||||||
|
local sect_construct_data = { app, page_div, panes, doc_map, search_db, btn_fg_bg, btn_active }
|
||||||
|
|
||||||
|
TextBox{parent=home,y=1,text="cc-mek-scada Guide",alignment=ALIGN.CENTER}
|
||||||
|
|
||||||
|
PushButton{parent=home,y=3,text="Search >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=search_page.nav_to}
|
||||||
|
PushButton{parent=home,y=5,text="System Usage >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=use_page.nav_to}
|
||||||
|
PushButton{parent=home,text="Operator UIs >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=uis_page.nav_to}
|
||||||
|
PushButton{parent=home,text="Front Panels >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=fps_page.nav_to}
|
||||||
|
PushButton{parent=home,text="Glossary >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=gls_page.nav_to}
|
||||||
|
|
||||||
|
TextBox{parent=search,y=1,text="Search",alignment=ALIGN.CENTER}
|
||||||
|
|
||||||
|
local query_field = TextField{parent=search,x=1,y=3,width=18,fg_bg=cpair(colors.white,colors.gray)}
|
||||||
|
|
||||||
|
local func_ref = {}
|
||||||
|
|
||||||
|
PushButton{parent=search,x=20,y=3,text="GO",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=function()func_ref.run_search()end}
|
||||||
|
|
||||||
|
local search_results = ListBox{parent=search,x=1,y=5,scroll_height=200,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)}
|
||||||
|
|
||||||
|
function func_ref.run_search()
|
||||||
|
local query = string.lower(query_field.get_value())
|
||||||
|
local s_results = { {}, {}, {} }
|
||||||
|
|
||||||
|
search_results.remove_all()
|
||||||
|
|
||||||
|
if string.len(query) < 3 then
|
||||||
|
TextBox{parent=search_results,text="Search requires at least 3 characters."}
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, entry in ipairs(search_db) do
|
||||||
|
local s_start, _ = string.find(entry[1], query, 1, true)
|
||||||
|
|
||||||
|
if s_start == nil then
|
||||||
|
elseif s_start == 1 then
|
||||||
|
-- best match, start of key
|
||||||
|
table.insert(s_results[1], entry)
|
||||||
|
elseif string.sub(query, s_start - 1, s_start) == " " then
|
||||||
|
-- start of word, good match
|
||||||
|
table.insert(s_results[2], entry)
|
||||||
|
else
|
||||||
|
-- basic match in content
|
||||||
|
table.insert(s_results[3], entry)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local empty = true
|
||||||
|
|
||||||
|
for tier = 1, 3 do
|
||||||
|
for idx = 1, #s_results[tier] do
|
||||||
|
local entry = s_results[tier][idx]
|
||||||
|
TextBox{parent=search_results,text=entry[3].." >",fg_bg=cpair(colors.gray,colors.black)}
|
||||||
|
PushButton{parent=search_results,text=entry[2],fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=entry[4]}
|
||||||
|
|
||||||
|
empty = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if empty then
|
||||||
|
TextBox{parent=search_results,text="No results found."}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
TextBox{parent=search_results,text="Click 'GO' to search..."}
|
||||||
|
|
||||||
|
util.nop()
|
||||||
|
|
||||||
|
TextBox{parent=use,y=1,text="System Usage",alignment=ALIGN.CENTER}
|
||||||
|
PushButton{parent=use,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=main_page.nav_to}
|
||||||
|
|
||||||
|
PushButton{parent=use,y=3,text="Configuring Devices >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,dis_fg_bg=btn_disable,callback=function()end}.disable()
|
||||||
|
PushButton{parent=use,text="Connecting Devices >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,dis_fg_bg=btn_disable,callback=function()end}.disable()
|
||||||
|
PushButton{parent=use,text="Manual Control >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,dis_fg_bg=btn_disable,callback=function()end}.disable()
|
||||||
|
PushButton{parent=use,text="Automatic Control >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,dis_fg_bg=btn_disable,callback=function()end}.disable()
|
||||||
|
PushButton{parent=use,text="Waste Control >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,dis_fg_bg=btn_disable,callback=function()end}.disable()
|
||||||
|
|
||||||
|
TextBox{parent=uis,y=1,text="Operator UIs",alignment=ALIGN.CENTER}
|
||||||
|
PushButton{parent=uis,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=main_page.nav_to}
|
||||||
|
|
||||||
|
local annunc_page = app.new_page(uis_page, #panes + 1)
|
||||||
|
local annunc_div = Div{parent=page_div,x=2}
|
||||||
|
table.insert(panes, annunc_div)
|
||||||
|
|
||||||
|
local alarms_page = guide_section(sect_construct_data, uis_page, "Alarms", docs.alarms, 100)
|
||||||
|
|
||||||
|
PushButton{parent=uis,y=3,text="Alarms >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=alarms_page.nav_to}
|
||||||
|
PushButton{parent=uis,text="Annunciators >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=annunc_page.nav_to}
|
||||||
|
PushButton{parent=uis,text="Pocket UI >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,dis_fg_bg=btn_disable,callback=function()end}.disable()
|
||||||
|
PushButton{parent=uis,text="Coordinator UI >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,dis_fg_bg=btn_disable,callback=function()end}.disable()
|
||||||
|
|
||||||
|
TextBox{parent=annunc_div,y=1,text="Annunciators",alignment=ALIGN.CENTER}
|
||||||
|
PushButton{parent=annunc_div,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=uis_page.nav_to}
|
||||||
|
|
||||||
|
local unit_gen_page = guide_section(sect_construct_data, annunc_page, "Unit General", docs.annunc.unit.main_section, 170)
|
||||||
|
local unit_rps_page = guide_section(sect_construct_data, annunc_page, "Unit RPS", docs.annunc.unit.rps_section, 100)
|
||||||
|
local unit_rcs_page = guide_section(sect_construct_data, annunc_page, "Unit RCS", docs.annunc.unit.rcs_section, 170)
|
||||||
|
local fac_annunc_page = guide_section(sect_construct_data, annunc_page, "Facility", docs.annunc.unit.fac_section, 100)
|
||||||
|
|
||||||
|
PushButton{parent=annunc_div,y=3,text="Unit General >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=unit_gen_page.nav_to}
|
||||||
|
PushButton{parent=annunc_div,text="Unit RPS >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=unit_rps_page.nav_to}
|
||||||
|
PushButton{parent=annunc_div,text="Unit RCS >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=unit_rcs_page.nav_to}
|
||||||
|
PushButton{parent=annunc_div,text="Facility General >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=fac_annunc_page.nav_to}
|
||||||
|
PushButton{parent=annunc_div,text="Waste & Valves >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,dis_fg_bg=btn_disable,callback=function()end}.disable()
|
||||||
|
|
||||||
|
TextBox{parent=fps,y=1,text="Front Panels",alignment=ALIGN.CENTER}
|
||||||
|
PushButton{parent=fps,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=main_page.nav_to}
|
||||||
|
|
||||||
|
PushButton{parent=fps,y=3,text="Common Items >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,dis_fg_bg=btn_disable,callback=function()end}.disable()
|
||||||
|
PushButton{parent=fps,text="Reactor PLC >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,dis_fg_bg=btn_disable,callback=function()end}.disable()
|
||||||
|
PushButton{parent=fps,text="RTU Gateway >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,dis_fg_bg=btn_disable,callback=function()end}.disable()
|
||||||
|
PushButton{parent=fps,text="Supervisor >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,dis_fg_bg=btn_disable,callback=function()end}.disable()
|
||||||
|
PushButton{parent=fps,text="Coordinator >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,dis_fg_bg=btn_disable,callback=function()end}.disable()
|
||||||
|
|
||||||
|
TextBox{parent=gls,y=1,text="Glossary",alignment=ALIGN.CENTER}
|
||||||
|
PushButton{parent=gls,x=3,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=main_page.nav_to}
|
||||||
|
|
||||||
|
local gls_abbv_page = guide_section(sect_construct_data, gls_page, "Abbreviations", docs.glossary.abbvs, 130)
|
||||||
|
local gls_term_page = guide_section(sect_construct_data, gls_page, "Terminology", docs.glossary.terms, 100)
|
||||||
|
|
||||||
|
PushButton{parent=gls,y=3,text="Abbreviations >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=gls_abbv_page.nav_to}
|
||||||
|
PushButton{parent=gls,text="Terminology >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=gls_term_page.nav_to}
|
||||||
|
|
||||||
|
-- setup multipane
|
||||||
|
local u_pane = MultiPane{parent=page_div,x=1,y=1,panes=panes}
|
||||||
|
app.set_root_pane(u_pane)
|
||||||
|
|
||||||
|
-- link help resources
|
||||||
|
db.nav.link_help(doc_map)
|
||||||
|
|
||||||
|
-- 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 = function () db.nav.open_app(APP_ID.ROOT) end } })
|
||||||
|
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
|
49
pocket/ui/apps/loader.lua
Normal file
49
pocket/ui/apps/loader.lua
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
--
|
||||||
|
-- Loading Screen App
|
||||||
|
--
|
||||||
|
|
||||||
|
local iocontrol = require("pocket.iocontrol")
|
||||||
|
local pocket = require("pocket.pocket")
|
||||||
|
|
||||||
|
local conn_waiting = require("pocket.ui.components.conn_waiting")
|
||||||
|
|
||||||
|
local core = require("graphics.core")
|
||||||
|
|
||||||
|
local Div = require("graphics.elements.div")
|
||||||
|
local MultiPane = require("graphics.elements.multipane")
|
||||||
|
local TextBox = require("graphics.elements.textbox")
|
||||||
|
|
||||||
|
local APP_ID = pocket.APP_ID
|
||||||
|
|
||||||
|
local LINK_STATE = iocontrol.LINK_STATE
|
||||||
|
|
||||||
|
-- create the connecting to SV & API page
|
||||||
|
---@param root graphics_element parent
|
||||||
|
local function create_pages(root)
|
||||||
|
local db = iocontrol.get_db()
|
||||||
|
|
||||||
|
local main = Div{parent=root,x=1,y=1}
|
||||||
|
|
||||||
|
db.nav.register_app(APP_ID.LOADER, main).new_page(nil, function () end)
|
||||||
|
|
||||||
|
local conn_sv_wait = conn_waiting(main, 6, false)
|
||||||
|
local conn_api_wait = conn_waiting(main, 6, true)
|
||||||
|
local main_pane = Div{parent=main,x=1,y=2}
|
||||||
|
|
||||||
|
local root_pane = MultiPane{parent=main,x=1,y=1,panes={conn_sv_wait,conn_api_wait,main_pane}}
|
||||||
|
|
||||||
|
root_pane.register(db.ps, "link_state", function (state)
|
||||||
|
if state == LINK_STATE.UNLINKED or state == LINK_STATE.API_LINK_ONLY then
|
||||||
|
root_pane.set_value(1)
|
||||||
|
elseif state == LINK_STATE.SV_LINK_ONLY then
|
||||||
|
root_pane.set_value(2)
|
||||||
|
else
|
||||||
|
root_pane.set_value(3)
|
||||||
|
db.nav.on_loader_connected()
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
TextBox{parent=main_pane,text="Connected!",x=1,y=6,alignment=core.ALIGN.CENTER}
|
||||||
|
end
|
||||||
|
|
||||||
|
return create_pages
|
146
pocket/ui/apps/sys_apps.lua
Normal file
146
pocket/ui/apps/sys_apps.lua
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
--
|
||||||
|
-- System Apps
|
||||||
|
--
|
||||||
|
|
||||||
|
local comms = require("scada-common.comms")
|
||||||
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
|
local lockbox = require("lockbox")
|
||||||
|
|
||||||
|
local iocontrol = require("pocket.iocontrol")
|
||||||
|
local pocket = require("pocket.pocket")
|
||||||
|
|
||||||
|
local core = require("graphics.core")
|
||||||
|
|
||||||
|
local Div = require("graphics.elements.div")
|
||||||
|
local ListBox = require("graphics.elements.listbox")
|
||||||
|
local MultiPane = require("graphics.elements.multipane")
|
||||||
|
local TextBox = require("graphics.elements.textbox")
|
||||||
|
|
||||||
|
local PushButton = require("graphics.elements.controls.push_button")
|
||||||
|
|
||||||
|
local ALIGN = core.ALIGN
|
||||||
|
local cpair = core.cpair
|
||||||
|
|
||||||
|
local APP_ID = pocket.APP_ID
|
||||||
|
|
||||||
|
-- create system app pages
|
||||||
|
---@param root graphics_element parent
|
||||||
|
local function create_pages(root)
|
||||||
|
local db = iocontrol.get_db()
|
||||||
|
|
||||||
|
----------------
|
||||||
|
-- About Page --
|
||||||
|
----------------
|
||||||
|
|
||||||
|
local about_root = Div{parent=root,x=1,y=1}
|
||||||
|
|
||||||
|
local about_app = db.nav.register_app(APP_ID.ABOUT, about_root)
|
||||||
|
|
||||||
|
local about_page = about_app.new_page(nil, 1)
|
||||||
|
local nt_page = about_app.new_page(about_page, 2)
|
||||||
|
local fw_page = about_app.new_page(about_page, 3)
|
||||||
|
local hw_page = about_app.new_page(about_page, 4)
|
||||||
|
|
||||||
|
local about = Div{parent=about_root,x=1,y=2}
|
||||||
|
|
||||||
|
TextBox{parent=about,y=1,text="System Information",alignment=ALIGN.CENTER}
|
||||||
|
|
||||||
|
local btn_fg_bg = cpair(colors.lightBlue, colors.black)
|
||||||
|
local btn_active = cpair(colors.white, colors.black)
|
||||||
|
local label = cpair(colors.lightGray, colors.black)
|
||||||
|
|
||||||
|
PushButton{parent=about,x=2,y=3,text="Network >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=nt_page.nav_to}
|
||||||
|
PushButton{parent=about,x=2,y=4,text="Firmware >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=fw_page.nav_to}
|
||||||
|
PushButton{parent=about,x=2,y=5,text="Host Details >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=hw_page.nav_to}
|
||||||
|
|
||||||
|
--#region Network Details
|
||||||
|
|
||||||
|
local config = pocket.config
|
||||||
|
|
||||||
|
local nt_div = Div{parent=about_root,x=1,y=2}
|
||||||
|
TextBox{parent=nt_div,y=1,text="Network Details",alignment=ALIGN.CENTER}
|
||||||
|
|
||||||
|
PushButton{parent=nt_div,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=about_page.nav_to}
|
||||||
|
|
||||||
|
TextBox{parent=nt_div,x=2,y=3,text="Pocket Address",fg_bg=label}
|
||||||
|
---@diagnostic disable-next-line: undefined-field
|
||||||
|
TextBox{parent=nt_div,x=2,text=util.c(os.getComputerID(),":",config.PKT_Channel)}
|
||||||
|
|
||||||
|
nt_div.line_break()
|
||||||
|
TextBox{parent=nt_div,x=2,text="Supervisor Address",fg_bg=label}
|
||||||
|
local sv = TextBox{parent=nt_div,x=2,text=""}
|
||||||
|
|
||||||
|
nt_div.line_break()
|
||||||
|
TextBox{parent=nt_div,x=2,text="Coordinator Address",fg_bg=label}
|
||||||
|
local coord = TextBox{parent=nt_div,x=2,text=""}
|
||||||
|
|
||||||
|
sv.register(db.ps, "sv_addr", sv.set_value)
|
||||||
|
coord.register(db.ps, "api_addr", coord.set_value)
|
||||||
|
|
||||||
|
nt_div.line_break()
|
||||||
|
TextBox{parent=nt_div,x=2,text="Message Authentication",fg_bg=label}
|
||||||
|
local auth = util.trinary(type(config.AuthKey) == "string" and string.len(config.AuthKey) > 0, "HMAC-MD5", "None")
|
||||||
|
TextBox{parent=nt_div,x=2,text=auth}
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
|
--#region Firmware Versions
|
||||||
|
|
||||||
|
local fw_div = Div{parent=about_root,x=1,y=2}
|
||||||
|
TextBox{parent=fw_div,y=1,text="Firmware Versions",alignment=ALIGN.CENTER}
|
||||||
|
|
||||||
|
PushButton{parent=fw_div,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=about_page.nav_to}
|
||||||
|
|
||||||
|
local fw_list_box = ListBox{parent=fw_div,x=1,y=3,scroll_height=100,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)}
|
||||||
|
|
||||||
|
local fw_list = Div{parent=fw_list_box,x=1,y=2,height=18}
|
||||||
|
|
||||||
|
TextBox{parent=fw_list,x=2,text="Pocket Version",fg_bg=label}
|
||||||
|
TextBox{parent=fw_list,x=2,text=db.version}
|
||||||
|
|
||||||
|
fw_list.line_break()
|
||||||
|
TextBox{parent=fw_list,x=2,text="Comms Version",fg_bg=label}
|
||||||
|
TextBox{parent=fw_list,x=2,text=comms.version}
|
||||||
|
|
||||||
|
fw_list.line_break()
|
||||||
|
TextBox{parent=fw_list,x=2,text="API Version",fg_bg=label}
|
||||||
|
TextBox{parent=fw_list,x=2,text=comms.api_version}
|
||||||
|
|
||||||
|
fw_list.line_break()
|
||||||
|
TextBox{parent=fw_list,x=2,text="Common Lib Version",fg_bg=label}
|
||||||
|
TextBox{parent=fw_list,x=2,text=util.version}
|
||||||
|
|
||||||
|
fw_list.line_break()
|
||||||
|
TextBox{parent=fw_list,x=2,text="Graphics Version",fg_bg=label}
|
||||||
|
TextBox{parent=fw_list,x=2,text=core.version}
|
||||||
|
|
||||||
|
fw_list.line_break()
|
||||||
|
TextBox{parent=fw_list,x=2,text="Lockbox Version",fg_bg=label}
|
||||||
|
TextBox{parent=fw_list,x=2,text=lockbox.version}
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
|
--#region Host Versions
|
||||||
|
|
||||||
|
local hw_div = Div{parent=about_root,x=1,y=2}
|
||||||
|
TextBox{parent=hw_div,y=1,text="Host Versions",alignment=ALIGN.CENTER}
|
||||||
|
|
||||||
|
PushButton{parent=hw_div,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=about_page.nav_to}
|
||||||
|
|
||||||
|
hw_div.line_break()
|
||||||
|
TextBox{parent=hw_div,x=2,text="Lua Version",fg_bg=label}
|
||||||
|
TextBox{parent=hw_div,x=2,text=_VERSION}
|
||||||
|
|
||||||
|
hw_div.line_break()
|
||||||
|
TextBox{parent=hw_div,x=2,text="Environment",fg_bg=label}
|
||||||
|
TextBox{parent=hw_div,x=2,text=_HOST,height=6}
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
|
local root_pane = MultiPane{parent=about_root,x=1,y=1,panes={about,nt_div,fw_div,hw_div}}
|
||||||
|
|
||||||
|
about_app.set_root_pane(root_pane)
|
||||||
|
end
|
||||||
|
|
||||||
|
return create_pages
|
399
pocket/ui/apps/unit.lua
Normal file
399
pocket/ui/apps/unit.lua
Normal file
@ -0,0 +1,399 @@
|
|||||||
|
--
|
||||||
|
-- Unit Overview Page
|
||||||
|
--
|
||||||
|
|
||||||
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
|
local iocontrol = require("pocket.iocontrol")
|
||||||
|
local pocket = require("pocket.pocket")
|
||||||
|
|
||||||
|
local style = require("pocket.ui.style")
|
||||||
|
|
||||||
|
local boiler = require("pocket.ui.pages.unit_boiler")
|
||||||
|
local reactor = require("pocket.ui.pages.unit_reactor")
|
||||||
|
local turbine = require("pocket.ui.pages.unit_turbine")
|
||||||
|
|
||||||
|
local core = require("graphics.core")
|
||||||
|
|
||||||
|
local Div = require("graphics.elements.div")
|
||||||
|
local ListBox = require("graphics.elements.listbox")
|
||||||
|
local MultiPane = require("graphics.elements.multipane")
|
||||||
|
local TextBox = require("graphics.elements.textbox")
|
||||||
|
|
||||||
|
local WaitingAnim = require("graphics.elements.animations.waiting")
|
||||||
|
|
||||||
|
local PushButton = require("graphics.elements.controls.push_button")
|
||||||
|
|
||||||
|
local DataIndicator = require("graphics.elements.indicators.data")
|
||||||
|
local IconIndicator = require("graphics.elements.indicators.icon")
|
||||||
|
|
||||||
|
local ALIGN = core.ALIGN
|
||||||
|
local cpair = core.cpair
|
||||||
|
|
||||||
|
local APP_ID = pocket.APP_ID
|
||||||
|
|
||||||
|
-- local label = style.label
|
||||||
|
local lu_col = style.label_unit_pair
|
||||||
|
local text_fg = style.text_fg
|
||||||
|
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 emc_ind_s = {
|
||||||
|
{ color = cpair(colors.black, colors.gray), symbol = "-" },
|
||||||
|
{ color = cpair(colors.black, colors.white), symbol = "\x07" },
|
||||||
|
{ color = cpair(colors.black, colors.green), symbol = "+" }
|
||||||
|
}
|
||||||
|
|
||||||
|
-- new unit page view
|
||||||
|
---@param root graphics_element 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.UNITS, 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 = function () db.nav.open_app(APP_ID.ROOT) end } })
|
||||||
|
|
||||||
|
local btn_fg_bg = cpair(colors.yellow, colors.black)
|
||||||
|
local btn_active = cpair(colors.white, colors.black)
|
||||||
|
|
||||||
|
local nav_links = {}
|
||||||
|
local page_div = nil ---@type nil|graphics_element
|
||||||
|
|
||||||
|
-- set sidebar to display unit-specific fields based on a specified unit
|
||||||
|
local function set_sidebar(id)
|
||||||
|
local unit = db.units[id] ---@type pioctl_unit
|
||||||
|
|
||||||
|
local list = {
|
||||||
|
{ label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = function () db.nav.open_app(APP_ID.ROOT) end },
|
||||||
|
{ label = "U-" .. id, color = core.cpair(colors.black, colors.yellow), callback = function () app.switcher(id) end },
|
||||||
|
{ label = " \x13 ", color = core.cpair(colors.black, colors.red), callback = nav_links[id].alarm },
|
||||||
|
{ label = "RPS", tall = true, color = core.cpair(colors.black, colors.cyan), callback = nav_links[id].rps },
|
||||||
|
{ label = " R ", color = core.cpair(colors.black, colors.lightGray), callback = nav_links[id].reactor },
|
||||||
|
{ label = "RCS", tall = true, color = core.cpair(colors.black, colors.blue), callback = nav_links[id].rcs },
|
||||||
|
}
|
||||||
|
|
||||||
|
for i = 1, unit.num_boilers do
|
||||||
|
table.insert(list, { label = "B-" .. i, color = core.cpair(colors.black, colors.lightGray), callback = nav_links[id].boiler[i] })
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = 1, unit.num_turbines do
|
||||||
|
table.insert(list, { label = "T-" .. i, color = core.cpair(colors.black, colors.lightGray), callback = nav_links[id].turbine[i] })
|
||||||
|
end
|
||||||
|
|
||||||
|
app.set_sidebar(list)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- load the app (create the elements)
|
||||||
|
local function load()
|
||||||
|
page_div = Div{parent=main,y=2,width=main.get_width()}
|
||||||
|
|
||||||
|
local panes = {}
|
||||||
|
|
||||||
|
local active_unit = 1
|
||||||
|
|
||||||
|
-- create all page divs
|
||||||
|
for _ = 1, db.facility.num_units do
|
||||||
|
local div = Div{parent=page_div}
|
||||||
|
table.insert(panes, div)
|
||||||
|
table.insert(nav_links, {})
|
||||||
|
end
|
||||||
|
|
||||||
|
-- previous unit
|
||||||
|
local function prev(x)
|
||||||
|
active_unit = util.trinary(x == 1, db.facility.num_units, x - 1)
|
||||||
|
app.switcher(active_unit)
|
||||||
|
set_sidebar(active_unit)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- next unit
|
||||||
|
local function next(x)
|
||||||
|
active_unit = util.trinary(x == db.facility.num_units, 1, x + 1)
|
||||||
|
app.switcher(active_unit)
|
||||||
|
set_sidebar(active_unit)
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = 1, db.facility.num_units do
|
||||||
|
local u_pane = panes[i]
|
||||||
|
local u_div = Div{parent=u_pane,x=2,width=main.get_width()-2}
|
||||||
|
local unit = db.units[i] ---@type pioctl_unit
|
||||||
|
local u_ps = unit.unit_ps
|
||||||
|
|
||||||
|
-- refresh data callback, every 500ms it will re-send the query
|
||||||
|
local last_update = 0
|
||||||
|
local function update()
|
||||||
|
if util.time_ms() - last_update >= 500 then
|
||||||
|
db.api.get_unit(i)
|
||||||
|
last_update = util.time_ms()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--#region Main Unit Overview
|
||||||
|
|
||||||
|
local u_page = app.new_page(nil, i)
|
||||||
|
u_page.tasks = { update }
|
||||||
|
|
||||||
|
TextBox{parent=u_div,y=1,text="Reactor Unit #"..i,alignment=ALIGN.CENTER}
|
||||||
|
PushButton{parent=u_div,x=1,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=function()prev(i)end}
|
||||||
|
PushButton{parent=u_div,x=21,y=1,text=">",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=function()next(i)end}
|
||||||
|
|
||||||
|
local type = util.trinary(unit.num_boilers > 0, "Sodium Cooled Reactor", "Boiling Water Reactor")
|
||||||
|
TextBox{parent=u_div,y=3,text=type,alignment=ALIGN.CENTER,fg_bg=cpair(colors.gray,colors.black)}
|
||||||
|
|
||||||
|
local rate = DataIndicator{parent=u_div,y=5,lu_colors=lu_col,label="Burn",unit="mB/t",format="%10.2f",value=0,commas=true,width=26,fg_bg=text_fg}
|
||||||
|
local temp = DataIndicator{parent=u_div,lu_colors=lu_col,label="Temp",unit=db.temp_label,format="%10.2f",value=0,commas=true,width=26,fg_bg=text_fg}
|
||||||
|
|
||||||
|
local ctrl = IconIndicator{parent=u_div,x=1,y=8,label="Control State",states=mode_states}
|
||||||
|
|
||||||
|
rate.register(u_ps, "act_burn_rate", rate.update)
|
||||||
|
temp.register(u_ps, "temp", function (t) temp.update(db.temp_convert(t)) end)
|
||||||
|
ctrl.register(u_ps, "U_ControlStatus", ctrl.update)
|
||||||
|
|
||||||
|
u_div.line_break()
|
||||||
|
|
||||||
|
local rct = IconIndicator{parent=u_div,x=1,label="Fission Reactor",states=basic_states}
|
||||||
|
local rps = IconIndicator{parent=u_div,x=1,label="Protection System",states=basic_states}
|
||||||
|
|
||||||
|
rct.register(u_ps, "U_ReactorStatus", rct.update)
|
||||||
|
rps.register(u_ps, "U_RPS", rps.update)
|
||||||
|
|
||||||
|
u_div.line_break()
|
||||||
|
|
||||||
|
local rcs = IconIndicator{parent=u_div,x=1,label="Coolant System",states=basic_states}
|
||||||
|
rcs.register(u_ps, "U_RCS", rcs.update)
|
||||||
|
|
||||||
|
for b = 1, unit.num_boilers do
|
||||||
|
local blr = IconIndicator{parent=u_div,x=1,label="Boiler "..b,states=basic_states}
|
||||||
|
blr.register(unit.boiler_ps_tbl[b], "BoilerStatus", blr.update)
|
||||||
|
end
|
||||||
|
|
||||||
|
for t = 1, unit.num_turbines do
|
||||||
|
local tbn = IconIndicator{parent=u_div,x=1,label="Turbine "..t,states=basic_states}
|
||||||
|
tbn.register(unit.turbine_ps_tbl[t], "TurbineStatus", tbn.update)
|
||||||
|
end
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
|
util.nop()
|
||||||
|
|
||||||
|
--#region Alarms Tab
|
||||||
|
|
||||||
|
local alm_div = Div{parent=page_div}
|
||||||
|
table.insert(panes, alm_div)
|
||||||
|
|
||||||
|
local alm_page = app.new_page(u_page, #panes)
|
||||||
|
alm_page.tasks = { update }
|
||||||
|
nav_links[i].alarm = alm_page.nav_to
|
||||||
|
|
||||||
|
TextBox{parent=alm_div,y=1,text="Status Info Display",alignment=ALIGN.CENTER}
|
||||||
|
|
||||||
|
local ecam_disp = ListBox{parent=alm_div,x=2,y=3,scroll_height=100,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)}
|
||||||
|
|
||||||
|
ecam_disp.register(u_ps, "U_ECAM", function (data)
|
||||||
|
local ecam = textutils.unserialize(data)
|
||||||
|
|
||||||
|
ecam_disp.remove_all()
|
||||||
|
for _, entry in ipairs(ecam) do
|
||||||
|
local div = Div{parent=ecam_disp,height=1+#entry.items,fg_bg=cpair(entry.color,colors.black)}
|
||||||
|
local text = TextBox{parent=div,text=entry.text}
|
||||||
|
|
||||||
|
if entry.help then
|
||||||
|
PushButton{parent=div,x=21,y=text.get_y(),text="?",callback=function()db.nav.open_help(entry.help)end,fg_bg=cpair(colors.gray,colors.black)}
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, item in ipairs(entry.items) do
|
||||||
|
local fg_bg = nil
|
||||||
|
if item.color then fg_bg = cpair(item.color, colors.black) end
|
||||||
|
|
||||||
|
text = TextBox{parent=div,x=3,text=item.text,fg_bg=fg_bg}
|
||||||
|
|
||||||
|
if item.help then
|
||||||
|
PushButton{parent=div,x=21,y=text.get_y(),text="?",callback=function()db.nav.open_help(item.help)end,fg_bg=cpair(colors.gray,colors.black)}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
ecam_disp.line_break()
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
|
--#region RPS Tab
|
||||||
|
|
||||||
|
local rps_pane = Div{parent=page_div}
|
||||||
|
local rps_div = Div{parent=rps_pane,x=2,width=main.get_width()-2}
|
||||||
|
table.insert(panes, rps_div)
|
||||||
|
|
||||||
|
local rps_page = app.new_page(u_page, #panes)
|
||||||
|
rps_page.tasks = { update }
|
||||||
|
nav_links[i].rps = rps_page.nav_to
|
||||||
|
|
||||||
|
TextBox{parent=rps_div,y=1,text="Protection System",alignment=ALIGN.CENTER}
|
||||||
|
|
||||||
|
local r_trip = IconIndicator{parent=rps_div,y=3,label="RPS Trip",states=basic_states}
|
||||||
|
r_trip.register(u_ps, "U_RPS", r_trip.update)
|
||||||
|
|
||||||
|
local r_mscrm = IconIndicator{parent=rps_div,y=5,label="Manual SCRAM",states=red_ind_s}
|
||||||
|
local r_ascrm = IconIndicator{parent=rps_div,label="Automatic SCRAM",states=red_ind_s}
|
||||||
|
local rps_tmo = IconIndicator{parent=rps_div,label="Timeout",states=yel_ind_s}
|
||||||
|
local rps_flt = IconIndicator{parent=rps_div,label="PPM Fault",states=yel_ind_s}
|
||||||
|
local rps_sfl = IconIndicator{parent=rps_div,label="Not Formed",states=red_ind_s}
|
||||||
|
|
||||||
|
r_mscrm.register(u_ps, "manual", r_mscrm.update)
|
||||||
|
r_ascrm.register(u_ps, "automatic", r_ascrm.update)
|
||||||
|
rps_tmo.register(u_ps, "timeout", rps_tmo.update)
|
||||||
|
rps_flt.register(u_ps, "fault", rps_flt.update)
|
||||||
|
rps_sfl.register(u_ps, "sys_fail", rps_sfl.update)
|
||||||
|
|
||||||
|
rps_div.line_break()
|
||||||
|
local rps_dmg = IconIndicator{parent=rps_div,label="Reactor Damage Hi",states=red_ind_s}
|
||||||
|
local rps_tmp = IconIndicator{parent=rps_div,label="Temp. Critical",states=red_ind_s}
|
||||||
|
local rps_nof = IconIndicator{parent=rps_div,label="Fuel Level Lo",states=yel_ind_s}
|
||||||
|
local rps_exw = IconIndicator{parent=rps_div,label="Waste Level Hi",states=yel_ind_s}
|
||||||
|
local rps_loc = IconIndicator{parent=rps_div,label="Coolant Lo Lo",states=yel_ind_s}
|
||||||
|
local rps_exh = IconIndicator{parent=rps_div,label="Heated Coolant Hi",states=yel_ind_s}
|
||||||
|
|
||||||
|
rps_dmg.register(u_ps, "high_dmg", rps_dmg.update)
|
||||||
|
rps_tmp.register(u_ps, "high_temp", rps_tmp.update)
|
||||||
|
rps_nof.register(u_ps, "no_fuel", rps_nof.update)
|
||||||
|
rps_exw.register(u_ps, "ex_waste", rps_exw.update)
|
||||||
|
rps_loc.register(u_ps, "low_cool", rps_loc.update)
|
||||||
|
rps_exh.register(u_ps, "ex_hcool", rps_exh.update)
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
|
--#region Reactor Tab
|
||||||
|
|
||||||
|
nav_links[i].reactor = reactor(app, u_page, panes, page_div, u_ps, update)
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
|
--#region RCS Tab
|
||||||
|
|
||||||
|
local rcs_pane = Div{parent=page_div}
|
||||||
|
local rcs_div = Div{parent=rcs_pane,x=2,width=main.get_width()-2}
|
||||||
|
table.insert(panes, rcs_pane)
|
||||||
|
|
||||||
|
local rcs_page = app.new_page(u_page, #panes)
|
||||||
|
rcs_page.tasks = { update }
|
||||||
|
|
||||||
|
nav_links[i].rcs = rcs_page.nav_to
|
||||||
|
|
||||||
|
TextBox{parent=rcs_div,y=1,text="Coolant System",alignment=ALIGN.CENTER}
|
||||||
|
|
||||||
|
local r_rtrip = IconIndicator{parent=rcs_div,y=3,label="RCP Trip",states=red_ind_s}
|
||||||
|
local r_cflow = IconIndicator{parent=rcs_div,label="RCS Flow Lo",states=yel_ind_s}
|
||||||
|
local r_clow = IconIndicator{parent=rcs_div,label="Coolant Level Lo",states=yel_ind_s}
|
||||||
|
|
||||||
|
r_rtrip.register(u_ps, "RCPTrip", r_rtrip.update)
|
||||||
|
r_cflow.register(u_ps, "RCSFlowLow", r_cflow.update)
|
||||||
|
r_clow.register(u_ps, "CoolantLevelLow", r_clow.update)
|
||||||
|
|
||||||
|
local c_flt = IconIndicator{parent=rcs_div,label="RCS HW Fault",states=yel_ind_s}
|
||||||
|
local c_emg = IconIndicator{parent=rcs_div,label="Emergency Coolant",states=emc_ind_s}
|
||||||
|
local c_mwrf = IconIndicator{parent=rcs_div,label="Max Water Return",states=yel_ind_s}
|
||||||
|
|
||||||
|
c_flt.register(u_ps, "RCSFault", c_flt.update)
|
||||||
|
c_emg.register(u_ps, "EmergencyCoolant", c_emg.update)
|
||||||
|
c_mwrf.register(u_ps, "MaxWaterReturnFeed", c_mwrf.update)
|
||||||
|
|
||||||
|
-- rcs_div.line_break()
|
||||||
|
-- TextBox{parent=rcs_div,text="Mismatches",alignment=ALIGN.CENTER,fg_bg=label}
|
||||||
|
local c_cfm = IconIndicator{parent=rcs_div,label="Coolant Feed",states=yel_ind_s}
|
||||||
|
local c_brm = IconIndicator{parent=rcs_div,label="Boil Rate",states=yel_ind_s}
|
||||||
|
local c_sfm = IconIndicator{parent=rcs_div,label="Steam Feed",states=yel_ind_s}
|
||||||
|
|
||||||
|
c_cfm.register(u_ps, "CoolantFeedMismatch", c_cfm.update)
|
||||||
|
c_brm.register(u_ps, "BoilRateMismatch", c_brm.update)
|
||||||
|
c_sfm.register(u_ps, "SteamFeedMismatch", c_sfm.update)
|
||||||
|
|
||||||
|
rcs_div.line_break()
|
||||||
|
-- TextBox{parent=rcs_div,text="Aggregate Checks",alignment=ALIGN.CENTER,fg_bg=label}
|
||||||
|
|
||||||
|
if unit.num_boilers > 0 then
|
||||||
|
local wll = IconIndicator{parent=rcs_div,label="Boiler Water Lo",states=red_ind_s}
|
||||||
|
local hrl = IconIndicator{parent=rcs_div,label="Heating Rate Lo",states=yel_ind_s}
|
||||||
|
|
||||||
|
wll.register(u_ps, "U_WaterLevelLow", wll.update)
|
||||||
|
hrl.register(u_ps, "U_HeatingRateLow", hrl.update)
|
||||||
|
end
|
||||||
|
|
||||||
|
local tospd = IconIndicator{parent=rcs_div,label="TRB Over Speed",states=red_ind_s}
|
||||||
|
local gtrip = IconIndicator{parent=rcs_div,label="Generator Trip",states=yel_ind_s}
|
||||||
|
local ttrip = IconIndicator{parent=rcs_div,label="Turbine Trip",states=red_ind_s}
|
||||||
|
|
||||||
|
tospd.register(u_ps, "U_TurbineOverSpeed", tospd.update)
|
||||||
|
gtrip.register(u_ps, "U_GeneratorTrip", gtrip.update)
|
||||||
|
ttrip.register(u_ps, "U_TurbineTrip", ttrip.update)
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
|
--#region Boiler Tabs
|
||||||
|
|
||||||
|
local blr_pane = Div{parent=page_div}
|
||||||
|
nav_links[i].boiler = {}
|
||||||
|
|
||||||
|
for b_id = 1, unit.num_boilers do
|
||||||
|
local ps = unit.boiler_ps_tbl[b_id]
|
||||||
|
nav_links[i].boiler[b_id] = boiler(app, u_page, panes, blr_pane, b_id, ps, update)
|
||||||
|
end
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
|
--#region Turbine Tabs
|
||||||
|
|
||||||
|
local tbn_pane = Div{parent=page_div}
|
||||||
|
nav_links[i].turbine = {}
|
||||||
|
|
||||||
|
for t_id = 1, unit.num_turbines do
|
||||||
|
local ps = unit.turbine_ps_tbl[t_id]
|
||||||
|
nav_links[i].turbine[t_id] = turbine(app, u_page, panes, tbn_pane, i, t_id, ps, update)
|
||||||
|
end
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
|
util.nop()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- setup multipane
|
||||||
|
local u_pane = MultiPane{parent=page_div,x=1,y=1,panes=panes}
|
||||||
|
app.set_root_pane(u_pane)
|
||||||
|
|
||||||
|
set_sidebar(active_unit)
|
||||||
|
|
||||||
|
-- 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 = function () db.nav.open_app(APP_ID.ROOT) end } })
|
||||||
|
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
|
@ -2,6 +2,8 @@
|
|||||||
-- Connection Waiting Spinner
|
-- Connection Waiting Spinner
|
||||||
--
|
--
|
||||||
|
|
||||||
|
local iocontrol = require("pocket.iocontrol")
|
||||||
|
|
||||||
local style = require("pocket.ui.style")
|
local style = require("pocket.ui.style")
|
||||||
|
|
||||||
local core = require("graphics.core")
|
local core = require("graphics.core")
|
||||||
@ -23,16 +25,20 @@ local function init(parent, y, is_api)
|
|||||||
local root = Div{parent=parent,x=1,y=1}
|
local root = Div{parent=parent,x=1,y=1}
|
||||||
|
|
||||||
-- bounding box div
|
-- bounding box div
|
||||||
local box = Div{parent=root,x=1,y=y,height=5}
|
local box = Div{parent=root,x=1,y=y,height=12}
|
||||||
|
|
||||||
local waiting_x = math.floor(parent.get_width() / 2) - 1
|
local waiting_x = math.floor(parent.get_width() / 2) - 1
|
||||||
|
|
||||||
|
local msg = TextBox{parent=box,x=3,y=11,width=box.get_width()-4,height=2,text="",alignment=ALIGN.CENTER,fg_bg=cpair(colors.red,style.root.bkg)}
|
||||||
|
|
||||||
if is_api then
|
if is_api then
|
||||||
WaitingAnim{parent=box,x=waiting_x,y=1,fg_bg=cpair(colors.blue,style.root.bkg)}
|
WaitingAnim{parent=box,x=waiting_x,y=1,fg_bg=cpair(colors.blue,style.root.bkg)}
|
||||||
TextBox{parent=box,text="Connecting to API",alignment=ALIGN.CENTER,y=5,height=1,fg_bg=cpair(colors.white,style.root.bkg)}
|
TextBox{parent=box,y=5,text="Connecting to API",alignment=ALIGN.CENTER,fg_bg=cpair(colors.white,style.root.bkg)}
|
||||||
|
msg.register(iocontrol.get_db().ps, "api_link_msg", msg.set_value)
|
||||||
else
|
else
|
||||||
WaitingAnim{parent=box,x=waiting_x,y=1,fg_bg=cpair(colors.green,style.root.bkg)}
|
WaitingAnim{parent=box,x=waiting_x,y=1,fg_bg=cpair(colors.green,style.root.bkg)}
|
||||||
TextBox{parent=box,text="Connecting to Supervisor",alignment=ALIGN.CENTER,y=5,height=1,fg_bg=cpair(colors.white,style.root.bkg)}
|
TextBox{parent=box,y=5,text="Connecting to Supervisor",alignment=ALIGN.CENTER,fg_bg=cpair(colors.white,style.root.bkg)}
|
||||||
|
msg.register(iocontrol.get_db().ps, "svr_link_msg", msg.set_value)
|
||||||
end
|
end
|
||||||
|
|
||||||
return root
|
return root
|
||||||
|
136
pocket/ui/docs.lua
Normal file
136
pocket/ui/docs.lua
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
local docs = {}
|
||||||
|
|
||||||
|
local target
|
||||||
|
|
||||||
|
local function doc(key, name, desc)
|
||||||
|
---@class pocket_doc_item
|
||||||
|
local item = { key = key, name = name, desc = desc }
|
||||||
|
table.insert(target, item)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- important to note in the future: The PLC should always be in a chunk with the reactor to ensure it can protect it on chunk load if you do not keep it all chunk loaded
|
||||||
|
|
||||||
|
docs.alarms = {}
|
||||||
|
|
||||||
|
target = docs.alarms
|
||||||
|
doc("ContainmentBreach", "Containment Breach", "Reactor disconnected or indicated unformed while being at or above 100% damage; explosion assumed.")
|
||||||
|
doc("ContainmentRadiation", "Containment Radiation", "Environment detector(s) assigned to the unit have observed high levels of radiation.")
|
||||||
|
doc("ReactorLost", "Reactor Lost", "Reactor PLC has stopped communicating with the supervisor.")
|
||||||
|
doc("CriticalDamage", "Damage Critical", "Reactor damage has reached or exceeded 100%, so it will explode at any moment.")
|
||||||
|
doc("ReactorDamage", "Reactor Damage", "Reactor temperature causing increasing damage to the reactor casing.")
|
||||||
|
doc("ReactorOverTemp", "Reactor Over Temp", "Reactor temperature is at or above maximum safe temperature, so it is now taking damage.")
|
||||||
|
doc("ReactorHighTemp", "Reactor High Temp", "Reactor temperature is above expected operating levels and may exceed maximum safe temperature soon.")
|
||||||
|
doc("ReactorWasteLeak", "Reactor Waste Leak", "The reactor is full of spent waste so it will now emit radiation if additional waste is generated.")
|
||||||
|
doc("ReactorHighWaste", "Reactor High Waste", "Reactor waste levels are high and may leak soon.")
|
||||||
|
doc("RPSTransient", "RPS Transient", "Reactor protection system was activated.")
|
||||||
|
doc("RCSTransient", "RCS Transient", "Something is wrong with the reactor coolant system, check RCS indicators for details.")
|
||||||
|
doc("TurbineTripAlarm", "Turbine Trip", "A turbine stopped rotating, likely due to having full energy storage. This will prevent cooling, so it needs to be resolved before using that unit.")
|
||||||
|
|
||||||
|
docs.annunc = {
|
||||||
|
unit = {
|
||||||
|
main_section = {}, rps_section = {}, rcs_section = {}, fac_section = {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
target = docs.annunc.unit.main_section
|
||||||
|
doc("PLCOnline", "PLC Online", "Indicates if the fission reactor PLC is connected. If it isn't, check that your PLC is on and configured properly.")
|
||||||
|
doc("PLCHeartbeat", "PLC Heartbeat", "An indicator of status data being live. As status messages are received from the PLC, this light will turn on and off. If it gets stuck, the supervisor has stopped receiving data or a screen has frozen.")
|
||||||
|
doc("RadiationMonitor", "Radiation Monitor", "On if at least one environment detector is connected and assigned to this unit.")
|
||||||
|
doc("AutoControl", "Automatic Control", "On if the reactor is under the control of one of the automatic control modes.")
|
||||||
|
doc("ReactorSCRAM", "Reactor SCRAM", "On if the reactor protection system is holding the reactor SCRAM'd.")
|
||||||
|
doc("ManualReactorSCRAM", "Manual Reactor SCRAM", "On if the operator (you) initiated a SCRAM.")
|
||||||
|
doc("AutoReactorSCRAM", "Auto Reactor SCRAM", "On if the automatic control system initiated a SCRAM. The main view screen annunciator will have an indication as to why.")
|
||||||
|
doc("RadiationWarning", "Radiation Warning", "On if radiation levels are above normal. There is likely a leak somewhere, so that should be identified and fixed. Hazmat suit recommended.")
|
||||||
|
doc("RCPTrip", "RCP Trip", "Reactor coolant pump tripped. This is a technical concept not directly mapping to Mekansim. Here, it indicates if there is either high heated coolant or low cooled coolant that caused an RPS trip. Check the coolant system if this occurs.")
|
||||||
|
doc("RCSFlowLow", "RCS Flow Low", "Indicates if the reactor coolant system flow is low. This is observed when the cooled coolant level in the reactor is dropping. This can occur while a turbine spins up, but if it persists, check that the cooling system is operating properly. This can occur with smaller boilers or when using pipes and not having enough.")
|
||||||
|
doc("CoolantLevelLow", "Coolant Level Low", "On if the reactor coolant level is lower than it should be. Check the coolant system.")
|
||||||
|
doc("ReactorTempHigh", "Reactor Temp. High", "On if the reactor temperature is above expected maximum operating temperature. This is not yet damaging, but should be attended to. Check coolant system.")
|
||||||
|
doc("ReactorHighDeltaT", "Reactor High Delta T", "On if the reactor temperature is climbing rapidly. This can occur when a reactor is starting up, but it is a concern if it happens while the burn rate is not increasing.")
|
||||||
|
doc("FuelInputRateLow", "Fuel Input Rate Low", "On if the fissile fuel levels in the reactor are dropping or very low. Ensure a steady supply of fuel is entering the reactor.")
|
||||||
|
doc("WasteLineOcclusion", "Waste Line Occlusion", "Waste levels in the reactor are increasing. Ensure your waste processing system is operating at a sufficient rate for your burn rate.")
|
||||||
|
doc("HighStartupRate", "Startup Rate High", "This is a rough calculation of if your burn rate is high enough to cause a loss of coolant on startup. A burn rate above this is likely to cause that, but it could occur at even higher or even lower rates depending on your setup (such as pipes, water supplies, and boiler tanks).")
|
||||||
|
|
||||||
|
target = docs.annunc.unit.rps_section
|
||||||
|
doc("rps_tripped", "RPS Trip", "Indicates if the reactor protection system has caused a SCRAM.")
|
||||||
|
doc("manual", "Manual Reactor SCRAM", "Indicates if the operator (you) tripped the RPS by pressing SCRAM.")
|
||||||
|
doc("automatic", "Auto Reactor SCRAM", "Indicates if the automatic control system tripped the RPS.")
|
||||||
|
doc("high_dmg", "Damage Level High", "Indicates if the RPS tripped due to significant reactor damage. Await damage levels to lower.")
|
||||||
|
doc("ex_waste", "Excess Waste", "Indicates if the RPS tripped due to very high waste levels. Ensure waste processing system is keeping up.")
|
||||||
|
doc("ex_hcool", "Excess Heated Coolant", "Indicates if the RPS tripped due to very high heated coolant levels. Check that the cooling system is able to keep up with heated coolant flow.")
|
||||||
|
doc("high_temp", "Temperature High", "Indicates if the RPS tripped due to reaching damaging temperatures. Await damage levels to lower.")
|
||||||
|
doc("low_cool", "Coolant Level Low Low", "Indicates if the RPS tripped due to very low coolant levels that result in the temperature uncontrollably rising. Ensure that the cooling system can provide sufficient cooled coolant flow.")
|
||||||
|
doc("no_fuel", "No Fuel", "Indicates if the RPS tripped due to no fuel being available. Check fuel input.")
|
||||||
|
doc("fault", "PPM Fault", "Indicates if the RPS tripped due to a peripheral access fault. Something went wrong interfacing with the reactor, try restarting the PLC.")
|
||||||
|
doc("timeout", "Connection Timeout", "Indicates if the RPS tripped due to losing connection with the supervisory computer. Check that your PLC and supervisor remain chunk loaded.")
|
||||||
|
doc("sys_fail", "System Failure", "Indicates if the RPS tripped due to the reactor not being formed. Ensure that the multi-block is formed.")
|
||||||
|
|
||||||
|
target = docs.annunc.unit.rcs_section
|
||||||
|
doc("RCSFault", "RCS Hardware Fault", "Indicates if one or more of the RCS devices have a peripheral fault. Check that your machines are formed. If this persists, try rebooting affected RTUs.")
|
||||||
|
doc("EmergencyCoolant", "Emergency Coolant", "Off if no emergency coolant redstone is configured, white when it is configured but not in use, and green/blue when it is activated. This is based on an RTU having a redstone emergency coolant output configured for this unit.")
|
||||||
|
doc("CoolantFeedMismatch", "Coolant Feed Mismatch", "The coolant system is accumulating heated coolant or losing cooled coolant, likely due to one of the machines not keeping up with the needs of the reactor. The flow monitor can help figure out where the problem is.")
|
||||||
|
doc("BoilRateMismatch", "Boil Rate Mismatch", "The total heating rate of the reactor exceed the tolerance from the steam input rate of the turbines OR for sodium setups, the boiler boil rates exceed the tolerance from the steam input rate of the turbines. The flow monitor can help figure out where the problem is.")
|
||||||
|
doc("SteamFeedMismatch", "Steam Feed Mismatch", "There is an above tolerance difference between turbine flow and steam input rates or the reactor/boilers are gaining steam or losing water. The flow monitor can help figure out where the problem is.")
|
||||||
|
doc("MaxWaterReturnFeed", "Max Water Return Feed", "The turbines are condensing the max rate of water that they can per the structure build. If water return is insufficient, add more saturating condensers to your turbine(s).")
|
||||||
|
doc("WaterLevelLow", "Water Level Low", "The water level in the boiler is low. A larger boiler water tank may help, or you can feed additional water into the boiler from elsewhere.")
|
||||||
|
doc("HeatingRateLow", "Heating Rate Low", "The boiler is not hot enough to boil water, but it is receiving heated coolant. This is almost never a safety concern.")
|
||||||
|
doc("SteamDumpOpen", "Steam Relief Valve Open", "This turns yellow if the turbine is set to dumping excess and red if it is set to dumping [all]. 'Relief Valve' in this case is that setting allowing the venting of steam. You should never have this set to dumping [all]. Emergency coolant activation from the supervisor will automatically set it to dumping excess to ensure there is no backup of steam as water is added.")
|
||||||
|
doc("TurbineOverSpeed", "Turbine Over Speed", "The turbine is at steam capacity, but not tripped. You may need more turbines if they can't keep up.")
|
||||||
|
doc("GeneratorTrip", "Generator Trip", "The turbine is no longer outputting power due to it having nowhere to go. Likely due to full power storage. This will lead to a Turbine Trip if not addressed.")
|
||||||
|
doc("TurbineTrip", "Turbine Trip", "The turbine has reached its maximum power charge and has stopped rotating, and as a result stopped cooling steam to water. Ensure the turbine has somewhere to output power, as this is the most common cause of reactor meltdowns. However, the likelihood of a meltdown with this system in place is much lower, especially with emergency coolant helping during turbine trips.")
|
||||||
|
|
||||||
|
target = docs.annunc.unit.fac_section
|
||||||
|
doc("?", "Unit Systems Online", "All unit systems (reactors, boilers, and turbines) are connected.")
|
||||||
|
doc("?", "Radiation Monitor", "At least one facility radiation monitor is connected")
|
||||||
|
doc("?", "Induction Matrix", "The induction matrix is connected.")
|
||||||
|
doc("?", "SPS Connected", "Indicates if the super-critical phase shifter is connected.")
|
||||||
|
doc("?", "Configured Units Ready", "All units assigned to automatic control are ready to run automatic control.")
|
||||||
|
doc("?", "Process Active", "Automatic process control is active.")
|
||||||
|
doc("?", "Process Ramping", "Automatic process control is performing an initial ramp-up of the reactors for later PID control (generation and charge mode).")
|
||||||
|
doc("?", "Min/Max Burn Rate", "Auto control has either commanded 0 mB/t or the maximum total burn rate available (from assigned units).")
|
||||||
|
doc("?", "Automatic SCRAM", "Automatic control system SCRAM'ed the assigned reactors due to a safety hazard, shown by the below indicators.")
|
||||||
|
doc("?", "Matrix Disconnected", "Automatic SCRAM occurred due to loss of induction matrix connection.")
|
||||||
|
doc("?", "Matrix Charge High", "Automatic SCRAM occurred due to induction matrix charge exceeding acceptable limit.")
|
||||||
|
doc("?", "Unit Critical Alarm", "Automatic SCRAM occurred due to critical level unit alarm(s).")
|
||||||
|
doc("?", "Facility Radiation High", "Automatic SCRAM occurred due to high facility radiation levels.")
|
||||||
|
doc("?", "Gen. Control Fault", "Automatic SCRAM occurred due to assigned units being degraded/no longer ready during generation mode. The system will automatically resume (starting with initial ramp) once the problem is resolved.")
|
||||||
|
|
||||||
|
docs.glossary = {
|
||||||
|
abbvs = {}, terms = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
target = docs.glossary.abbvs
|
||||||
|
doc("G_ACK", "ACK", "Alarm ACKnowledge. Pressing this acknowledges that you understand an alarm occurred and would like to stop the audio tone(s).")
|
||||||
|
doc("G_Auto", "Auto", "Automatic.")
|
||||||
|
doc("G_CRD", "CRD", "Coordinator. Abbreviation for the coordinator computer.")
|
||||||
|
doc("G_DBG", "DBG", "Debug. Abbreviation for the debugging sessions from pocket computers found on the supervisor's front panel.")
|
||||||
|
doc("G_FP", "FP", "Front Panel. See Terminology.")
|
||||||
|
doc("G_Hi", "Hi", "High.")
|
||||||
|
doc("G_Lo", "Lo", "Low.")
|
||||||
|
doc("G_PID", "PID", "A Proportional Integral Derivitave closed-loop controller.")
|
||||||
|
doc("G_PKT", "PKT", "Pocket. Abbreviation for the pocket computer.")
|
||||||
|
doc("G_PLC", "PLC", "Programmable Logic Controller. A device that not only reports data and controls outputs, but can also make decisions on its own.")
|
||||||
|
doc("G_PPM", "PPM", "Protected Peripheral Manager. This is an abstraction layer created for this project that prevents peripheral calls from crashing applications.")
|
||||||
|
doc("G_RCP", "RCP", "Reactor Coolant Pump. This is from real-world terminology with water-cooled (boiling water and pressurized water) reactors, but in this system it just reflects to the functioning of reactor coolant flow. See the annunciator page on it for more information.")
|
||||||
|
doc("G_RCS", "RCS", "Reactor Cooling System. The combination of all machines used to cool the reactor (turbines, boilers, dynamic tanks).")
|
||||||
|
doc("G_RPS", "RPS", "Reactor Protection System. A component of the reactor PLC responsible for keeping the reactor safe.")
|
||||||
|
doc("G_RTU", "RTU", "Remote Terminal Unit. Provides monitoring to and basic output from a SCADA system, interfacing with various types of devices/interfaces.")
|
||||||
|
doc("G_SCADA", "SCADA", "Supervisory Control and Data Acquisition. A control systems architecture used in a wide variety process control applications.")
|
||||||
|
doc("G_SVR", "SVR", "Supervisor. Abbreviation for the supervisory computer.")
|
||||||
|
doc("G_UI", "UI", "User Interface.")
|
||||||
|
|
||||||
|
target = docs.glossary.terms
|
||||||
|
doc("G_AssignedUnit", "Assigned Unit", "A unit that is assigned to an automatic control group (not assigned to Manual).")
|
||||||
|
doc("G_Fault", "Fault", "Something has gone wrong and/or failed to function.")
|
||||||
|
doc("G_FrontPanel", "Front Panel", "A basic interface on the front of a device for viewing and sometimes modifying its state. This is what you see when looking at a computer running one of the SCADA applications.")
|
||||||
|
doc("G_HighHigh", "High High", "Very High.")
|
||||||
|
doc("G_LowLow", "Low Low", "Very Low.")
|
||||||
|
doc("G_Nominal", "Nominal", "Normal operation. Everything operating as intended.")
|
||||||
|
doc("G_Ringback", "Ringback", "An indication that an alarm had gone off but is no longer having its trip condition(s) met. This is to make you are aware that it occurred.")
|
||||||
|
doc("G_SCRAM", "SCRAM", "[Emergency] shut-down of a reactor by stopping the fission. In Mekanism and here, it isn't always for an emergency.")
|
||||||
|
doc("G_Transient", "Transient", "A temporary change in state from normal operation. Coolant levels dropping or core temperature rising above nominal values are examples of transients.")
|
||||||
|
doc("G_Trip", "Trip", "A checked condition had occurred, see 'Tripped'.")
|
||||||
|
doc("G_Tripped", "Tripped", "An alarm condition has been met, and is still met.")
|
||||||
|
doc("G_Tripping", "Tripping", "Alarm condition(s) is/are met, but has/have not reached the minimum time before the condition(s) is/are deemed a problem.")
|
||||||
|
doc("G_TurbineTrip", "Turbine Trip", "The turbine stopped, which prevents heated coolant from being cooled. In Mekanism, this would occur when a turbine cannot generate any more energy due to filling its buffer and having no output with any remaining energy capacity.")
|
||||||
|
|
||||||
|
return docs
|
@ -2,122 +2,84 @@
|
|||||||
-- Pocket GUI Root
|
-- Pocket GUI Root
|
||||||
--
|
--
|
||||||
|
|
||||||
local iocontrol = require("pocket.iocontrol")
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
local style = require("pocket.ui.style")
|
local iocontrol = require("pocket.iocontrol")
|
||||||
|
local pocket = require("pocket.pocket")
|
||||||
|
|
||||||
local conn_waiting = require("pocket.ui.components.conn_waiting")
|
local diag_apps = require("pocket.ui.apps.diag_apps")
|
||||||
|
local dummy_app = require("pocket.ui.apps.dummy_app")
|
||||||
|
local guide_app = require("pocket.ui.apps.guide")
|
||||||
|
local loader_app = require("pocket.ui.apps.loader")
|
||||||
|
local sys_apps = require("pocket.ui.apps.sys_apps")
|
||||||
|
local unit_app = require("pocket.ui.apps.unit")
|
||||||
|
|
||||||
local boiler_page = require("pocket.ui.pages.boiler_page")
|
local home_page = require("pocket.ui.pages.home_page")
|
||||||
local diag_page = require("pocket.ui.pages.diag_page")
|
|
||||||
local home_page = require("pocket.ui.pages.home_page")
|
|
||||||
local reactor_page = require("pocket.ui.pages.reactor_page")
|
|
||||||
local turbine_page = require("pocket.ui.pages.turbine_page")
|
|
||||||
local unit_page = require("pocket.ui.pages.unit_page")
|
|
||||||
|
|
||||||
local core = require("graphics.core")
|
local style = require("pocket.ui.style")
|
||||||
|
|
||||||
local Div = require("graphics.elements.div")
|
local core = require("graphics.core")
|
||||||
local MultiPane = require("graphics.elements.multipane")
|
|
||||||
local TextBox = require("graphics.elements.textbox")
|
|
||||||
|
|
||||||
local Sidebar = require("graphics.elements.controls.sidebar")
|
local Div = require("graphics.elements.div")
|
||||||
|
local MultiPane = require("graphics.elements.multipane")
|
||||||
|
local TextBox = require("graphics.elements.textbox")
|
||||||
|
|
||||||
local LINK_STATE = iocontrol.LINK_STATE
|
local WaitingAnim = require("graphics.elements.animations.waiting")
|
||||||
local NAV_PAGE = iocontrol.NAV_PAGE
|
|
||||||
|
local PushButton = require("graphics.elements.controls.push_button")
|
||||||
|
local Sidebar = require("graphics.elements.controls.sidebar")
|
||||||
|
|
||||||
|
local SignalBar = require("graphics.elements.indicators.signal")
|
||||||
|
|
||||||
local ALIGN = core.ALIGN
|
local ALIGN = core.ALIGN
|
||||||
|
|
||||||
local cpair = core.cpair
|
local cpair = core.cpair
|
||||||
|
|
||||||
|
local APP_ID = pocket.APP_ID
|
||||||
|
|
||||||
-- create new main view
|
-- create new main view
|
||||||
---@param main graphics_element main displaybox
|
---@param main graphics_element main displaybox
|
||||||
local function init(main)
|
local function init(main)
|
||||||
local nav = iocontrol.get_db().nav
|
local db = iocontrol.get_db()
|
||||||
local ps = iocontrol.get_db().ps
|
|
||||||
|
|
||||||
-- window header message
|
-- window header message and connection status
|
||||||
TextBox{parent=main,y=1,text="",alignment=ALIGN.LEFT,height=1,fg_bg=style.header}
|
TextBox{parent=main,y=1,text="EARLY ACCESS ALPHA 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 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)}
|
||||||
|
|
||||||
--
|
db.ps.subscribe("svr_conn_quality", svr_conn.set_value)
|
||||||
-- root panel panes (connection screens + main screen)
|
db.ps.subscribe("crd_conn_quality", crd_conn.set_value)
|
||||||
--
|
|
||||||
|
|
||||||
local root_pane_div = Div{parent=main,x=1,y=2}
|
local start_pane = Div{parent=main,x=1,y=2}
|
||||||
|
|
||||||
local conn_sv_wait = conn_waiting(root_pane_div, 6, false)
|
|
||||||
local conn_api_wait = conn_waiting(root_pane_div, 6, true)
|
|
||||||
local main_pane = Div{parent=main,x=1,y=2}
|
local main_pane = Div{parent=main,x=1,y=2}
|
||||||
local root_panes = { conn_sv_wait, conn_api_wait, main_pane }
|
|
||||||
|
|
||||||
local root_pane = MultiPane{parent=root_pane_div,x=1,y=1,panes=root_panes}
|
WaitingAnim{parent=start_pane,x=12,y=7,fg_bg=cpair(colors.lightBlue,style.root.bkg)}
|
||||||
|
TextBox{parent=start_pane,y=11,text="starting up...",alignment=ALIGN.CENTER,fg_bg=cpair(colors.lightGray,style.root.bkg)}
|
||||||
|
|
||||||
root_pane.register(ps, "link_state", function (state)
|
local root_pane = MultiPane{parent=main,x=1,y=2,panes={start_pane,main_pane}}
|
||||||
if state == LINK_STATE.UNLINKED or state == LINK_STATE.API_LINK_ONLY then
|
|
||||||
root_pane.set_value(1)
|
|
||||||
elseif state == LINK_STATE.SV_LINK_ONLY then
|
|
||||||
root_pane.set_value(2)
|
|
||||||
else
|
|
||||||
root_pane.set_value(3)
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
--
|
|
||||||
-- main page panel panes & sidebar
|
|
||||||
--
|
|
||||||
|
|
||||||
local page_div = Div{parent=main_pane,x=4,y=1}
|
local page_div = Div{parent=main_pane,x=4,y=1}
|
||||||
|
|
||||||
local sidebar_tabs = {
|
-- create all the apps & pages
|
||||||
{
|
home_page(page_div)
|
||||||
char = "#",
|
unit_app(page_div)
|
||||||
color = cpair(colors.black,colors.green)
|
guide_app(page_div)
|
||||||
},
|
loader_app(page_div)
|
||||||
{
|
sys_apps(page_div)
|
||||||
char = "U",
|
diag_apps(page_div)
|
||||||
color = cpair(colors.black,colors.yellow)
|
dummy_app(page_div)
|
||||||
},
|
|
||||||
{
|
|
||||||
char = "R",
|
|
||||||
color = cpair(colors.black,colors.cyan)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
char = "B",
|
|
||||||
color = cpair(colors.black,colors.lightGray)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
char = "T",
|
|
||||||
color = cpair(colors.black,colors.white)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
char = "D",
|
|
||||||
color = cpair(colors.black,colors.orange)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
local panes = { home_page(page_div), unit_page(page_div), reactor_page(page_div), boiler_page(page_div), turbine_page(page_div), diag_page(page_div) }
|
-- verify all apps were created
|
||||||
|
assert(util.table_len(db.nav.get_containers()) == APP_ID.NUM_APPS, "app IDs were not sequential or some apps weren't registered")
|
||||||
|
|
||||||
local page_pane = MultiPane{parent=page_div,x=1,y=1,panes=panes}
|
db.nav.set_pane(MultiPane{parent=page_div,x=1,y=1,panes=db.nav.get_containers()})
|
||||||
|
db.nav.set_sidebar(Sidebar{parent=main_pane,x=1,y=1,height=18,fg_bg=cpair(colors.white,colors.gray)})
|
||||||
|
|
||||||
local function navigate_sidebar(page)
|
PushButton{parent=main_pane,x=1,y=19,text="\x1b",min_width=3,fg_bg=cpair(colors.white,colors.gray),active_fg_bg=cpair(colors.gray,colors.black),callback=db.nav.nav_up}
|
||||||
if page == 1 then
|
|
||||||
nav.page = nav.sub_pages[NAV_PAGE.HOME]
|
|
||||||
elseif page == 2 then
|
|
||||||
nav.page = nav.sub_pages[NAV_PAGE.UNITS]
|
|
||||||
elseif page == 3 then
|
|
||||||
nav.page = nav.sub_pages[NAV_PAGE.REACTORS]
|
|
||||||
elseif page == 4 then
|
|
||||||
nav.page = nav.sub_pages[NAV_PAGE.BOILERS]
|
|
||||||
elseif page == 5 then
|
|
||||||
nav.page = nav.sub_pages[NAV_PAGE.TURBINES]
|
|
||||||
elseif page == 6 then
|
|
||||||
nav.page = nav.sub_pages[NAV_PAGE.DIAG]
|
|
||||||
end
|
|
||||||
|
|
||||||
page_pane.set_value(page)
|
db.nav.open_app(APP_ID.ROOT)
|
||||||
end
|
|
||||||
|
|
||||||
Sidebar{parent=main_pane,x=1,y=1,tabs=sidebar_tabs,fg_bg=cpair(colors.white,colors.gray),callback=navigate_sidebar}
|
-- done with initial render, lets go!
|
||||||
|
root_pane.set_value(2)
|
||||||
end
|
end
|
||||||
|
|
||||||
return init
|
return init
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
-- local style = require("pocket.ui.style")
|
|
||||||
|
|
||||||
local core = require("graphics.core")
|
|
||||||
|
|
||||||
local Div = require("graphics.elements.div")
|
|
||||||
local TextBox = require("graphics.elements.textbox")
|
|
||||||
|
|
||||||
-- local cpair = core.cpair
|
|
||||||
|
|
||||||
local ALIGN = core.ALIGN
|
|
||||||
|
|
||||||
-- new boiler page view
|
|
||||||
---@param root graphics_element parent
|
|
||||||
local function new_view(root)
|
|
||||||
local main = Div{parent=root,x=1,y=1}
|
|
||||||
|
|
||||||
TextBox{parent=main,text="BOILERS",x=1,y=1,height=1,alignment=ALIGN.CENTER}
|
|
||||||
|
|
||||||
return main
|
|
||||||
end
|
|
||||||
|
|
||||||
return new_view
|
|
68
pocket/ui/pages/guide_section.lua
Normal file
68
pocket/ui/pages/guide_section.lua
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
local log = require("scada-common.log")
|
||||||
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
|
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.push_button")
|
||||||
|
|
||||||
|
local ALIGN = core.ALIGN
|
||||||
|
local cpair = core.cpair
|
||||||
|
|
||||||
|
-- new guide documentation section
|
||||||
|
---@param data _guide_section_constructor_data
|
||||||
|
---@param base_page nav_tree_page
|
||||||
|
---@param title string
|
||||||
|
---@param items table
|
||||||
|
---@param scroll_height integer
|
||||||
|
---@return nav_tree_page
|
||||||
|
return function (data, base_page, title, items, scroll_height)
|
||||||
|
local app, page_div, panes, doc_map, search_db, btn_fg_bg, btn_active = table.unpack(data)
|
||||||
|
|
||||||
|
local section_page = app.new_page(base_page, #panes + 1)
|
||||||
|
local section_div = Div{parent=page_div,x=2}
|
||||||
|
table.insert(panes, section_div)
|
||||||
|
TextBox{parent=section_div,y=1,text=title,alignment=ALIGN.CENTER}
|
||||||
|
PushButton{parent=section_div,x=3,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=base_page.nav_to}
|
||||||
|
|
||||||
|
local view_page = app.new_page(section_page, #panes + 1)
|
||||||
|
local section_view_div = Div{parent=page_div,x=2}
|
||||||
|
table.insert(panes, section_view_div)
|
||||||
|
TextBox{parent=section_view_div,y=1,text=title,alignment=ALIGN.CENTER}
|
||||||
|
PushButton{parent=section_view_div,x=3,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=section_page.nav_to}
|
||||||
|
|
||||||
|
local name_list = ListBox{parent=section_div,x=1,y=3,scroll_height=30,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)}
|
||||||
|
local def_list = ListBox{parent=section_view_div,x=1,y=3,scroll_height=scroll_height,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)}
|
||||||
|
|
||||||
|
local _end
|
||||||
|
|
||||||
|
for i = 1, #items do
|
||||||
|
local item = items[i] ---@type pocket_doc_item
|
||||||
|
|
||||||
|
local anchor = TextBox{parent=def_list,text=item.name,anchor=true,fg_bg=cpair(colors.blue,colors.black)}
|
||||||
|
TextBox{parent=def_list,text=item.desc}
|
||||||
|
_end = Div{parent=def_list,height=1,can_focus=true}
|
||||||
|
|
||||||
|
local function view()
|
||||||
|
_end.focus()
|
||||||
|
view_page.nav_to()
|
||||||
|
anchor.focus()
|
||||||
|
end
|
||||||
|
|
||||||
|
doc_map[item.key] = view
|
||||||
|
table.insert(search_db, { string.lower(item.name), item.name, title, view })
|
||||||
|
|
||||||
|
PushButton{parent=name_list,text=item.name,fg_bg=cpair(colors.blue,colors.black),active_fg_bg=btn_active,callback=view}
|
||||||
|
|
||||||
|
if i % 12 == 0 then util.nop() end
|
||||||
|
end
|
||||||
|
|
||||||
|
log.debug("guide section " .. title .. " generated with final height ".. _end.get_y())
|
||||||
|
|
||||||
|
util.nop()
|
||||||
|
|
||||||
|
return section_page
|
||||||
|
end
|
@ -1,21 +1,64 @@
|
|||||||
local core = require("graphics.core")
|
--
|
||||||
|
-- Main Home Page
|
||||||
|
--
|
||||||
|
|
||||||
local Div = require("graphics.elements.div")
|
local iocontrol = require("pocket.iocontrol")
|
||||||
|
local pocket = require("pocket.pocket")
|
||||||
|
|
||||||
local App = require("graphics.elements.controls.app")
|
local core = require("graphics.core")
|
||||||
|
|
||||||
|
local AppMultiPane = require("graphics.elements.appmultipane")
|
||||||
|
local Div = require("graphics.elements.div")
|
||||||
|
local TextBox = require("graphics.elements.textbox")
|
||||||
|
|
||||||
|
local App = require("graphics.elements.controls.app")
|
||||||
|
|
||||||
|
local ALIGN = core.ALIGN
|
||||||
local cpair = core.cpair
|
local cpair = core.cpair
|
||||||
|
|
||||||
|
local APP_ID = pocket.APP_ID
|
||||||
|
|
||||||
-- new home page view
|
-- new home page view
|
||||||
---@param root graphics_element parent
|
---@param root graphics_element parent
|
||||||
local function new_view(root)
|
local function new_view(root)
|
||||||
local main = Div{parent=root,x=1,y=1}
|
local db = iocontrol.get_db()
|
||||||
|
|
||||||
App{parent=main,x=3,y=2,text="\x17",title="PRC",callback=function()end,app_fg_bg=cpair(colors.black,colors.purple)}
|
local main = Div{parent=root,x=1,y=1,height=19}
|
||||||
App{parent=main,x=10,y=2,text="\x15",title="CTL",callback=function()end,app_fg_bg=cpair(colors.black,colors.green)}
|
|
||||||
App{parent=main,x=17,y=2,text="\x08",title="DEV",callback=function()end,app_fg_bg=cpair(colors.black,colors.lightGray)}
|
local app = db.nav.register_app(APP_ID.ROOT, main)
|
||||||
App{parent=main,x=3,y=7,text="\x7f",title="Waste",callback=function()end,app_fg_bg=cpair(colors.black,colors.brown)}
|
|
||||||
App{parent=main,x=10,y=7,text="\xb6",title="Guide",callback=function()end,app_fg_bg=cpair(colors.black,colors.cyan)}
|
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 panes = { apps_1, apps_2 }
|
||||||
|
|
||||||
|
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}
|
||||||
|
|
||||||
|
app.set_root_pane(app_pane)
|
||||||
|
app.new_page(app.new_page(nil, 1), 2)
|
||||||
|
|
||||||
|
local function open(id) db.nav.open_app(id) end
|
||||||
|
|
||||||
|
app.set_sidebar({
|
||||||
|
{ label = " #\x10", tall = true, color = core.cpair(colors.black, colors.green), callback = function () open(APP_ID.ROOT) end }
|
||||||
|
})
|
||||||
|
|
||||||
|
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=9,y=2,text="F",title="Facil",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.orange),active_fg_bg=active_fg_bg}
|
||||||
|
App{parent=apps_1,x=16,y=2,text="\x15",title="Control",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.green),active_fg_bg=active_fg_bg}
|
||||||
|
App{parent=apps_1,x=2,y=7,text="\x17",title="Process",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.purple),active_fg_bg=active_fg_bg}
|
||||||
|
App{parent=apps_1,x=9,y=7,text="\x7f",title="Waste",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.brown),active_fg_bg=active_fg_bg}
|
||||||
|
App{parent=apps_1,x=16,y=7,text="\x08",title="Devices",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.lightGray),active_fg_bg=active_fg_bg}
|
||||||
|
App{parent=apps_1,x=2,y=12,text="\xb6",title="Guide",callback=function()open(APP_ID.GUIDE)end,app_fg_bg=cpair(colors.black,colors.cyan),active_fg_bg=active_fg_bg}
|
||||||
|
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=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}
|
||||||
|
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}
|
||||||
|
|
||||||
return main
|
return main
|
||||||
end
|
end
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
-- local style = require("pocket.ui.style")
|
|
||||||
|
|
||||||
local core = require("graphics.core")
|
|
||||||
|
|
||||||
local Div = require("graphics.elements.div")
|
|
||||||
local TextBox = require("graphics.elements.textbox")
|
|
||||||
|
|
||||||
-- local cpair = core.cpair
|
|
||||||
|
|
||||||
local ALIGN = core.ALIGN
|
|
||||||
|
|
||||||
-- new reactor page view
|
|
||||||
---@param root graphics_element parent
|
|
||||||
local function new_view(root)
|
|
||||||
local main = Div{parent=root,x=1,y=1}
|
|
||||||
|
|
||||||
TextBox{parent=main,text="REACTOR",x=1,y=1,height=1,alignment=ALIGN.CENTER}
|
|
||||||
|
|
||||||
return main
|
|
||||||
end
|
|
||||||
|
|
||||||
return new_view
|
|
@ -1,22 +0,0 @@
|
|||||||
-- local style = require("pocket.ui.style")
|
|
||||||
|
|
||||||
local core = require("graphics.core")
|
|
||||||
|
|
||||||
local Div = require("graphics.elements.div")
|
|
||||||
local TextBox = require("graphics.elements.textbox")
|
|
||||||
|
|
||||||
-- local cpair = core.cpair
|
|
||||||
|
|
||||||
local ALIGN = core.ALIGN
|
|
||||||
|
|
||||||
-- new turbine page view
|
|
||||||
---@param root graphics_element parent
|
|
||||||
local function new_view(root)
|
|
||||||
local main = Div{parent=root,x=1,y=1}
|
|
||||||
|
|
||||||
TextBox{parent=main,text="TURBINES",x=1,y=1,height=1,alignment=ALIGN.CENTER}
|
|
||||||
|
|
||||||
return main
|
|
||||||
end
|
|
||||||
|
|
||||||
return new_view
|
|
131
pocket/ui/pages/unit_boiler.lua
Normal file
131
pocket/ui/pages/unit_boiler.lua
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
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 PushButton = require("graphics.elements.controls.push_button")
|
||||||
|
|
||||||
|
local DataIndicator = require("graphics.elements.indicators.data")
|
||||||
|
local StateIndicator = require("graphics.elements.indicators.state")
|
||||||
|
local IconIndicator = require("graphics.elements.indicators.icon")
|
||||||
|
local VerticalBar = require("graphics.elements.indicators.vbar")
|
||||||
|
|
||||||
|
local 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 red_ind_s = style.icon_states.red_ind_s
|
||||||
|
local yel_ind_s = style.icon_states.yel_ind_s
|
||||||
|
|
||||||
|
-- create a boiler view in the unit app
|
||||||
|
---@param app pocket_app
|
||||||
|
---@param u_page nav_tree_page
|
||||||
|
---@param panes table
|
||||||
|
---@param blr_pane graphics_element
|
||||||
|
---@param b_id integer boiler ID
|
||||||
|
---@param ps psil
|
||||||
|
---@param update function
|
||||||
|
return function (app, u_page, panes, blr_pane, b_id, ps, update)
|
||||||
|
local db = iocontrol.get_db()
|
||||||
|
|
||||||
|
local blr_div = Div{parent=blr_pane,x=2,width=blr_pane.get_width()-2}
|
||||||
|
table.insert(panes, blr_div)
|
||||||
|
|
||||||
|
local blr_page = app.new_page(u_page, #panes)
|
||||||
|
blr_page.tasks = { update }
|
||||||
|
|
||||||
|
TextBox{parent=blr_div,y=1,text="BLR #"..b_id,width=8}
|
||||||
|
local status = StateIndicator{parent=blr_div,x=10,y=1,states=style.boiler.states,value=1,min_width=12}
|
||||||
|
status.register(ps, "BoilerStateStatus", status.update)
|
||||||
|
|
||||||
|
local hcool = VerticalBar{parent=blr_div,x=1,y=4,fg_bg=cpair(colors.orange,colors.gray),height=5,width=1}
|
||||||
|
local water = VerticalBar{parent=blr_div,x=3,y=4,fg_bg=cpair(colors.blue,colors.gray),height=5,width=1}
|
||||||
|
local steam = VerticalBar{parent=blr_div,x=19,y=4,fg_bg=cpair(colors.white,colors.gray),height=5,width=1}
|
||||||
|
local ccool = VerticalBar{parent=blr_div,x=21,y=4,fg_bg=cpair(colors.lightBlue,colors.gray),height=5,width=1}
|
||||||
|
|
||||||
|
TextBox{parent=blr_div,text="H",x=1,y=3,width=1,fg_bg=label}
|
||||||
|
TextBox{parent=blr_div,text="W",x=3,y=3,width=1,fg_bg=label}
|
||||||
|
TextBox{parent=blr_div,text="S",x=19,y=3,width=1,fg_bg=label}
|
||||||
|
TextBox{parent=blr_div,text="C",x=21,y=3,width=1,fg_bg=label}
|
||||||
|
|
||||||
|
hcool.register(ps, "hcool_fill", hcool.update)
|
||||||
|
water.register(ps, "water_fill", water.update)
|
||||||
|
steam.register(ps, "steam_fill", steam.update)
|
||||||
|
ccool.register(ps, "ccool_fill", ccool.update)
|
||||||
|
|
||||||
|
TextBox{parent=blr_div,text="Temperature",x=5,y=5,width=13,fg_bg=label}
|
||||||
|
local t_prec = util.trinary(db.temp_label == types.TEMP_SCALE_UNITS[types.TEMP_SCALE.KELVIN], 11, 10)
|
||||||
|
local temp = DataIndicator{parent=blr_div,x=5,y=6,lu_colors=lu_col,label="",unit=db.temp_label,format="%"..t_prec..".2f",value=0,commas=true,width=13,fg_bg=text_fg}
|
||||||
|
|
||||||
|
temp.register(ps, "temperature", function (t) temp.update(db.temp_convert(t)) end)
|
||||||
|
|
||||||
|
local b_wll = IconIndicator{parent=blr_div,y=10,label="Water Level Lo",states=red_ind_s}
|
||||||
|
local b_hr = IconIndicator{parent=blr_div,label="Heating Rate Lo",states=yel_ind_s}
|
||||||
|
|
||||||
|
b_wll.register(ps, "WaterLevelLow", b_wll.update)
|
||||||
|
b_hr.register(ps, "HeatingRateLow", b_hr.update)
|
||||||
|
|
||||||
|
TextBox{parent=blr_div,text="Boil Rate",x=1,y=13,width=12,fg_bg=label}
|
||||||
|
local boil_r = DataIndicator{parent=blr_div,x=6,y=14,lu_colors=lu_col,label="",unit="mB/t",format="%11.0f",value=0,commas=true,width=16,fg_bg=text_fg}
|
||||||
|
|
||||||
|
boil_r.register(ps, "boil_rate", boil_r.update)
|
||||||
|
|
||||||
|
local blr_ext_div = Div{parent=blr_pane,x=2,width=blr_pane.get_width()-2}
|
||||||
|
table.insert(panes, blr_ext_div)
|
||||||
|
|
||||||
|
local blr_ext_page = app.new_page(blr_page, #panes)
|
||||||
|
blr_ext_page.tasks = { update }
|
||||||
|
|
||||||
|
PushButton{parent=blr_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=blr_ext_page.nav_to}
|
||||||
|
PushButton{parent=blr_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=blr_page.nav_to}
|
||||||
|
|
||||||
|
TextBox{parent=blr_ext_div,y=1,text="More Boiler Info",alignment=ALIGN.CENTER}
|
||||||
|
|
||||||
|
local function update_amount(indicator)
|
||||||
|
return function (x) indicator.update(x.amount) end
|
||||||
|
end
|
||||||
|
|
||||||
|
TextBox{parent=blr_ext_div,text="Hot Coolant",x=1,y=3,width=12,fg_bg=label}
|
||||||
|
local heated_p = DataIndicator{parent=blr_ext_div,x=14,y=3,lu_colors=lu_col,label="",unit="%",format="%6.2f",value=0,width=8,fg_bg=text_fg}
|
||||||
|
local hcool_amnt = DataIndicator{parent=blr_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}
|
||||||
|
|
||||||
|
heated_p.register(ps, "hcool_fill", function (x) heated_p.update(x * 100) end)
|
||||||
|
hcool_amnt.register(ps, "hcool", update_amount(hcool_amnt))
|
||||||
|
|
||||||
|
TextBox{parent=blr_ext_div,text="Water Tank",x=1,y=6,width=9,fg_bg=label}
|
||||||
|
local fuel_p = DataIndicator{parent=blr_ext_div,x=14,y=6,lu_colors=lu_col,label="",unit="%",format="%6.2f",value=0,width=8,fg_bg=text_fg}
|
||||||
|
local fuel_amnt = DataIndicator{parent=blr_ext_div,x=1,y=7,lu_colors=lu_col,label="",unit="mB",format="%18.0f",value=0,commas=true,width=21,fg_bg=text_fg}
|
||||||
|
|
||||||
|
fuel_p.register(ps, "water_fill", function (x) fuel_p.update(x * 100) end)
|
||||||
|
fuel_amnt.register(ps, "water", update_amount(fuel_amnt))
|
||||||
|
|
||||||
|
TextBox{parent=blr_ext_div,text="Steam Tank",x=1,y=9,width=10,fg_bg=label}
|
||||||
|
local steam_p = DataIndicator{parent=blr_ext_div,x=14,y=9,lu_colors=lu_col,label="",unit="%",format="%6.2f",value=0,width=8,fg_bg=text_fg}
|
||||||
|
local steam_amnt = DataIndicator{parent=blr_ext_div,x=1,y=10,lu_colors=lu_col,label="",unit="mB",format="%18.0f",value=0,commas=true,width=21,fg_bg=text_fg}
|
||||||
|
|
||||||
|
steam_p.register(ps, "steam_fill", function (x) steam_p.update(x * 100) end)
|
||||||
|
steam_amnt.register(ps, "steam", update_amount(steam_amnt))
|
||||||
|
|
||||||
|
TextBox{parent=blr_ext_div,text="Cool Coolant",x=1,y=12,width=12,fg_bg=label}
|
||||||
|
local cooled_p = DataIndicator{parent=blr_ext_div,x=14,y=12,lu_colors=lu_col,label="",unit="%",format="%6.2f",value=0,width=8,fg_bg=text_fg}
|
||||||
|
local ccool_amnt = DataIndicator{parent=blr_ext_div,x=1,y=13,lu_colors=lu_col,label="",unit="mB",format="%18.0f",value=0,commas=true,width=21,fg_bg=text_fg}
|
||||||
|
|
||||||
|
cooled_p.register(ps, "ccool_fill", function (x) cooled_p.update(x * 100) end)
|
||||||
|
ccool_amnt.register(ps, "ccool", update_amount(ccool_amnt))
|
||||||
|
|
||||||
|
TextBox{parent=blr_ext_div,text="Env. Loss",x=1,y=15,width=9,fg_bg=label}
|
||||||
|
local env_loss = DataIndicator{parent=blr_ext_div,x=11,y=15,lu_colors=lu_col,label="",unit="",format="%11.8f",value=0,width=11,fg_bg=text_fg}
|
||||||
|
|
||||||
|
env_loss.register(ps, "env_loss", env_loss.update)
|
||||||
|
|
||||||
|
return blr_page.nav_to
|
||||||
|
end
|
@ -1,22 +0,0 @@
|
|||||||
-- local style = require("pocket.ui.style")
|
|
||||||
|
|
||||||
local core = require("graphics.core")
|
|
||||||
|
|
||||||
local Div = require("graphics.elements.div")
|
|
||||||
local TextBox = require("graphics.elements.textbox")
|
|
||||||
|
|
||||||
-- local cpair = core.cpair
|
|
||||||
|
|
||||||
local ALIGN = core.ALIGN
|
|
||||||
|
|
||||||
-- new unit page view
|
|
||||||
---@param root graphics_element parent
|
|
||||||
local function new_view(root)
|
|
||||||
local main = Div{parent=root,x=1,y=1}
|
|
||||||
|
|
||||||
TextBox{parent=main,text="UNITS",x=1,y=1,height=1,alignment=ALIGN.CENTER}
|
|
||||||
|
|
||||||
return main
|
|
||||||
end
|
|
||||||
|
|
||||||
return new_view
|
|
158
pocket/ui/pages/unit_reactor.lua
Normal file
158
pocket/ui/pages/unit_reactor.lua
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
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 PushButton = require("graphics.elements.controls.push_button")
|
||||||
|
|
||||||
|
local DataIndicator = require("graphics.elements.indicators.data")
|
||||||
|
local StateIndicator = require("graphics.elements.indicators.state")
|
||||||
|
local IconIndicator = require("graphics.elements.indicators.icon")
|
||||||
|
local VerticalBar = require("graphics.elements.indicators.vbar")
|
||||||
|
|
||||||
|
local 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 red_ind_s = style.icon_states.red_ind_s
|
||||||
|
local yel_ind_s = style.icon_states.yel_ind_s
|
||||||
|
|
||||||
|
-- create a reactor view in the unit app
|
||||||
|
---@param app pocket_app
|
||||||
|
---@param u_page nav_tree_page
|
||||||
|
---@param panes table
|
||||||
|
---@param page_div graphics_element
|
||||||
|
---@param u_ps psil
|
||||||
|
---@param update function
|
||||||
|
return function (app, u_page, panes, page_div, u_ps, update)
|
||||||
|
local db = iocontrol.get_db()
|
||||||
|
|
||||||
|
local rct_pane = Div{parent=page_div}
|
||||||
|
local rct_div = Div{parent=rct_pane,x=2,width=page_div.get_width()-2}
|
||||||
|
table.insert(panes, rct_div)
|
||||||
|
|
||||||
|
local rct_page = app.new_page(u_page, #panes)
|
||||||
|
rct_page.tasks = { update }
|
||||||
|
|
||||||
|
TextBox{parent=rct_div,y=1,text="Reactor",width=8}
|
||||||
|
local status = StateIndicator{parent=rct_div,x=10,y=1,states=style.reactor.states,value=1,min_width=12}
|
||||||
|
status.register(u_ps, "U_ReactorStateStatus", status.update)
|
||||||
|
|
||||||
|
local fuel = VerticalBar{parent=rct_div,x=1,y=4,fg_bg=cpair(colors.lightGray,colors.gray),height=5,width=1}
|
||||||
|
local ccool = VerticalBar{parent=rct_div,x=3,y=4,fg_bg=cpair(colors.blue,colors.gray),height=5,width=1}
|
||||||
|
local hcool = VerticalBar{parent=rct_div,x=19,y=4,fg_bg=cpair(colors.white,colors.gray),height=5,width=1}
|
||||||
|
local waste = VerticalBar{parent=rct_div,x=21,y=4,fg_bg=cpair(colors.brown,colors.gray),height=5,width=1}
|
||||||
|
|
||||||
|
TextBox{parent=rct_div,text="F",x=1,y=3,width=1,fg_bg=label}
|
||||||
|
TextBox{parent=rct_div,text="C",x=3,y=3,width=1,fg_bg=label}
|
||||||
|
TextBox{parent=rct_div,text="H",x=19,y=3,width=1,fg_bg=label}
|
||||||
|
TextBox{parent=rct_div,text="W",x=21,y=3,width=1,fg_bg=label}
|
||||||
|
|
||||||
|
fuel.register(u_ps, "fuel_fill", fuel.update)
|
||||||
|
ccool.register(u_ps, "ccool_fill", ccool.update)
|
||||||
|
hcool.register(u_ps, "hcool_fill", hcool.update)
|
||||||
|
waste.register(u_ps, "waste_fill", waste.update)
|
||||||
|
|
||||||
|
ccool.register(u_ps, "ccool_type", function (type)
|
||||||
|
if type == types.FLUID.SODIUM then
|
||||||
|
ccool.recolor(cpair(colors.lightBlue, colors.gray))
|
||||||
|
else
|
||||||
|
ccool.recolor(cpair(colors.blue, colors.gray))
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
hcool.register(u_ps, "hcool_type", function (type)
|
||||||
|
if type == types.FLUID.SUPERHEATED_SODIUM then
|
||||||
|
hcool.recolor(cpair(colors.orange, colors.gray))
|
||||||
|
else
|
||||||
|
hcool.recolor(cpair(colors.white, colors.gray))
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
TextBox{parent=rct_div,text="Burn Rate",x=5,y=4,width=13,fg_bg=label}
|
||||||
|
local burn_rate = DataIndicator{parent=rct_div,x=5,y=5,lu_colors=lu_col,label="",unit="mB/t",format="%8.2f",value=0,commas=true,width=13,fg_bg=text_fg}
|
||||||
|
TextBox{parent=rct_div,text="Temperature",x=5,y=6,width=13,fg_bg=label}
|
||||||
|
local t_prec = util.trinary(db.temp_label == types.TEMP_SCALE_UNITS[types.TEMP_SCALE.KELVIN], 11, 10)
|
||||||
|
local core_temp = DataIndicator{parent=rct_div,x=5,y=7,lu_colors=lu_col,label="",unit=db.temp_label,format="%"..t_prec..".2f",value=0,commas=true,width=13,fg_bg=text_fg}
|
||||||
|
|
||||||
|
burn_rate.register(u_ps, "act_burn_rate", burn_rate.update)
|
||||||
|
core_temp.register(u_ps, "temp", function (t) core_temp.update(db.temp_convert(t)) end)
|
||||||
|
|
||||||
|
local r_temp = IconIndicator{parent=rct_div,y=10,label="Reactor Temp. Hi",states=red_ind_s}
|
||||||
|
local r_rhdt = IconIndicator{parent=rct_div,label="Hi Delta Temp.",states=yel_ind_s}
|
||||||
|
local r_firl = IconIndicator{parent=rct_div,label="Fuel Rate Lo",states=yel_ind_s}
|
||||||
|
local r_wloc = IconIndicator{parent=rct_div,label="Waste Line Occl.",states=yel_ind_s}
|
||||||
|
local r_hsrt = IconIndicator{parent=rct_div,label="Hi Startup Rate",states=yel_ind_s}
|
||||||
|
|
||||||
|
r_temp.register(u_ps, "ReactorTempHigh", r_temp.update)
|
||||||
|
r_rhdt.register(u_ps, "ReactorHighDeltaT", r_rhdt.update)
|
||||||
|
r_firl.register(u_ps, "FuelInputRateLow", r_firl.update)
|
||||||
|
r_wloc.register(u_ps, "WasteLineOcclusion", r_wloc.update)
|
||||||
|
r_hsrt.register(u_ps, "HighStartupRate", r_hsrt.update)
|
||||||
|
|
||||||
|
TextBox{parent=rct_div,text="HR",x=1,y=16,width=4,fg_bg=label}
|
||||||
|
local heating_r = DataIndicator{parent=rct_div,x=6,y=16,lu_colors=lu_col,label="",unit="mB/t",format="%11.0f",value=0,commas=true,width=16,fg_bg=text_fg}
|
||||||
|
TextBox{parent=rct_div,text="DMG",x=1,y=17,width=4,fg_bg=label}
|
||||||
|
local damage_p = DataIndicator{parent=rct_div,x=6,y=17,lu_colors=lu_col,label="",unit="%",format="%11.2f",value=0,width=16,fg_bg=text_fg}
|
||||||
|
|
||||||
|
heating_r.register(u_ps, "heating_rate", heating_r.update)
|
||||||
|
damage_p.register(u_ps, "damage", damage_p.update)
|
||||||
|
|
||||||
|
local rct_ext_div = Div{parent=rct_pane,x=2,width=page_div.get_width()-2}
|
||||||
|
table.insert(panes, rct_ext_div)
|
||||||
|
|
||||||
|
local rct_ext_page = app.new_page(rct_page, #panes)
|
||||||
|
rct_ext_page.tasks = { update }
|
||||||
|
|
||||||
|
PushButton{parent=rct_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=rct_ext_page.nav_to}
|
||||||
|
PushButton{parent=rct_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=rct_page.nav_to}
|
||||||
|
|
||||||
|
TextBox{parent=rct_ext_div,y=1,text="More Reactor Info",alignment=ALIGN.CENTER}
|
||||||
|
|
||||||
|
TextBox{parent=rct_ext_div,text="Fuel Tank",x=1,y=3,width=9,fg_bg=label}
|
||||||
|
local fuel_p = DataIndicator{parent=rct_ext_div,x=14,y=3,lu_colors=lu_col,label="",unit="%",format="%6.2f",value=0,width=8,fg_bg=text_fg}
|
||||||
|
local fuel_amnt = DataIndicator{parent=rct_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}
|
||||||
|
|
||||||
|
fuel_p.register(u_ps, "fuel_fill", function (x) fuel_p.update(x * 100) end)
|
||||||
|
fuel_amnt.register(u_ps, "fuel", fuel_amnt.update)
|
||||||
|
|
||||||
|
TextBox{parent=rct_ext_div,text="Cool Coolant",x=1,y=6,width=12,fg_bg=label}
|
||||||
|
local cooled_p = DataIndicator{parent=rct_ext_div,x=14,y=6,lu_colors=lu_col,label="",unit="%",format="%6.2f",value=0,width=8,fg_bg=text_fg}
|
||||||
|
local ccool_amnt = DataIndicator{parent=rct_ext_div,x=1,y=7,lu_colors=lu_col,label="",unit="mB",format="%18.0f",value=0,commas=true,width=21,fg_bg=text_fg}
|
||||||
|
|
||||||
|
cooled_p.register(u_ps, "ccool_fill", function (x) cooled_p.update(x * 100) end)
|
||||||
|
ccool_amnt.register(u_ps, "ccool_amnt", ccool_amnt.update)
|
||||||
|
|
||||||
|
TextBox{parent=rct_ext_div,text="Hot Coolant",x=1,y=9,width=12,fg_bg=label}
|
||||||
|
local heated_p = DataIndicator{parent=rct_ext_div,x=14,y=9,lu_colors=lu_col,label="",unit="%",format="%6.2f",value=0,width=8,fg_bg=text_fg}
|
||||||
|
local hcool_amnt = DataIndicator{parent=rct_ext_div,x=1,y=10,lu_colors=lu_col,label="",unit="mB",format="%18.0f",value=0,commas=true,width=21,fg_bg=text_fg}
|
||||||
|
|
||||||
|
heated_p.register(u_ps, "hcool_fill", function (x) heated_p.update(x * 100) end)
|
||||||
|
hcool_amnt.register(u_ps, "hcool_amnt", hcool_amnt.update)
|
||||||
|
|
||||||
|
TextBox{parent=rct_ext_div,text="Waste Tank",x=1,y=12,width=10,fg_bg=label}
|
||||||
|
local waste_p = DataIndicator{parent=rct_ext_div,x=14,y=12,lu_colors=lu_col,label="",unit="%",format="%6.2f",value=0,width=8,fg_bg=text_fg}
|
||||||
|
local waste_amnt = DataIndicator{parent=rct_ext_div,x=1,y=13,lu_colors=lu_col,label="",unit="mB",format="%18.0f",value=0,commas=true,width=21,fg_bg=text_fg}
|
||||||
|
|
||||||
|
waste_p.register(u_ps, "waste_fill", function (x) waste_p.update(x * 100) end)
|
||||||
|
waste_amnt.register(u_ps, "waste", waste_amnt.update)
|
||||||
|
|
||||||
|
TextBox{parent=rct_ext_div,text="Boil Eff.",x=1,y=15,width=9,fg_bg=label}
|
||||||
|
TextBox{parent=rct_ext_div,text="Env. Loss",x=1,y=16,width=9,fg_bg=label}
|
||||||
|
local boil_eff = DataIndicator{parent=rct_ext_div,x=11,y=15,lu_colors=lu_col,label="",unit="%",format="%9.2f",value=0,width=11,fg_bg=text_fg}
|
||||||
|
local env_loss = DataIndicator{parent=rct_ext_div,x=11,y=16,lu_colors=lu_col,label="",unit="",format="%11.8f",value=0,width=11,fg_bg=text_fg}
|
||||||
|
|
||||||
|
boil_eff.register(u_ps, "boil_eff", function (x) boil_eff.update(x * 100) end)
|
||||||
|
env_loss.register(u_ps, "env_loss", env_loss.update)
|
||||||
|
|
||||||
|
return rct_page.nav_to
|
||||||
|
end
|
116
pocket/ui/pages/unit_turbine.lua
Normal file
116
pocket/ui/pages/unit_turbine.lua
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
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 PushButton = require("graphics.elements.controls.push_button")
|
||||||
|
|
||||||
|
local DataIndicator = require("graphics.elements.indicators.data")
|
||||||
|
local IconIndicator = require("graphics.elements.indicators.icon")
|
||||||
|
local PowerIndicator = require("graphics.elements.indicators.power")
|
||||||
|
local StateIndicator = require("graphics.elements.indicators.state")
|
||||||
|
local VerticalBar = require("graphics.elements.indicators.vbar")
|
||||||
|
|
||||||
|
local 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 tri_ind_s = style.icon_states.tri_ind_s
|
||||||
|
local red_ind_s = style.icon_states.red_ind_s
|
||||||
|
local yel_ind_s = style.icon_states.yel_ind_s
|
||||||
|
|
||||||
|
-- create a turbine view in the unit app
|
||||||
|
---@param app pocket_app
|
||||||
|
---@param u_page nav_tree_page
|
||||||
|
---@param panes table
|
||||||
|
---@param tbn_pane graphics_element
|
||||||
|
---@param u_id integer unit ID
|
||||||
|
---@param t_id integer turbine ID
|
||||||
|
---@param ps psil
|
||||||
|
---@param update function
|
||||||
|
return function (app, u_page, panes, tbn_pane, u_id, t_id, ps, update)
|
||||||
|
local db = iocontrol.get_db()
|
||||||
|
|
||||||
|
local tbn_div = Div{parent=tbn_pane,x=2,width=tbn_pane.get_width()-2}
|
||||||
|
table.insert(panes, tbn_div)
|
||||||
|
|
||||||
|
local tbn_page = app.new_page(u_page, #panes)
|
||||||
|
tbn_page.tasks = { update }
|
||||||
|
|
||||||
|
TextBox{parent=tbn_div,y=1,text="TRBN #"..t_id,width=8}
|
||||||
|
local status = StateIndicator{parent=tbn_div,x=10,y=1,states=style.turbine.states,value=1,min_width=12}
|
||||||
|
status.register(ps, "TurbineStateStatus", status.update)
|
||||||
|
|
||||||
|
local steam = VerticalBar{parent=tbn_div,x=1,y=4,fg_bg=cpair(colors.white,colors.gray),height=5,width=1}
|
||||||
|
local ccool = VerticalBar{parent=tbn_div,x=21,y=4,fg_bg=cpair(colors.green,colors.gray),height=5,width=1}
|
||||||
|
|
||||||
|
TextBox{parent=tbn_div,text="S",x=1,y=3,width=1,fg_bg=label}
|
||||||
|
TextBox{parent=tbn_div,text="E",x=21,y=3,width=1,fg_bg=label}
|
||||||
|
|
||||||
|
steam.register(ps, "steam_fill", steam.update)
|
||||||
|
ccool.register(ps, "energy_fill", ccool.update)
|
||||||
|
|
||||||
|
TextBox{parent=tbn_div,text="Production",x=3,y=3,width=17,fg_bg=label}
|
||||||
|
local prod_rate = PowerIndicator{parent=tbn_div,x=3,y=4,lu_colors=lu_col,label="",unit=db.energy_label,format="%11.2f",value=0,rate=true,width=17,fg_bg=text_fg}
|
||||||
|
TextBox{parent=tbn_div,text="Flow Rate",x=3,y=5,width=17,fg_bg=label}
|
||||||
|
local flow_rate = DataIndicator{parent=tbn_div,x=3,y=6,lu_colors=lu_col,label="",unit="mB/t",format="%11.0f",value=0,commas=true,width=17,fg_bg=text_fg}
|
||||||
|
TextBox{parent=tbn_div,text="Steam Input Rate",x=3,y=7,width=17,fg_bg=label}
|
||||||
|
local input_rate = DataIndicator{parent=tbn_div,x=3,y=8,lu_colors=lu_col,label="",unit="mB/t",format="%11.0f",value=0,commas=true,width=17,fg_bg=text_fg}
|
||||||
|
|
||||||
|
prod_rate.register(ps, "prod_rate", function (val) prod_rate.update(db.energy_convert(val)) end)
|
||||||
|
flow_rate.register(ps, "flow_rate", flow_rate.update)
|
||||||
|
input_rate.register(ps, "steam_input_rate", input_rate.update)
|
||||||
|
|
||||||
|
local t_sdo = IconIndicator{parent=tbn_div,y=10,label="Steam Dumping",states=tri_ind_s}
|
||||||
|
local t_tos = IconIndicator{parent=tbn_div,label="Over Speed",states=red_ind_s}
|
||||||
|
local t_gtrp = IconIndicator{parent=tbn_div,label="Generator Trip",states=yel_ind_s}
|
||||||
|
local t_trp = IconIndicator{parent=tbn_div,label="Turbine Trip",states=red_ind_s}
|
||||||
|
|
||||||
|
t_sdo.register(ps, "SteamDumpOpen", t_sdo.update)
|
||||||
|
t_tos.register(ps, "TurbineOverSpeed", t_tos.update)
|
||||||
|
t_gtrp.register(ps, "GeneratorTrip", t_gtrp.update)
|
||||||
|
t_trp.register(ps, "TurbineTrip", t_trp.update)
|
||||||
|
|
||||||
|
local tbn_ext_div = Div{parent=tbn_pane,x=2,width=tbn_pane.get_width()-2}
|
||||||
|
table.insert(panes, tbn_ext_div)
|
||||||
|
|
||||||
|
local tbn_ext_page = app.new_page(tbn_page, #panes)
|
||||||
|
tbn_ext_page.tasks = { update }
|
||||||
|
|
||||||
|
PushButton{parent=tbn_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=tbn_ext_page.nav_to}
|
||||||
|
PushButton{parent=tbn_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=tbn_page.nav_to}
|
||||||
|
|
||||||
|
TextBox{parent=tbn_ext_div,y=1,text="More Turbine Info",alignment=ALIGN.CENTER}
|
||||||
|
|
||||||
|
TextBox{parent=tbn_ext_div,text="Steam Tank",x=1,y=3,width=10,fg_bg=label}
|
||||||
|
local steam_p = DataIndicator{parent=tbn_ext_div,x=14,y=3,lu_colors=lu_col,label="",unit="%",format="%6.2f",value=0,width=8,fg_bg=text_fg}
|
||||||
|
local steam_amnt = DataIndicator{parent=tbn_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}
|
||||||
|
|
||||||
|
steam_p.register(ps, "steam_fill", function (x) steam_p.update(x * 100) end)
|
||||||
|
steam_amnt.register(ps, "steam", function (x) steam_amnt.update(x.amount) end)
|
||||||
|
|
||||||
|
TextBox{parent=tbn_ext_div,text="Energy Fill",x=1,y=6,width=12,fg_bg=label}
|
||||||
|
local charge_p = DataIndicator{parent=tbn_ext_div,x=14,y=6,lu_colors=lu_col,label="",unit="%",format="%6.2f",value=0,width=8,fg_bg=text_fg}
|
||||||
|
local charge_amnt = PowerIndicator{parent=tbn_ext_div,x=1,y=7,lu_colors=lu_col,label="",unit=db.energy_label,format="%17.4f",value=0,width=21,fg_bg=text_fg}
|
||||||
|
|
||||||
|
charge_p.register(ps, "energy_fill", function (x) charge_p.update(x * 100) end)
|
||||||
|
charge_amnt.register(ps, "energy", function (val) charge_amnt.update(db.energy_convert(val)) end)
|
||||||
|
|
||||||
|
TextBox{parent=tbn_ext_div,text="Rotation Rate",x=1,y=9,width=13,fg_bg=label}
|
||||||
|
local rotation = DataIndicator{parent=tbn_ext_div,x=1,y=10,lu_colors=lu_col,label="",unit="",format="%21.12f",value=0,width=21,fg_bg=text_fg}
|
||||||
|
|
||||||
|
rotation.register(ps, "steam", function ()
|
||||||
|
local ok, result = pcall(function () return util.turbine_rotation(db.units[u_id].turbine_data_tbl[t_id]) end)
|
||||||
|
if ok then rotation.update(result) end
|
||||||
|
end)
|
||||||
|
|
||||||
|
return tbn_page.nav_to
|
||||||
|
end
|
@ -12,7 +12,9 @@ local cpair = core.cpair
|
|||||||
|
|
||||||
style.root = cpair(colors.white, colors.black)
|
style.root = cpair(colors.white, colors.black)
|
||||||
style.header = cpair(colors.white, colors.gray)
|
style.header = cpair(colors.white, colors.gray)
|
||||||
style.label = cpair(colors.gray, colors.lightGray)
|
style.text_fg = cpair(colors.white, colors._INHERIT)
|
||||||
|
style.label = cpair(colors.lightGray, colors.black)
|
||||||
|
style.label_unit_pair = cpair(colors.lightGray, colors.lightGray)
|
||||||
|
|
||||||
style.colors = {
|
style.colors = {
|
||||||
{ c = colors.red, hex = 0xdf4949 },
|
{ c = colors.red, hex = 0xdf4949 },
|
||||||
@ -33,6 +35,46 @@ style.colors = {
|
|||||||
-- { c = colors.brown, hex = 0x7f664c }
|
-- { c = colors.brown, hex = 0x7f664c }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local states = {}
|
||||||
|
|
||||||
|
states.basic_states = {
|
||||||
|
{ color = cpair(colors.black, colors.lightGray), symbol = "\x07" },
|
||||||
|
{ color = cpair(colors.black, colors.red), symbol = "-" },
|
||||||
|
{ color = cpair(colors.black, colors.yellow), symbol = "\x1e" },
|
||||||
|
{ color = cpair(colors.black, colors.green), symbol = "+" }
|
||||||
|
}
|
||||||
|
|
||||||
|
states.mode_states = {
|
||||||
|
{ color = cpair(colors.black, colors.lightGray), symbol = "\x07" },
|
||||||
|
{ color = cpair(colors.black, colors.red), symbol = "-" },
|
||||||
|
{ color = cpair(colors.black, colors.green), symbol = "+" },
|
||||||
|
{ color = cpair(colors.black, colors.purple), symbol = "A" }
|
||||||
|
}
|
||||||
|
|
||||||
|
states.emc_ind_s = {
|
||||||
|
{ color = cpair(colors.black, colors.gray), symbol = "-" },
|
||||||
|
{ color = cpair(colors.black, colors.white), symbol = "\x07" },
|
||||||
|
{ color = cpair(colors.black, colors.green), symbol = "+" }
|
||||||
|
}
|
||||||
|
|
||||||
|
states.tri_ind_s = {
|
||||||
|
{ color = cpair(colors.black, colors.lightGray), symbol = "+" },
|
||||||
|
{ color = cpair(colors.black, colors.yellow), symbol = "\x1e" },
|
||||||
|
{ color = cpair(colors.black, colors.red), symbol = "-" }
|
||||||
|
}
|
||||||
|
|
||||||
|
states.red_ind_s = {
|
||||||
|
{ color = cpair(colors.black, colors.lightGray), symbol = "+" },
|
||||||
|
{ color = cpair(colors.black, colors.red), symbol = "-" }
|
||||||
|
}
|
||||||
|
|
||||||
|
states.yel_ind_s = {
|
||||||
|
{ color = cpair(colors.black, colors.lightGray), symbol = "+" },
|
||||||
|
{ color = cpair(colors.black, colors.yellow), symbol = "-" }
|
||||||
|
}
|
||||||
|
|
||||||
|
style.icon_states = states
|
||||||
|
|
||||||
-- MAIN LAYOUT --
|
-- MAIN LAYOUT --
|
||||||
|
|
||||||
style.reactor = {
|
style.reactor = {
|
||||||
@ -40,7 +82,7 @@ style.reactor = {
|
|||||||
states = {
|
states = {
|
||||||
{
|
{
|
||||||
color = cpair(colors.black, colors.yellow),
|
color = cpair(colors.black, colors.yellow),
|
||||||
text = "PLC OFF-LINE"
|
text = "OFF-LINE"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color = cpair(colors.black, colors.orange),
|
color = cpair(colors.black, colors.orange),
|
||||||
@ -64,7 +106,7 @@ style.reactor = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
color = cpair(colors.black, colors.red),
|
color = cpair(colors.black, colors.red),
|
||||||
text = "FORCE DISABLED"
|
text = "FORCE DSBL"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
239
reactor-plc/config/check.lua
Normal file
239
reactor-plc/config/check.lua
Normal file
@ -0,0 +1,239 @@
|
|||||||
|
local comms = require("scada-common.comms")
|
||||||
|
local network = require("scada-common.network")
|
||||||
|
local ppm = require("scada-common.ppm")
|
||||||
|
local tcd = require("scada-common.tcd")
|
||||||
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
|
local plc = require("reactor-plc.plc")
|
||||||
|
|
||||||
|
local core = require("graphics.core")
|
||||||
|
|
||||||
|
local Div = require("graphics.elements.div")
|
||||||
|
local ListBox = require("graphics.elements.listbox")
|
||||||
|
local TextBox = require("graphics.elements.textbox")
|
||||||
|
|
||||||
|
local PushButton = require("graphics.elements.controls.push_button")
|
||||||
|
|
||||||
|
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 plc_config
|
||||||
|
|
||||||
|
run_test_btn = nil, ---@type graphics_element
|
||||||
|
sc_log = nil, ---@type graphics_element
|
||||||
|
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.PLC_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.PLC_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.COLLISION then
|
||||||
|
error_msg = "another reactor PLC is connected with this reactor unit ID"
|
||||||
|
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 ...)"
|
||||||
|
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
|
||||||
|
|
||||||
|
-- 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 modem = ppm.get_wireless_modem()
|
||||||
|
local reactor = ppm.get_fission_reactor()
|
||||||
|
local valid_cfg = plc.validate_config(self.settings)
|
||||||
|
|
||||||
|
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 formed...")
|
||||||
|
-- this consumes events, but that is fine here
|
||||||
|
self.self_check_msg(nil, reactor and reactor.isFormed(), "ensure the fission reactor multiblock is formed")
|
||||||
|
|
||||||
|
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
|
||||||
|
self.self_check_msg("> check supervisor connection...")
|
||||||
|
|
||||||
|
-- init mac as needed
|
||||||
|
if self.settings.AuthKey and string.len(self.settings.AuthKey) >= 8 then
|
||||||
|
network.init_mac(self.settings.AuthKey)
|
||||||
|
else
|
||||||
|
network.deinit_mac()
|
||||||
|
end
|
||||||
|
|
||||||
|
self.nic = network.nic(modem)
|
||||||
|
|
||||||
|
self.nic.closeAll()
|
||||||
|
self.nic.open(self.settings.PLC_Channel)
|
||||||
|
|
||||||
|
self.sv_addr = comms.BROADCAST
|
||||||
|
self.net_listen = true
|
||||||
|
|
||||||
|
send_sv(MGMT_TYPE.ESTABLISH, { comms.version, "0.0.0", DEVICE_TYPE.PLC, self.settings.UnitID })
|
||||||
|
|
||||||
|
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 graphics_element
|
||||||
|
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 graphics_element
|
||||||
|
---@param settings_cfg plc_config
|
||||||
|
---@param check_sys graphics_element
|
||||||
|
---@param style table
|
||||||
|
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=" Reactor PLC Self-Check",fg_bg=bw_fg_bg}
|
||||||
|
|
||||||
|
self.sc_log = ListBox{parent=sc,x=1,y=1,height=12,width=49,scroll_height=100,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
|
||||||
|
|
||||||
|
local 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
|
621
reactor-plc/config/system.lua
Normal file
621
reactor-plc/config/system.lua
Normal file
@ -0,0 +1,621 @@
|
|||||||
|
local log = require("scada-common.log")
|
||||||
|
local rsio = require("scada-common.rsio")
|
||||||
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
|
local core = require("graphics.core")
|
||||||
|
local themes = require("graphics.themes")
|
||||||
|
|
||||||
|
local Div = require("graphics.elements.div")
|
||||||
|
local ListBox = require("graphics.elements.listbox")
|
||||||
|
local MultiPane = require("graphics.elements.multipane")
|
||||||
|
local TextBox = require("graphics.elements.textbox")
|
||||||
|
|
||||||
|
local CheckBox = require("graphics.elements.controls.checkbox")
|
||||||
|
local PushButton = require("graphics.elements.controls.push_button")
|
||||||
|
local Radio2D = require("graphics.elements.controls.radio_2d")
|
||||||
|
local RadioButton = require("graphics.elements.controls.radio_button")
|
||||||
|
|
||||||
|
local NumberField = require("graphics.elements.form.number_field")
|
||||||
|
local TextField = require("graphics.elements.form.text_field")
|
||||||
|
|
||||||
|
local IndLight = require("graphics.elements.indicators.light")
|
||||||
|
|
||||||
|
local cpair = core.cpair
|
||||||
|
|
||||||
|
local LEFT = core.ALIGN.LEFT
|
||||||
|
local RIGHT = core.ALIGN.RIGHT
|
||||||
|
|
||||||
|
local self = {
|
||||||
|
importing_legacy = false,
|
||||||
|
|
||||||
|
set_networked = nil, ---@type function
|
||||||
|
bundled_emcool = nil, ---@type function
|
||||||
|
|
||||||
|
show_auth_key = nil, ---@type function
|
||||||
|
show_key_btn = nil, ---@type graphics_element
|
||||||
|
auth_key_textbox = nil, ---@type graphics_element
|
||||||
|
auth_key_value = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
local side_options = { "Top", "Bottom", "Left", "Right", "Front", "Back" }
|
||||||
|
local side_options_map = { "top", "bottom", "left", "right", "front", "back" }
|
||||||
|
local color_options = { "Red", "Orange", "Yellow", "Lime", "Green", "Cyan", "Light Blue", "Blue", "Purple", "Magenta", "Pink", "White", "Light Gray", "Gray", "Black", "Brown" }
|
||||||
|
local color_options_map = { colors.red, colors.orange, colors.yellow, colors.lime, colors.green, colors.cyan, colors.lightBlue, colors.blue, colors.purple, colors.magenta, colors.pink, colors.white, colors.lightGray, colors.gray, colors.black, colors.brown }
|
||||||
|
|
||||||
|
-- convert text representation to index
|
||||||
|
---@param side string
|
||||||
|
local function side_to_idx(side)
|
||||||
|
for k, v in ipairs(side_options_map) do
|
||||||
|
if v == side then return k end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- convert color to index
|
||||||
|
---@param color color
|
||||||
|
local function color_to_idx(color)
|
||||||
|
for k, v in ipairs(color_options_map) do
|
||||||
|
if v == color then return k end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local system = {}
|
||||||
|
|
||||||
|
-- create the system configuration view
|
||||||
|
---@param tool_ctl _plc_cfg_tool_ctl
|
||||||
|
---@param main_pane graphics_element
|
||||||
|
---@param cfg_sys table
|
||||||
|
---@param divs table
|
||||||
|
---@param style table
|
||||||
|
---@param exit function
|
||||||
|
function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit)
|
||||||
|
---@type plc_config, plc_config, plc_config, table, function
|
||||||
|
local settings_cfg, ini_cfg, tmp_cfg, fields, load_settings = table.unpack(cfg_sys)
|
||||||
|
|
||||||
|
---@type graphics_element, graphics_element, graphics_element, graphics_element, graphics_element
|
||||||
|
local plc_cfg, net_cfg, log_cfg, clr_cfg, summary = table.unpack(divs)
|
||||||
|
|
||||||
|
local bw_fg_bg = style.bw_fg_bg
|
||||||
|
local g_lg_fg_bg = style.g_lg_fg_bg
|
||||||
|
local nav_fg_bg = style.nav_fg_bg
|
||||||
|
local btn_act_fg_bg = style.btn_act_fg_bg
|
||||||
|
local btn_dis_fg_bg = style.btn_dis_fg_bg
|
||||||
|
|
||||||
|
--#region PLC
|
||||||
|
|
||||||
|
local plc_c_1 = 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_4 = 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}}
|
||||||
|
|
||||||
|
TextBox{parent=plc_cfg,x=1,y=2,text=" PLC Configuration",fg_bg=cpair(colors.black,colors.orange)}
|
||||||
|
|
||||||
|
TextBox{parent=plc_c_1,x=1,y=1,text="Would you like to set this PLC as networked?"}
|
||||||
|
TextBox{parent=plc_c_1,x=1,y=3,height=4,text="If you have a supervisor, select the box. You will later be prompted to select the network configuration. If you instead want to use this as a standalone safety system, don't select the box.",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
|
local networked = CheckBox{parent=plc_c_1,x=1,y=8,label="Networked",default=ini_cfg.Networked,box_fg_bg=cpair(colors.orange,colors.black)}
|
||||||
|
|
||||||
|
local function submit_networked()
|
||||||
|
self.set_networked(networked.get_value())
|
||||||
|
plc_pane.set_value(2)
|
||||||
|
end
|
||||||
|
|
||||||
|
PushButton{parent=plc_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=plc_c_1,x=44,y=14,text="Next \x1a",callback=submit_networked,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
|
TextBox{parent=plc_c_2,x=1,y=1,text="Please enter the reactor unit ID for this PLC."}
|
||||||
|
TextBox{parent=plc_c_2,x=1,y=3,height=3,text="If this is a networked PLC, currently only IDs 1 through 4 are acceptable.",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
|
TextBox{parent=plc_c_2,x=1,y=6,text="Unit #"}
|
||||||
|
local u_id = NumberField{parent=plc_c_2,x=7,y=6,width=5,max_chars=3,default=ini_cfg.UnitID,min=1,fg_bg=bw_fg_bg}
|
||||||
|
|
||||||
|
local u_id_err = TextBox{parent=plc_c_2,x=8,y=14,width=35,text="Please set a unit ID.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||||
|
|
||||||
|
function self.set_networked(enable)
|
||||||
|
tmp_cfg.Networked = enable
|
||||||
|
if enable then u_id.set_max(4) else u_id.set_max(999) end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function submit_id()
|
||||||
|
local unit_id = tonumber(u_id.get_value())
|
||||||
|
if unit_id ~= nil then
|
||||||
|
u_id_err.hide(true)
|
||||||
|
tmp_cfg.UnitID = unit_id
|
||||||
|
plc_pane.set_value(3)
|
||||||
|
else u_id_err.show() end
|
||||||
|
end
|
||||||
|
|
||||||
|
PushButton{parent=plc_c_2,x=1,y=14,text="\x1b Back",callback=function()plc_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
PushButton{parent=plc_c_2,x=44,y=14,text="Next \x1a",callback=submit_id,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
|
TextBox{parent=plc_c_3,x=1,y=1,height=4,text="When networked, the supervisor takes care of emergency coolant via RTUs. However, you can configure independent emergency coolant via the PLC."}
|
||||||
|
TextBox{parent=plc_c_3,x=1,y=6,height=5,text="This independent control can be used with or without a supervisor. To configure, you would next select the interface of the redstone output connected to one or more mekanism pipes.",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
|
local en_em_cool = CheckBox{parent=plc_c_3,x=1,y=11,label="Enable PLC Emergency Coolant Control",default=ini_cfg.EmerCoolEnable,box_fg_bg=cpair(colors.orange,colors.black)}
|
||||||
|
|
||||||
|
local function next_from_plc()
|
||||||
|
if tmp_cfg.Networked then main_pane.set_value(3) else main_pane.set_value(4) end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function submit_en_emcool()
|
||||||
|
tmp_cfg.EmerCoolEnable = en_em_cool.get_value()
|
||||||
|
if tmp_cfg.EmerCoolEnable then plc_pane.set_value(4) else next_from_plc() end
|
||||||
|
end
|
||||||
|
|
||||||
|
PushButton{parent=plc_c_3,x=1,y=14,text="\x1b Back",callback=function()plc_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
PushButton{parent=plc_c_3,x=44,y=14,text="Next \x1a",callback=submit_en_emcool,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
|
TextBox{parent=plc_c_4,x=1,y=1,text="Emergency Coolant Redstone Output Side"}
|
||||||
|
local side = Radio2D{parent=plc_c_4,x=1,y=2,rows=2,columns=3,default=side_to_idx(ini_cfg.EmerCoolSide),options=side_options,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.orange}
|
||||||
|
|
||||||
|
TextBox{parent=plc_c_4,x=1,y=5,text="Bundled Redstone Configuration"}
|
||||||
|
local bundled = CheckBox{parent=plc_c_4,x=1,y=6,label="Is Bundled?",default=ini_cfg.EmerCoolColor~=nil,box_fg_bg=cpair(colors.orange,colors.black),callback=function(v)self.bundled_emcool(v)end}
|
||||||
|
local color = Radio2D{parent=plc_c_4,x=1,y=8,rows=4,columns=4,default=color_to_idx(ini_cfg.EmerCoolColor),options=color_options,radio_colors=cpair(colors.lightGray,colors.black),color_map=color_options_map,disable_color=colors.gray,disable_fg_bg=g_lg_fg_bg}
|
||||||
|
if ini_cfg.EmerCoolColor == nil then color.disable() end
|
||||||
|
|
||||||
|
function self.bundled_emcool(en) if en then color.enable() else color.disable() end end
|
||||||
|
|
||||||
|
local function submit_emcool()
|
||||||
|
tmp_cfg.EmerCoolSide = side_options_map[side.get_value()]
|
||||||
|
tmp_cfg.EmerCoolColor = util.trinary(bundled.get_value(), color_options_map[color.get_value()], nil)
|
||||||
|
next_from_plc()
|
||||||
|
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=44,y=14,text="Next \x1a",callback=submit_emcool,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
|
--#region Network
|
||||||
|
|
||||||
|
local net_c_1 = Div{parent=net_cfg,x=2,y=4,width=49}
|
||||||
|
local net_c_2 = Div{parent=net_cfg,x=2,y=4,width=49}
|
||||||
|
local net_c_3 = Div{parent=net_cfg,x=2,y=4,width=49}
|
||||||
|
|
||||||
|
local net_pane = MultiPane{parent=net_cfg,x=1,y=4,panes={net_c_1,net_c_2,net_c_3}}
|
||||||
|
|
||||||
|
TextBox{parent=net_cfg,x=1,y=2,text=" Network Configuration",fg_bg=cpair(colors.black,colors.lightBlue)}
|
||||||
|
|
||||||
|
TextBox{parent=net_c_1,x=1,y=1,text="Please set the network channels below."}
|
||||||
|
TextBox{parent=net_c_1,x=1,y=3,height=4,text="Each of the 5 uniquely named channels, including the 2 below, must be the same for each device in this SCADA network. For multiplayer servers, it is recommended to not use the default channels.",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
|
TextBox{parent=net_c_1,x=1,y=8,text="Supervisor Channel"}
|
||||||
|
local svr_chan = NumberField{parent=net_c_1,x=1,y=9,width=7,default=ini_cfg.SVR_Channel,min=1,max=65535,fg_bg=bw_fg_bg}
|
||||||
|
TextBox{parent=net_c_1,x=9,y=9,height=4,text="[SVR_CHANNEL]",fg_bg=g_lg_fg_bg}
|
||||||
|
TextBox{parent=net_c_1,x=1,y=11,text="PLC Channel"}
|
||||||
|
local plc_chan = NumberField{parent=net_c_1,x=1,y=12,width=7,default=ini_cfg.PLC_Channel,min=1,max=65535,fg_bg=bw_fg_bg}
|
||||||
|
TextBox{parent=net_c_1,x=9,y=12,height=4,text="[PLC_CHANNEL]",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
|
local chan_err = TextBox{parent=net_c_1,x=8,y=14,width=35,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||||
|
|
||||||
|
local function submit_channels()
|
||||||
|
local svr_c = tonumber(svr_chan.get_value())
|
||||||
|
local plc_c = tonumber(plc_chan.get_value())
|
||||||
|
if svr_c ~= nil and plc_c ~= nil then
|
||||||
|
tmp_cfg.SVR_Channel = svr_c
|
||||||
|
tmp_cfg.PLC_Channel = plc_c
|
||||||
|
net_pane.set_value(2)
|
||||||
|
chan_err.hide(true)
|
||||||
|
elseif svr_c == nil then
|
||||||
|
chan_err.set_value("Please set the supervisor channel.")
|
||||||
|
chan_err.show()
|
||||||
|
else
|
||||||
|
chan_err.set_value("Please set the PLC channel.")
|
||||||
|
chan_err.show()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
PushButton{parent=net_c_1,x=1,y=14,text="\x1b Back",callback=function()main_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
PushButton{parent=net_c_1,x=44,y=14,text="Next \x1a",callback=submit_channels,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
|
TextBox{parent=net_c_2,x=1,y=1,text="Connection Timeout"}
|
||||||
|
local timeout = NumberField{parent=net_c_2,x=1,y=2,width=7,default=ini_cfg.ConnTimeout,min=2,max=25,max_chars=6,max_frac_digits=2,allow_decimal=true,fg_bg=bw_fg_bg}
|
||||||
|
TextBox{parent=net_c_2,x=9,y=2,height=2,text="seconds (default 5)",fg_bg=g_lg_fg_bg}
|
||||||
|
TextBox{parent=net_c_2,x=1,y=3,height=4,text="You generally do not want or need to modify this. On slow servers, you can increase this to make the system wait longer before assuming a disconnection.",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
|
TextBox{parent=net_c_2,x=1,y=8,text="Trusted Range"}
|
||||||
|
local range = NumberField{parent=net_c_2,x=1,y=9,width=10,default=ini_cfg.TrustedRange,min=0,max_chars=20,allow_decimal=true,fg_bg=bw_fg_bg}
|
||||||
|
TextBox{parent=net_c_2,x=1,y=10,height=4,text="Setting this to a value larger than 0 prevents connections with devices that many meters (blocks) away in any direction.",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
|
local p2_err = TextBox{parent=net_c_2,x=8,y=14,width=35,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||||
|
|
||||||
|
local function submit_ct_tr()
|
||||||
|
local timeout_val = tonumber(timeout.get_value())
|
||||||
|
local range_val = tonumber(range.get_value())
|
||||||
|
if timeout_val ~= nil and range_val ~= nil then
|
||||||
|
tmp_cfg.ConnTimeout = timeout_val
|
||||||
|
tmp_cfg.TrustedRange = range_val
|
||||||
|
net_pane.set_value(3)
|
||||||
|
p2_err.hide(true)
|
||||||
|
elseif timeout_val == nil then
|
||||||
|
p2_err.set_value("Please set the connection timeout.")
|
||||||
|
p2_err.show()
|
||||||
|
else
|
||||||
|
p2_err.set_value("Please set the trusted range.")
|
||||||
|
p2_err.show()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
PushButton{parent=net_c_2,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
PushButton{parent=net_c_2,x=44,y=14,text="Next \x1a",callback=submit_ct_tr,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
|
TextBox{parent=net_c_3,x=1,y=1,height=2,text="Optionally, set the facility authentication key below. Do NOT use one of your passwords."}
|
||||||
|
TextBox{parent=net_c_3,x=1,y=4,height=6,text="This enables verifying that messages are authentic, so it is intended for security on multiplayer servers. All devices on the same network MUST use the same key if any device has a key. This does result in some extra compution (can slow things down).",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
|
TextBox{parent=net_c_3,x=1,y=11,text="Facility Auth Key"}
|
||||||
|
local key, _, censor = TextField{parent=net_c_3,x=1,y=12,max_len=64,value=ini_cfg.AuthKey,width=32,height=1,fg_bg=bw_fg_bg}
|
||||||
|
|
||||||
|
local function censor_key(enable) censor(util.trinary(enable, "*", nil)) end
|
||||||
|
|
||||||
|
local hide_key = CheckBox{parent=net_c_3,x=34,y=12,label="Hide",box_fg_bg=cpair(colors.lightBlue,colors.black),callback=censor_key}
|
||||||
|
|
||||||
|
hide_key.set_value(true)
|
||||||
|
censor_key(true)
|
||||||
|
|
||||||
|
local key_err = TextBox{parent=net_c_3,x=8,y=14,width=35,text="Key must be at least 8 characters.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||||
|
|
||||||
|
local function submit_auth()
|
||||||
|
local v = key.get_value()
|
||||||
|
if string.len(v) == 0 or string.len(v) >= 8 then
|
||||||
|
tmp_cfg.AuthKey = key.get_value()
|
||||||
|
main_pane.set_value(4)
|
||||||
|
key_err.hide(true)
|
||||||
|
else key_err.show() end
|
||||||
|
end
|
||||||
|
|
||||||
|
PushButton{parent=net_c_3,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
PushButton{parent=net_c_3,x=44,y=14,text="Next \x1a",callback=submit_auth,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
|
--#region Logging
|
||||||
|
|
||||||
|
local log_c_1 = Div{parent=log_cfg,x=2,y=4,width=49}
|
||||||
|
|
||||||
|
TextBox{parent=log_cfg,x=1,y=2,text=" Logging Configuration",fg_bg=cpair(colors.black,colors.pink)}
|
||||||
|
|
||||||
|
TextBox{parent=log_c_1,x=1,y=1,text="Please configure logging below."}
|
||||||
|
|
||||||
|
TextBox{parent=log_c_1,x=1,y=3,text="Log File Mode"}
|
||||||
|
local mode = RadioButton{parent=log_c_1,x=1,y=4,default=ini_cfg.LogMode+1,options={"Append on Startup","Replace on Startup"},callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.pink}
|
||||||
|
|
||||||
|
TextBox{parent=log_c_1,x=1,y=7,text="Log File Path"}
|
||||||
|
local path = TextField{parent=log_c_1,x=1,y=8,width=49,height=1,value=ini_cfg.LogPath,max_len=128,fg_bg=bw_fg_bg}
|
||||||
|
|
||||||
|
local en_dbg = CheckBox{parent=log_c_1,x=1,y=10,default=ini_cfg.LogDebug,label="Enable Logging Debug Messages",box_fg_bg=cpair(colors.pink,colors.black)}
|
||||||
|
TextBox{parent=log_c_1,x=3,y=11,height=2,text="This results in much larger log files. It is best to only use this when there is a problem.",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
|
local path_err = TextBox{parent=log_c_1,x=8,y=14,width=35,text="Please provide a log file path.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||||
|
|
||||||
|
local function submit_log()
|
||||||
|
if path.get_value() ~= "" then
|
||||||
|
path_err.hide(true)
|
||||||
|
tmp_cfg.LogMode = mode.get_value() - 1
|
||||||
|
tmp_cfg.LogPath = path.get_value()
|
||||||
|
tmp_cfg.LogDebug = en_dbg.get_value()
|
||||||
|
tool_ctl.color_apply.hide(true)
|
||||||
|
tool_ctl.color_next.show()
|
||||||
|
main_pane.set_value(5)
|
||||||
|
else path_err.show() end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function back_from_log()
|
||||||
|
if tmp_cfg.Networked then main_pane.set_value(3) else main_pane.set_value(2) end
|
||||||
|
end
|
||||||
|
|
||||||
|
PushButton{parent=log_c_1,x=1,y=14,text="\x1b Back",callback=back_from_log,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
PushButton{parent=log_c_1,x=44,y=14,text="Next \x1a",callback=submit_log,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
|
--#region Color Options
|
||||||
|
|
||||||
|
local clr_c_1 = Div{parent=clr_cfg,x=2,y=4,width=49}
|
||||||
|
local clr_c_2 = Div{parent=clr_cfg,x=2,y=4,width=49}
|
||||||
|
local clr_c_3 = Div{parent=clr_cfg,x=2,y=4,width=49}
|
||||||
|
local clr_c_4 = Div{parent=clr_cfg,x=2,y=4,width=49}
|
||||||
|
|
||||||
|
local clr_pane = MultiPane{parent=clr_cfg,x=1,y=4,panes={clr_c_1,clr_c_2,clr_c_3,clr_c_4}}
|
||||||
|
|
||||||
|
TextBox{parent=clr_cfg,x=1,y=2,text=" Color Configuration",fg_bg=cpair(colors.black,colors.magenta)}
|
||||||
|
|
||||||
|
TextBox{parent=clr_c_1,x=1,y=1,height=2,text="Here you can select the color theme for the front panel."}
|
||||||
|
TextBox{parent=clr_c_1,x=1,y=4,height=2,text="Click 'Accessibility' below to access colorblind assistive options.",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
|
TextBox{parent=clr_c_1,x=1,y=7,text="Front Panel Theme"}
|
||||||
|
local fp_theme = RadioButton{parent=clr_c_1,x=1,y=8,default=ini_cfg.FrontPanelTheme,options=themes.FP_THEME_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.magenta}
|
||||||
|
|
||||||
|
TextBox{parent=clr_c_2,x=1,y=1,height=6,text="This system uses color heavily to distinguish ok and not, with some indicators using many colors. By selecting a mode below, indicators will change as shown. For non-standard modes, indicators with more than two colors will be split up."}
|
||||||
|
|
||||||
|
TextBox{parent=clr_c_2,x=21,y=7,text="Preview"}
|
||||||
|
local _ = IndLight{parent=clr_c_2,x=21,y=8,label="Good",colors=cpair(colors.black,colors.green)}
|
||||||
|
_ = IndLight{parent=clr_c_2,x=21,y=9,label="Warning",colors=cpair(colors.black,colors.yellow)}
|
||||||
|
_ = IndLight{parent=clr_c_2,x=21,y=10,label="Bad",colors=cpair(colors.black,colors.red)}
|
||||||
|
local b_off = IndLight{parent=clr_c_2,x=21,y=11,label="Off",colors=cpair(colors.black,colors.black),hidden=true}
|
||||||
|
local g_off = IndLight{parent=clr_c_2,x=21,y=11,label="Off",colors=cpair(colors.gray,colors.gray),hidden=true}
|
||||||
|
|
||||||
|
local function recolor(value)
|
||||||
|
local c = themes.smooth_stone.color_modes[value]
|
||||||
|
|
||||||
|
if value == themes.COLOR_MODE.STANDARD or value == themes.COLOR_MODE.BLUE_IND then
|
||||||
|
b_off.hide()
|
||||||
|
g_off.show()
|
||||||
|
else
|
||||||
|
g_off.hide()
|
||||||
|
b_off.show()
|
||||||
|
end
|
||||||
|
|
||||||
|
if #c == 0 then
|
||||||
|
for i = 1, #style.colors do term.setPaletteColor(style.colors[i].c, style.colors[i].hex) end
|
||||||
|
else
|
||||||
|
term.setPaletteColor(colors.green, c[1].hex)
|
||||||
|
term.setPaletteColor(colors.yellow, c[2].hex)
|
||||||
|
term.setPaletteColor(colors.red, c[3].hex)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
TextBox{parent=clr_c_2,x=1,y=7,width=10,text="Color Mode"}
|
||||||
|
local c_mode = RadioButton{parent=clr_c_2,x=1,y=8,default=ini_cfg.ColorMode,options=themes.COLOR_MODE_NAMES,callback=recolor,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.magenta}
|
||||||
|
|
||||||
|
TextBox{parent=clr_c_2,x=21,y=13,height=2,width=18,text="Note: exact color varies by theme.",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
|
PushButton{parent=clr_c_2,x=44,y=14,min_width=6,text="Done",callback=function()clr_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
|
local function back_from_colors()
|
||||||
|
main_pane.set_value(util.trinary(tool_ctl.jumped_to_color, 1, 4))
|
||||||
|
tool_ctl.jumped_to_color = false
|
||||||
|
recolor(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function show_access()
|
||||||
|
clr_pane.set_value(2)
|
||||||
|
recolor(c_mode.get_value())
|
||||||
|
end
|
||||||
|
|
||||||
|
local function submit_colors()
|
||||||
|
tmp_cfg.FrontPanelTheme = fp_theme.get_value()
|
||||||
|
tmp_cfg.ColorMode = c_mode.get_value()
|
||||||
|
|
||||||
|
if tool_ctl.jumped_to_color then
|
||||||
|
settings.set("FrontPanelTheme", tmp_cfg.FrontPanelTheme)
|
||||||
|
settings.set("ColorMode", tmp_cfg.ColorMode)
|
||||||
|
|
||||||
|
if settings.save("/reactor-plc.settings") then
|
||||||
|
load_settings(settings_cfg, true)
|
||||||
|
load_settings(ini_cfg)
|
||||||
|
clr_pane.set_value(3)
|
||||||
|
else
|
||||||
|
clr_pane.set_value(4)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
tool_ctl.gen_summary(tmp_cfg)
|
||||||
|
tool_ctl.viewing_config = false
|
||||||
|
self.importing_legacy = false
|
||||||
|
tool_ctl.settings_apply.show()
|
||||||
|
main_pane.set_value(6)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
PushButton{parent=clr_c_1,x=1,y=14,text="\x1b Back",callback=back_from_colors,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
PushButton{parent=clr_c_1,x=8,y=14,min_width=15,text="Accessibility",callback=show_access,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
tool_ctl.color_next = PushButton{parent=clr_c_1,x=44,y=14,text="Next \x1a",callback=submit_colors,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
tool_ctl.color_apply = PushButton{parent=clr_c_1,x=43,y=14,min_width=7,text="Apply",callback=submit_colors,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
|
tool_ctl.color_apply.hide(true)
|
||||||
|
|
||||||
|
local function c_go_home()
|
||||||
|
main_pane.set_value(1)
|
||||||
|
clr_pane.set_value(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
TextBox{parent=clr_c_3,x=1,y=1,text="Settings saved!"}
|
||||||
|
PushButton{parent=clr_c_3,x=1,y=14,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray)}
|
||||||
|
PushButton{parent=clr_c_3,x=44,y=14,min_width=6,text="Home",callback=c_go_home,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
|
TextBox{parent=clr_c_4,x=1,y=1,height=5,text="Failed to save the settings file.\n\nThere may not be enough space for the modification or server file permissions may be denying writes."}
|
||||||
|
PushButton{parent=clr_c_4,x=1,y=14,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray)}
|
||||||
|
PushButton{parent=clr_c_4,x=44,y=14,min_width=6,text="Home",callback=c_go_home,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
|
--#region Summary and Saving
|
||||||
|
|
||||||
|
local sum_c_1 = Div{parent=summary,x=2,y=4,width=49}
|
||||||
|
local sum_c_2 = Div{parent=summary,x=2,y=4,width=49}
|
||||||
|
local sum_c_3 = Div{parent=summary,x=2,y=4,width=49}
|
||||||
|
local sum_c_4 = Div{parent=summary,x=2,y=4,width=49}
|
||||||
|
|
||||||
|
local sum_pane = MultiPane{parent=summary,x=1,y=4,panes={sum_c_1,sum_c_2,sum_c_3,sum_c_4}}
|
||||||
|
|
||||||
|
TextBox{parent=summary,x=1,y=2,text=" Summary",fg_bg=cpair(colors.black,colors.green)}
|
||||||
|
|
||||||
|
local setting_list = ListBox{parent=sum_c_1,x=1,y=1,height=12,width=49,scroll_height=100,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
|
||||||
|
|
||||||
|
local function back_from_settings()
|
||||||
|
if tool_ctl.viewing_config or self.importing_legacy then
|
||||||
|
main_pane.set_value(1)
|
||||||
|
tool_ctl.viewing_config = false
|
||||||
|
self.importing_legacy = false
|
||||||
|
tool_ctl.settings_apply.show()
|
||||||
|
else
|
||||||
|
main_pane.set_value(5)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param element graphics_element
|
||||||
|
---@param data any
|
||||||
|
local function try_set(element, data)
|
||||||
|
if data ~= nil then element.set_value(data) end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function save_and_continue()
|
||||||
|
for _, field in ipairs(fields) do
|
||||||
|
local k, v = field[1], tmp_cfg[field[1]]
|
||||||
|
if v == nil then settings.unset(k) else settings.set(k, v) end
|
||||||
|
end
|
||||||
|
|
||||||
|
if settings.save("/reactor-plc.settings") then
|
||||||
|
load_settings(settings_cfg, true)
|
||||||
|
load_settings(ini_cfg)
|
||||||
|
|
||||||
|
try_set(networked, ini_cfg.Networked)
|
||||||
|
try_set(u_id, ini_cfg.UnitID)
|
||||||
|
try_set(en_em_cool, ini_cfg.EmerCoolEnable)
|
||||||
|
try_set(side, side_to_idx(ini_cfg.EmerCoolSide))
|
||||||
|
try_set(bundled, ini_cfg.EmerCoolColor ~= nil)
|
||||||
|
if ini_cfg.EmerCoolColor ~= nil then try_set(color, color_to_idx(ini_cfg.EmerCoolColor)) end
|
||||||
|
try_set(svr_chan, ini_cfg.SVR_Channel)
|
||||||
|
try_set(plc_chan, ini_cfg.PLC_Channel)
|
||||||
|
try_set(timeout, ini_cfg.ConnTimeout)
|
||||||
|
try_set(range, ini_cfg.TrustedRange)
|
||||||
|
try_set(key, ini_cfg.AuthKey)
|
||||||
|
try_set(mode, ini_cfg.LogMode)
|
||||||
|
try_set(path, ini_cfg.LogPath)
|
||||||
|
try_set(en_dbg, ini_cfg.LogDebug)
|
||||||
|
try_set(fp_theme, ini_cfg.FrontPanelTheme)
|
||||||
|
try_set(c_mode, ini_cfg.ColorMode)
|
||||||
|
|
||||||
|
tool_ctl.view_cfg.enable()
|
||||||
|
tool_ctl.color_cfg.enable()
|
||||||
|
|
||||||
|
if self.importing_legacy then
|
||||||
|
self.importing_legacy = false
|
||||||
|
sum_pane.set_value(3)
|
||||||
|
else
|
||||||
|
sum_pane.set_value(2)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
sum_pane.set_value(4)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
PushButton{parent=sum_c_1,x=1,y=14,text="\x1b Back",callback=back_from_settings,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
self.show_key_btn = PushButton{parent=sum_c_1,x=8,y=14,min_width=17,text="Unhide Auth Key",callback=function()self.show_auth_key()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
||||||
|
tool_ctl.settings_apply = PushButton{parent=sum_c_1,x=43,y=14,min_width=7,text="Apply",callback=save_and_continue,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
|
TextBox{parent=sum_c_2,x=1,y=1,text="Settings saved!"}
|
||||||
|
TextBox{parent=sum_c_2,x=1,y=3,text="Tip: you can run a Self-Check from the configurator home screen to make sure everything is going to work right!"}
|
||||||
|
|
||||||
|
local function go_home()
|
||||||
|
main_pane.set_value(1)
|
||||||
|
plc_pane.set_value(1)
|
||||||
|
net_pane.set_value(1)
|
||||||
|
clr_pane.set_value(1)
|
||||||
|
sum_pane.set_value(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
PushButton{parent=sum_c_2,x=1,y=14,min_width=6,text="Home",callback=go_home,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
PushButton{parent=sum_c_2,x=44,y=14,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray)}
|
||||||
|
|
||||||
|
TextBox{parent=sum_c_3,x=1,y=1,height=2,text="The old config.lua file will now be deleted, then the configurator will exit."}
|
||||||
|
|
||||||
|
local function delete_legacy()
|
||||||
|
fs.delete("/reactor-plc/config.lua")
|
||||||
|
exit()
|
||||||
|
end
|
||||||
|
|
||||||
|
PushButton{parent=sum_c_3,x=1,y=14,min_width=8,text="Cancel",callback=go_home,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
PushButton{parent=sum_c_3,x=44,y=14,min_width=6,text="OK",callback=delete_legacy,fg_bg=cpair(colors.black,colors.green),active_fg_bg=cpair(colors.white,colors.gray)}
|
||||||
|
|
||||||
|
TextBox{parent=sum_c_4,x=1,y=1,height=5,text="Failed to save the settings file.\n\nThere may not be enough space for the modification or server file permissions may be denying writes."}
|
||||||
|
PushButton{parent=sum_c_4,x=1,y=14,min_width=6,text="Home",callback=go_home,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
PushButton{parent=sum_c_4,x=44,y=14,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray)}
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
|
--#region Tool Functions
|
||||||
|
|
||||||
|
-- load a legacy config file
|
||||||
|
function tool_ctl.load_legacy()
|
||||||
|
local config = require("reactor-plc.config")
|
||||||
|
|
||||||
|
tmp_cfg.Networked = config.NETWORKED
|
||||||
|
tmp_cfg.UnitID = config.REACTOR_ID
|
||||||
|
tmp_cfg.EmerCoolEnable = type(config.EMERGENCY_COOL) == "table"
|
||||||
|
|
||||||
|
if tmp_cfg.EmerCoolEnable then
|
||||||
|
tmp_cfg.EmerCoolSide = config.EMERGENCY_COOL.side
|
||||||
|
tmp_cfg.EmerCoolColor = config.EMERGENCY_COOL.color
|
||||||
|
else
|
||||||
|
tmp_cfg.EmerCoolSide = nil
|
||||||
|
tmp_cfg.EmerCoolColor = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
tmp_cfg.SVR_Channel = config.SVR_CHANNEL
|
||||||
|
tmp_cfg.PLC_Channel = config.PLC_CHANNEL
|
||||||
|
tmp_cfg.ConnTimeout = config.COMMS_TIMEOUT
|
||||||
|
tmp_cfg.TrustedRange = config.TRUSTED_RANGE
|
||||||
|
tmp_cfg.AuthKey = config.AUTH_KEY or ""
|
||||||
|
tmp_cfg.LogMode = config.LOG_MODE
|
||||||
|
tmp_cfg.LogPath = config.LOG_PATH
|
||||||
|
tmp_cfg.LogDebug = config.LOG_DEBUG or false
|
||||||
|
|
||||||
|
tool_ctl.gen_summary(tmp_cfg)
|
||||||
|
sum_pane.set_value(1)
|
||||||
|
main_pane.set_value(6)
|
||||||
|
self.importing_legacy = true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- expose the auth key on the summary page
|
||||||
|
function self.show_auth_key()
|
||||||
|
self.show_key_btn.disable()
|
||||||
|
self.auth_key_textbox.set_value(self.auth_key_value)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- generate the summary list
|
||||||
|
---@param cfg plc_config
|
||||||
|
function tool_ctl.gen_summary(cfg)
|
||||||
|
setting_list.remove_all()
|
||||||
|
|
||||||
|
local alternate = false
|
||||||
|
local inner_width = setting_list.get_width() - 1
|
||||||
|
|
||||||
|
if cfg.AuthKey then self.show_key_btn.enable() else self.show_key_btn.disable() end
|
||||||
|
self.auth_key_value = cfg.AuthKey or "" -- to show auth key
|
||||||
|
|
||||||
|
for i = 1, #fields do
|
||||||
|
local f = fields[i]
|
||||||
|
local height = 1
|
||||||
|
local label_w = string.len(f[2])
|
||||||
|
local val_max_w = (inner_width - label_w) + 1
|
||||||
|
local raw = cfg[f[1]]
|
||||||
|
local val = util.strval(raw)
|
||||||
|
|
||||||
|
if f[1] == "AuthKey" and raw then val = string.rep("*", string.len(val))
|
||||||
|
elseif f[1] == "LogMode" then val = util.trinary(raw == log.MODE.APPEND, "append", "replace")
|
||||||
|
elseif f[1] == "EmerCoolColor" and raw ~= nil then val = rsio.color_name(raw)
|
||||||
|
elseif f[1] == "FrontPanelTheme" then
|
||||||
|
val = util.strval(themes.fp_theme_name(raw))
|
||||||
|
elseif f[1] == "ColorMode" then
|
||||||
|
val = util.strval(themes.color_mode_name(raw))
|
||||||
|
end
|
||||||
|
|
||||||
|
if val == "nil" then val = "<not set>" end
|
||||||
|
|
||||||
|
local c = util.trinary(alternate, g_lg_fg_bg, cpair(colors.gray,colors.white))
|
||||||
|
alternate = not alternate
|
||||||
|
|
||||||
|
if string.len(val) > val_max_w then
|
||||||
|
local lines = util.strwrap(val, inner_width)
|
||||||
|
height = #lines + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
local line = Div{parent=setting_list,height=height,fg_bg=c}
|
||||||
|
TextBox{parent=line,text=f[2],width=string.len(f[2]),fg_bg=cpair(colors.black,line.get_fg_bg().bkg)}
|
||||||
|
|
||||||
|
local textbox
|
||||||
|
if height > 1 then
|
||||||
|
textbox = TextBox{parent=line,x=1,y=2,text=val,height=height-1,alignment=LEFT}
|
||||||
|
else
|
||||||
|
textbox = TextBox{parent=line,x=label_w+1,y=1,text=val,alignment=RIGHT}
|
||||||
|
end
|
||||||
|
|
||||||
|
if f[1] == "AuthKey" then self.auth_key_textbox = textbox end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
end
|
||||||
|
|
||||||
|
return system
|
@ -2,44 +2,37 @@
|
|||||||
-- Configuration GUI
|
-- Configuration GUI
|
||||||
--
|
--
|
||||||
|
|
||||||
local log = require("scada-common.log")
|
local log = require("scada-common.log")
|
||||||
local rsio = require("scada-common.rsio")
|
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 core = require("graphics.core")
|
local check = require("reactor-plc.config.check")
|
||||||
local themes = require("graphics.themes")
|
local system = require("reactor-plc.config.system")
|
||||||
|
|
||||||
local DisplayBox = require("graphics.elements.displaybox")
|
local core = require("graphics.core")
|
||||||
local Div = require("graphics.elements.div")
|
local themes = require("graphics.themes")
|
||||||
local ListBox = require("graphics.elements.listbox")
|
|
||||||
local MultiPane = require("graphics.elements.multipane")
|
|
||||||
local TextBox = require("graphics.elements.textbox")
|
|
||||||
|
|
||||||
local CheckBox = require("graphics.elements.controls.checkbox")
|
local DisplayBox = require("graphics.elements.displaybox")
|
||||||
local PushButton = require("graphics.elements.controls.push_button")
|
local Div = require("graphics.elements.div")
|
||||||
local Radio2D = require("graphics.elements.controls.radio_2d")
|
local ListBox = require("graphics.elements.listbox")
|
||||||
local RadioButton = require("graphics.elements.controls.radio_button")
|
local MultiPane = require("graphics.elements.multipane")
|
||||||
|
local TextBox = require("graphics.elements.textbox")
|
||||||
|
|
||||||
local NumberField = require("graphics.elements.form.number_field")
|
local PushButton = require("graphics.elements.controls.push_button")
|
||||||
local TextField = require("graphics.elements.form.text_field")
|
|
||||||
|
|
||||||
local IndLight = require("graphics.elements.indicators.light")
|
|
||||||
|
|
||||||
local println = util.println
|
local println = util.println
|
||||||
local tri = util.trinary
|
local tri = util.trinary
|
||||||
|
|
||||||
local cpair = core.cpair
|
local cpair = core.cpair
|
||||||
|
|
||||||
local LEFT = core.ALIGN.LEFT
|
|
||||||
local CENTER = core.ALIGN.CENTER
|
local CENTER = core.ALIGN.CENTER
|
||||||
local RIGHT = core.ALIGN.RIGHT
|
|
||||||
|
|
||||||
-- 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.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" } }
|
||||||
}
|
}
|
||||||
|
|
||||||
---@class plc_configurator
|
---@class plc_configurator
|
||||||
@ -47,22 +40,22 @@ local configurator = {}
|
|||||||
|
|
||||||
local style = {}
|
local style = {}
|
||||||
|
|
||||||
style.root = cpair(colors.black, colors.lightGray)
|
style.root = cpair(colors.black, colors.lightGray)
|
||||||
style.header = cpair(colors.white, colors.gray)
|
style.header = cpair(colors.white, colors.gray)
|
||||||
|
|
||||||
style.colors = themes.smooth_stone.colors
|
style.colors = themes.smooth_stone.colors
|
||||||
|
|
||||||
local bw_fg_bg = cpair(colors.black, colors.white)
|
style.bw_fg_bg = cpair(colors.black, colors.white)
|
||||||
local g_lg_fg_bg = cpair(colors.gray, colors.lightGray)
|
style.g_lg_fg_bg = cpair(colors.gray, colors.lightGray)
|
||||||
local nav_fg_bg = bw_fg_bg
|
style.nav_fg_bg = style.bw_fg_bg
|
||||||
local btn_act_fg_bg = cpair(colors.white, colors.gray)
|
style.btn_act_fg_bg = cpair(colors.white, colors.gray)
|
||||||
|
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 = {
|
||||||
ask_config = false,
|
ask_config = false,
|
||||||
has_config = false,
|
has_config = false,
|
||||||
viewing_config = false,
|
viewing_config = false,
|
||||||
importing_legacy = false,
|
|
||||||
jumped_to_color = false,
|
jumped_to_color = false,
|
||||||
|
|
||||||
view_cfg = nil, ---@type graphics_element
|
view_cfg = nil, ---@type graphics_element
|
||||||
@ -71,16 +64,8 @@ local tool_ctl = {
|
|||||||
color_apply = nil, ---@type graphics_element
|
color_apply = nil, ---@type graphics_element
|
||||||
settings_apply = nil, ---@type graphics_element
|
settings_apply = nil, ---@type graphics_element
|
||||||
|
|
||||||
set_networked = nil, ---@type function
|
|
||||||
bundled_emcool = nil, ---@type function
|
|
||||||
gen_summary = nil, ---@type function
|
gen_summary = nil, ---@type function
|
||||||
show_current_cfg = nil, ---@type function
|
|
||||||
load_legacy = nil, ---@type function
|
load_legacy = nil, ---@type function
|
||||||
|
|
||||||
show_auth_key = nil, ---@type function
|
|
||||||
show_key_btn = nil, ---@type graphics_element
|
|
||||||
auth_key_textbox = nil, ---@type graphics_element
|
|
||||||
auth_key_value = ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
---@class plc_config
|
---@class plc_config
|
||||||
@ -126,27 +111,6 @@ local fields = {
|
|||||||
{ "ColorMode", "Color Mode", themes.COLOR_MODE.STANDARD }
|
{ "ColorMode", "Color Mode", themes.COLOR_MODE.STANDARD }
|
||||||
}
|
}
|
||||||
|
|
||||||
local side_options = { "Top", "Bottom", "Left", "Right", "Front", "Back" }
|
|
||||||
local side_options_map = { "top", "bottom", "left", "right", "front", "back" }
|
|
||||||
local color_options = { "Red", "Orange", "Yellow", "Lime", "Green", "Cyan", "Light Blue", "Blue", "Purple", "Magenta", "Pink", "White", "Light Gray", "Gray", "Black", "Brown" }
|
|
||||||
local color_options_map = { colors.red, colors.orange, colors.yellow, colors.lime, colors.green, colors.cyan, colors.lightBlue, colors.blue, colors.purple, colors.magenta, colors.pink, colors.white, colors.lightGray, colors.gray, colors.black, colors.brown }
|
|
||||||
|
|
||||||
-- convert text representation to index
|
|
||||||
---@param side string
|
|
||||||
local function side_to_idx(side)
|
|
||||||
for k, v in ipairs(side_options_map) do
|
|
||||||
if v == side then return k end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- convert color to index
|
|
||||||
---@param color color
|
|
||||||
local function color_to_idx(color)
|
|
||||||
for k, v in ipairs(color_options_map) do
|
|
||||||
if v == color then return k end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- load data from the settings file
|
-- load data from the settings file
|
||||||
---@param target plc_config
|
---@param target plc_config
|
||||||
---@param raw boolean? true to not use default values
|
---@param raw boolean? true to not use default values
|
||||||
@ -163,11 +127,16 @@ end
|
|||||||
-- create the config view
|
-- create the config view
|
||||||
---@param display graphics_element
|
---@param display graphics_element
|
||||||
local function config_view(display)
|
local function config_view(display)
|
||||||
|
local bw_fg_bg = style.bw_fg_bg
|
||||||
|
local g_lg_fg_bg = style.g_lg_fg_bg
|
||||||
|
local nav_fg_bg = style.nav_fg_bg
|
||||||
|
local btn_act_fg_bg = style.btn_act_fg_bg
|
||||||
|
local btn_dis_fg_bg = style.btn_dis_fg_bg
|
||||||
|
|
||||||
---@diagnostic disable-next-line: undefined-field
|
---@diagnostic disable-next-line: undefined-field
|
||||||
local function exit() os.queueEvent("terminate") end
|
local function exit() os.queueEvent("terminate") end
|
||||||
|
|
||||||
TextBox{parent=display,y=1,text="Reactor PLC Configurator",alignment=CENTER,height=1,fg_bg=style.header}
|
TextBox{parent=display,y=1,text="Reactor PLC Configurator",alignment=CENTER,fg_bg=style.header}
|
||||||
|
|
||||||
local root_pane_div = Div{parent=display,x=1,y=2}
|
local root_pane_div = Div{parent=display,x=1,y=2}
|
||||||
|
|
||||||
@ -178,17 +147,18 @@ local function config_view(display)
|
|||||||
local clr_cfg = Div{parent=root_pane_div,x=1,y=1}
|
local clr_cfg = Div{parent=root_pane_div,x=1,y=1}
|
||||||
local summary = Div{parent=root_pane_div,x=1,y=1}
|
local summary = Div{parent=root_pane_div,x=1,y=1}
|
||||||
local changelog = Div{parent=root_pane_div,x=1,y=1}
|
local changelog = 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,plc_cfg,net_cfg,log_cfg,clr_cfg,summary,changelog}}
|
local main_pane = MultiPane{parent=root_pane_div,x=1,y=1,panes={main_page,plc_cfg,net_cfg,log_cfg,clr_cfg,summary,changelog,check_sys}}
|
||||||
|
|
||||||
-- Main Page
|
--#region Main Page
|
||||||
|
|
||||||
local y_start = 5
|
local y_start = 5
|
||||||
|
|
||||||
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 has 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 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)}
|
||||||
y_start = y_start + 5
|
y_start = y_start + 5
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -205,7 +175,7 @@ local function config_view(display)
|
|||||||
end
|
end
|
||||||
|
|
||||||
PushButton{parent=main_page,x=2,y=y_start,min_width=18,text="Configure System",callback=function()main_pane.set_value(2)end,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=main_page,x=2,y=y_start,min_width=18,text="Configure System",callback=function()main_pane.set_value(2)end,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg}
|
||||||
tool_ctl.view_cfg = PushButton{parent=main_page,x=2,y=y_start+2,min_width=20,text="View Configuration",callback=view_config,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)}
|
tool_ctl.view_cfg = PushButton{parent=main_page,x=2,y=y_start+2,min_width=20,text="View Configuration",callback=view_config,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
||||||
|
|
||||||
local function jump_color()
|
local function jump_color()
|
||||||
tool_ctl.jumped_to_color = true
|
tool_ctl.jumped_to_color = true
|
||||||
@ -215,7 +185,8 @@ local function config_view(display)
|
|||||||
end
|
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}
|
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}
|
||||||
|
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=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=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}
|
||||||
|
|
||||||
if not tool_ctl.has_config then
|
if not tool_ctl.has_config then
|
||||||
@ -223,544 +194,43 @@ local function config_view(display)
|
|||||||
tool_ctl.color_cfg.disable()
|
tool_ctl.color_cfg.disable()
|
||||||
end
|
end
|
||||||
|
|
||||||
--#region PLC
|
--#endregion
|
||||||
|
|
||||||
local plc_c_1 = Div{parent=plc_cfg,x=2,y=4,width=49}
|
--#region System Configuration
|
||||||
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_4 = 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 settings = { settings_cfg, ini_cfg, tmp_cfg, fields, load_settings }
|
||||||
|
local divs = { plc_cfg, net_cfg, log_cfg, clr_cfg, summary }
|
||||||
|
|
||||||
TextBox{parent=plc_cfg,x=1,y=2,height=1,text=" PLC Configuration",fg_bg=cpair(colors.black,colors.orange)}
|
system.create(tool_ctl, main_pane, settings, divs, style, exit)
|
||||||
|
|
||||||
TextBox{parent=plc_c_1,x=1,y=1,height=1,text="Would you like to set this PLC as networked?"}
|
|
||||||
TextBox{parent=plc_c_1,x=1,y=3,height=4,text="If you have a supervisor, select the box. You will later be prompted to select the network configuration. If you instead want to use this as a standalone safety system, don't select the box.",fg_bg=g_lg_fg_bg}
|
|
||||||
|
|
||||||
local networked = CheckBox{parent=plc_c_1,x=1,y=8,label="Networked",default=ini_cfg.Networked,box_fg_bg=cpair(colors.orange,colors.black)}
|
|
||||||
|
|
||||||
local function submit_networked()
|
|
||||||
tool_ctl.set_networked(networked.get_value())
|
|
||||||
plc_pane.set_value(2)
|
|
||||||
end
|
|
||||||
|
|
||||||
PushButton{parent=plc_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=plc_c_1,x=44,y=14,text="Next \x1a",callback=submit_networked,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
|
||||||
|
|
||||||
TextBox{parent=plc_c_2,x=1,y=1,height=1,text="Please enter the reactor unit ID for this PLC."}
|
|
||||||
TextBox{parent=plc_c_2,x=1,y=3,height=3,text="If this is a networked PLC, currently only IDs 1 through 4 are acceptable.",fg_bg=g_lg_fg_bg}
|
|
||||||
|
|
||||||
TextBox{parent=plc_c_2,x=1,y=6,height=1,text="Unit #"}
|
|
||||||
local u_id = NumberField{parent=plc_c_2,x=7,y=6,width=5,max_chars=3,default=ini_cfg.UnitID,min=1,fg_bg=bw_fg_bg}
|
|
||||||
|
|
||||||
local u_id_err = TextBox{parent=plc_c_2,x=8,y=14,height=1,width=35,text="Please set a unit ID.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
|
||||||
|
|
||||||
local function submit_id()
|
|
||||||
local unit_id = tonumber(u_id.get_value())
|
|
||||||
if unit_id ~= nil then
|
|
||||||
u_id_err.hide(true)
|
|
||||||
tmp_cfg.UnitID = unit_id
|
|
||||||
plc_pane.set_value(3)
|
|
||||||
else u_id_err.show() end
|
|
||||||
end
|
|
||||||
|
|
||||||
PushButton{parent=plc_c_2,x=1,y=14,text="\x1b Back",callback=function()plc_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
|
||||||
PushButton{parent=plc_c_2,x=44,y=14,text="Next \x1a",callback=submit_id,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
|
||||||
|
|
||||||
TextBox{parent=plc_c_3,x=1,y=1,height=4,text="When networked, the supervisor takes care of emergency coolant via RTUs. However, you can configure independent emergency coolant via the PLC."}
|
|
||||||
TextBox{parent=plc_c_3,x=1,y=6,height=5,text="This independent control can be used with or without a supervisor. To configure, you would next select the interface of the redstone output connected to one or more mekanism pipes.",fg_bg=g_lg_fg_bg}
|
|
||||||
|
|
||||||
local en_em_cool = CheckBox{parent=plc_c_3,x=1,y=11,label="Enable PLC Emergency Coolant Control",default=ini_cfg.EmerCoolEnable,box_fg_bg=cpair(colors.orange,colors.black)}
|
|
||||||
|
|
||||||
local function next_from_plc()
|
|
||||||
if tmp_cfg.Networked then main_pane.set_value(3) else main_pane.set_value(4) end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function submit_en_emcool()
|
|
||||||
tmp_cfg.EmerCoolEnable = en_em_cool.get_value()
|
|
||||||
if tmp_cfg.EmerCoolEnable then plc_pane.set_value(4) else next_from_plc() end
|
|
||||||
end
|
|
||||||
|
|
||||||
PushButton{parent=plc_c_3,x=1,y=14,text="\x1b Back",callback=function()plc_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
|
||||||
PushButton{parent=plc_c_3,x=44,y=14,text="Next \x1a",callback=submit_en_emcool,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
|
||||||
|
|
||||||
TextBox{parent=plc_c_4,x=1,y=1,height=1,text="Emergency Coolant Redstone Output Side"}
|
|
||||||
local side = Radio2D{parent=plc_c_4,x=1,y=2,rows=2,columns=3,default=side_to_idx(ini_cfg.EmerCoolSide),options=side_options,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.orange}
|
|
||||||
|
|
||||||
TextBox{parent=plc_c_4,x=1,y=5,height=1,text="Bundled Redstone Configuration"}
|
|
||||||
local bundled = CheckBox{parent=plc_c_4,x=1,y=6,label="Is Bundled?",default=ini_cfg.EmerCoolColor~=nil,box_fg_bg=cpair(colors.orange,colors.black),callback=function(v)tool_ctl.bundled_emcool(v)end}
|
|
||||||
local color = Radio2D{parent=plc_c_4,x=1,y=8,rows=4,columns=4,default=color_to_idx(ini_cfg.EmerCoolColor),options=color_options,radio_colors=cpair(colors.lightGray,colors.black),color_map=color_options_map,disable_color=colors.gray,disable_fg_bg=g_lg_fg_bg}
|
|
||||||
if ini_cfg.EmerCoolColor == nil then color.disable() end
|
|
||||||
|
|
||||||
local function submit_emcool()
|
|
||||||
tmp_cfg.EmerCoolSide = side_options_map[side.get_value()]
|
|
||||||
tmp_cfg.EmerCoolColor = util.trinary(bundled.get_value(), color_options_map[color.get_value()], nil)
|
|
||||||
next_from_plc()
|
|
||||||
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=44,y=14,text="Next \x1a",callback=submit_emcool,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
|
||||||
|
|
||||||
--#endregion
|
--#endregion
|
||||||
|
|
||||||
--#region Network
|
--#region Config Change Log
|
||||||
|
|
||||||
local net_c_1 = Div{parent=net_cfg,x=2,y=4,width=49}
|
|
||||||
local net_c_2 = Div{parent=net_cfg,x=2,y=4,width=49}
|
|
||||||
local net_c_3 = Div{parent=net_cfg,x=2,y=4,width=49}
|
|
||||||
|
|
||||||
local net_pane = MultiPane{parent=net_cfg,x=1,y=4,panes={net_c_1,net_c_2,net_c_3}}
|
|
||||||
|
|
||||||
TextBox{parent=net_cfg,x=1,y=2,height=1,text=" Network Configuration",fg_bg=cpair(colors.black,colors.lightBlue)}
|
|
||||||
|
|
||||||
TextBox{parent=net_c_1,x=1,y=1,height=1,text="Please set the network channels below."}
|
|
||||||
TextBox{parent=net_c_1,x=1,y=3,height=4,text="Each of the 5 uniquely named channels, including the 2 below, must be the same for each device in this SCADA network. For multiplayer servers, it is recommended to not use the default channels.",fg_bg=g_lg_fg_bg}
|
|
||||||
|
|
||||||
TextBox{parent=net_c_1,x=1,y=8,height=1,text="Supervisor Channel"}
|
|
||||||
local svr_chan = NumberField{parent=net_c_1,x=1,y=9,width=7,default=ini_cfg.SVR_Channel,min=1,max=65535,fg_bg=bw_fg_bg}
|
|
||||||
TextBox{parent=net_c_1,x=9,y=9,height=4,text="[SVR_CHANNEL]",fg_bg=g_lg_fg_bg}
|
|
||||||
TextBox{parent=net_c_1,x=1,y=11,height=1,text="PLC Channel"}
|
|
||||||
local plc_chan = NumberField{parent=net_c_1,x=1,y=12,width=7,default=ini_cfg.PLC_Channel,min=1,max=65535,fg_bg=bw_fg_bg}
|
|
||||||
TextBox{parent=net_c_1,x=9,y=12,height=4,text="[PLC_CHANNEL]",fg_bg=g_lg_fg_bg}
|
|
||||||
|
|
||||||
local chan_err = TextBox{parent=net_c_1,x=8,y=14,height=1,width=35,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
|
||||||
|
|
||||||
local function submit_channels()
|
|
||||||
local svr_c = tonumber(svr_chan.get_value())
|
|
||||||
local plc_c = tonumber(plc_chan.get_value())
|
|
||||||
if svr_c ~= nil and plc_c ~= nil then
|
|
||||||
tmp_cfg.SVR_Channel = svr_c
|
|
||||||
tmp_cfg.PLC_Channel = plc_c
|
|
||||||
net_pane.set_value(2)
|
|
||||||
chan_err.hide(true)
|
|
||||||
elseif svr_c == nil then
|
|
||||||
chan_err.set_value("Please set the supervisor channel.")
|
|
||||||
chan_err.show()
|
|
||||||
else
|
|
||||||
chan_err.set_value("Please set the PLC channel.")
|
|
||||||
chan_err.show()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
PushButton{parent=net_c_1,x=1,y=14,text="\x1b Back",callback=function()main_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
|
||||||
PushButton{parent=net_c_1,x=44,y=14,text="Next \x1a",callback=submit_channels,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
|
||||||
|
|
||||||
TextBox{parent=net_c_2,x=1,y=1,height=1,text="Connection Timeout"}
|
|
||||||
local timeout = NumberField{parent=net_c_2,x=1,y=2,width=7,default=ini_cfg.ConnTimeout,min=2,max=25,max_chars=6,max_frac_digits=2,allow_decimal=true,fg_bg=bw_fg_bg}
|
|
||||||
TextBox{parent=net_c_2,x=9,y=2,height=2,text="seconds (default 5)",fg_bg=g_lg_fg_bg}
|
|
||||||
TextBox{parent=net_c_2,x=1,y=3,height=4,text="You generally do not want or need to modify this. On slow servers, you can increase this to make the system wait longer before assuming a disconnection.",fg_bg=g_lg_fg_bg}
|
|
||||||
|
|
||||||
TextBox{parent=net_c_2,x=1,y=8,height=1,text="Trusted Range"}
|
|
||||||
local range = NumberField{parent=net_c_2,x=1,y=9,width=10,default=ini_cfg.TrustedRange,min=0,max_chars=20,allow_decimal=true,fg_bg=bw_fg_bg}
|
|
||||||
TextBox{parent=net_c_2,x=1,y=10,height=4,text="Setting this to a value larger than 0 prevents connections with devices that many meters (blocks) away in any direction.",fg_bg=g_lg_fg_bg}
|
|
||||||
|
|
||||||
local p2_err = TextBox{parent=net_c_2,x=8,y=14,height=1,width=35,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
|
||||||
|
|
||||||
local function submit_ct_tr()
|
|
||||||
local timeout_val = tonumber(timeout.get_value())
|
|
||||||
local range_val = tonumber(range.get_value())
|
|
||||||
if timeout_val ~= nil and range_val ~= nil then
|
|
||||||
tmp_cfg.ConnTimeout = timeout_val
|
|
||||||
tmp_cfg.TrustedRange = range_val
|
|
||||||
net_pane.set_value(3)
|
|
||||||
p2_err.hide(true)
|
|
||||||
elseif timeout_val == nil then
|
|
||||||
p2_err.set_value("Please set the connection timeout.")
|
|
||||||
p2_err.show()
|
|
||||||
else
|
|
||||||
p2_err.set_value("Please set the trusted range.")
|
|
||||||
p2_err.show()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
PushButton{parent=net_c_2,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
|
||||||
PushButton{parent=net_c_2,x=44,y=14,text="Next \x1a",callback=submit_ct_tr,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
|
||||||
|
|
||||||
TextBox{parent=net_c_3,x=1,y=1,height=2,text="Optionally, set the facility authentication key below. Do NOT use one of your passwords."}
|
|
||||||
TextBox{parent=net_c_3,x=1,y=4,height=6,text="This enables verifying that messages are authentic, so it is intended for security on multiplayer servers. All devices on the same network MUST use the same key if any device has a key. This does result in some extra compution (can slow things down).",fg_bg=g_lg_fg_bg}
|
|
||||||
|
|
||||||
TextBox{parent=net_c_3,x=1,y=11,height=1,text="Facility Auth Key"}
|
|
||||||
local key, _, censor = TextField{parent=net_c_3,x=1,y=12,max_len=64,value=ini_cfg.AuthKey,width=32,height=1,fg_bg=bw_fg_bg}
|
|
||||||
|
|
||||||
local function censor_key(enable) censor(util.trinary(enable, "*", nil)) end
|
|
||||||
|
|
||||||
local hide_key = CheckBox{parent=net_c_3,x=34,y=12,label="Hide",box_fg_bg=cpair(colors.lightBlue,colors.black),callback=censor_key}
|
|
||||||
|
|
||||||
hide_key.set_value(true)
|
|
||||||
censor_key(true)
|
|
||||||
|
|
||||||
local key_err = TextBox{parent=net_c_3,x=8,y=14,height=1,width=35,text="Key must be at least 8 characters.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
|
||||||
|
|
||||||
local function submit_auth()
|
|
||||||
local v = key.get_value()
|
|
||||||
if string.len(v) == 0 or string.len(v) >= 8 then
|
|
||||||
tmp_cfg.AuthKey = key.get_value()
|
|
||||||
main_pane.set_value(4)
|
|
||||||
key_err.hide(true)
|
|
||||||
else key_err.show() end
|
|
||||||
end
|
|
||||||
|
|
||||||
PushButton{parent=net_c_3,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
|
||||||
PushButton{parent=net_c_3,x=44,y=14,text="Next \x1a",callback=submit_auth,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
|
||||||
|
|
||||||
--#endregion
|
|
||||||
|
|
||||||
--#region Logging
|
|
||||||
|
|
||||||
local log_c_1 = Div{parent=log_cfg,x=2,y=4,width=49}
|
|
||||||
|
|
||||||
TextBox{parent=log_cfg,x=1,y=2,height=1,text=" Logging Configuration",fg_bg=cpair(colors.black,colors.pink)}
|
|
||||||
|
|
||||||
TextBox{parent=log_c_1,x=1,y=1,height=1,text="Please configure logging below."}
|
|
||||||
|
|
||||||
TextBox{parent=log_c_1,x=1,y=3,height=1,text="Log File Mode"}
|
|
||||||
local mode = RadioButton{parent=log_c_1,x=1,y=4,default=ini_cfg.LogMode+1,options={"Append on Startup","Replace on Startup"},callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.pink}
|
|
||||||
|
|
||||||
TextBox{parent=log_c_1,x=1,y=7,height=1,text="Log File Path"}
|
|
||||||
local path = TextField{parent=log_c_1,x=1,y=8,width=49,height=1,value=ini_cfg.LogPath,max_len=128,fg_bg=bw_fg_bg}
|
|
||||||
|
|
||||||
local en_dbg = CheckBox{parent=log_c_1,x=1,y=10,default=ini_cfg.LogDebug,label="Enable Logging Debug Messages",box_fg_bg=cpair(colors.pink,colors.black)}
|
|
||||||
TextBox{parent=log_c_1,x=3,y=11,height=2,text="This results in much larger log files. It is best to only use this when there is a problem.",fg_bg=g_lg_fg_bg}
|
|
||||||
|
|
||||||
local path_err = TextBox{parent=log_c_1,x=8,y=14,height=1,width=35,text="Please provide a log file path.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
|
||||||
|
|
||||||
local function submit_log()
|
|
||||||
if path.get_value() ~= "" then
|
|
||||||
path_err.hide(true)
|
|
||||||
tmp_cfg.LogMode = mode.get_value() - 1
|
|
||||||
tmp_cfg.LogPath = path.get_value()
|
|
||||||
tmp_cfg.LogDebug = en_dbg.get_value()
|
|
||||||
tool_ctl.color_apply.hide(true)
|
|
||||||
tool_ctl.color_next.show()
|
|
||||||
main_pane.set_value(5)
|
|
||||||
else path_err.show() end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function back_from_log()
|
|
||||||
if tmp_cfg.Networked then main_pane.set_value(3) else main_pane.set_value(2) end
|
|
||||||
end
|
|
||||||
|
|
||||||
PushButton{parent=log_c_1,x=1,y=14,text="\x1b Back",callback=back_from_log,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
|
||||||
PushButton{parent=log_c_1,x=44,y=14,text="Next \x1a",callback=submit_log,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
|
||||||
|
|
||||||
--#endregion
|
|
||||||
|
|
||||||
--#region Color Options
|
|
||||||
|
|
||||||
local clr_c_1 = Div{parent=clr_cfg,x=2,y=4,width=49}
|
|
||||||
local clr_c_2 = Div{parent=clr_cfg,x=2,y=4,width=49}
|
|
||||||
local clr_c_3 = Div{parent=clr_cfg,x=2,y=4,width=49}
|
|
||||||
local clr_c_4 = Div{parent=clr_cfg,x=2,y=4,width=49}
|
|
||||||
|
|
||||||
local clr_pane = MultiPane{parent=clr_cfg,x=1,y=4,panes={clr_c_1,clr_c_2,clr_c_3,clr_c_4}}
|
|
||||||
|
|
||||||
TextBox{parent=clr_cfg,x=1,y=2,height=1,text=" Color Configuration",fg_bg=cpair(colors.black,colors.magenta)}
|
|
||||||
|
|
||||||
TextBox{parent=clr_c_1,x=1,y=1,height=2,text="Here you can select the color theme for the front panel."}
|
|
||||||
TextBox{parent=clr_c_1,x=1,y=4,height=2,text="Click 'Accessibility' below to access colorblind assistive options.",fg_bg=g_lg_fg_bg}
|
|
||||||
|
|
||||||
TextBox{parent=clr_c_1,x=1,y=7,height=1,text="Front Panel Theme"}
|
|
||||||
local fp_theme = RadioButton{parent=clr_c_1,x=1,y=8,default=ini_cfg.FrontPanelTheme,options=themes.FP_THEME_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.magenta}
|
|
||||||
|
|
||||||
TextBox{parent=clr_c_2,x=1,y=1,height=6,text="By default, this project uses green/red heavily to distinguish ok and not, with some indicators also using multiple colors. By selecting a color blindness below, blues will be used instead of greens on indicators and multi-color indicators will be split up as space permits."}
|
|
||||||
|
|
||||||
local function recolor(value)
|
|
||||||
local c = themes.smooth_stone.color_modes[value]
|
|
||||||
|
|
||||||
if value == 1 then
|
|
||||||
for i = 1, #style.colors do term.setPaletteColor(style.colors[i].c, style.colors[i].hex) end
|
|
||||||
else
|
|
||||||
term.setPaletteColor(colors.green, c[1].hex)
|
|
||||||
term.setPaletteColor(colors.yellow, c[2].hex)
|
|
||||||
term.setPaletteColor(colors.red, c[3].hex)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
TextBox{parent=clr_c_2,x=1,y=8,height=1,text="Color Mode"}
|
|
||||||
local c_mode = RadioButton{parent=clr_c_2,x=1,y=9,default=ini_cfg.ColorMode,options=themes.COLOR_MODE_NAMES,callback=recolor,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.magenta}
|
|
||||||
|
|
||||||
TextBox{parent=clr_c_2,x=20,y=8,height=1,text="Preview"}
|
|
||||||
local _ = IndLight{parent=clr_c_2,x=20,y=9,label="Good",colors=cpair(colors.black,colors.green)}
|
|
||||||
_ = IndLight{parent=clr_c_2,x=20,y=10,label="Warning",colors=cpair(colors.black,colors.yellow)}
|
|
||||||
_ = IndLight{parent=clr_c_2,x=20,y=11,label="Bad",colors=cpair(colors.black,colors.red)}
|
|
||||||
|
|
||||||
TextBox{parent=clr_c_2,x=1,y=14,height=6,text="Note: exact color varies by theme.",fg_bg=g_lg_fg_bg}
|
|
||||||
|
|
||||||
PushButton{parent=clr_c_2,x=44,y=14,min_width=6,text="Done",callback=function()clr_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
|
||||||
|
|
||||||
local function back_from_colors()
|
|
||||||
main_pane.set_value(util.trinary(tool_ctl.jumped_to_color, 1, 4))
|
|
||||||
tool_ctl.jumped_to_color = false
|
|
||||||
recolor(1)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function show_access()
|
|
||||||
clr_pane.set_value(2)
|
|
||||||
recolor(c_mode.get_value())
|
|
||||||
end
|
|
||||||
|
|
||||||
local function submit_colors()
|
|
||||||
tmp_cfg.FrontPanelTheme = fp_theme.get_value()
|
|
||||||
tmp_cfg.ColorMode = c_mode.get_value()
|
|
||||||
|
|
||||||
if tool_ctl.jumped_to_color then
|
|
||||||
settings.set("FrontPanelTheme", tmp_cfg.FrontPanelTheme)
|
|
||||||
settings.set("ColorMode", tmp_cfg.ColorMode)
|
|
||||||
|
|
||||||
if settings.save("/reactor-plc.settings") then
|
|
||||||
load_settings(settings_cfg, true)
|
|
||||||
load_settings(ini_cfg)
|
|
||||||
clr_pane.set_value(3)
|
|
||||||
else
|
|
||||||
clr_pane.set_value(4)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
tool_ctl.gen_summary(tmp_cfg)
|
|
||||||
tool_ctl.viewing_config = false
|
|
||||||
tool_ctl.importing_legacy = false
|
|
||||||
tool_ctl.settings_apply.show()
|
|
||||||
main_pane.set_value(6)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
PushButton{parent=clr_c_1,x=1,y=14,text="\x1b Back",callback=back_from_colors,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
|
||||||
PushButton{parent=clr_c_1,x=8,y=14,min_width=15,text="Accessibility",callback=show_access,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
|
||||||
tool_ctl.color_next = PushButton{parent=clr_c_1,x=44,y=14,text="Next \x1a",callback=submit_colors,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
|
||||||
tool_ctl.color_apply = PushButton{parent=clr_c_1,x=43,y=14,min_width=7,text="Apply",callback=submit_colors,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg}
|
|
||||||
|
|
||||||
tool_ctl.color_apply.hide(true)
|
|
||||||
|
|
||||||
local function c_go_home()
|
|
||||||
main_pane.set_value(1)
|
|
||||||
clr_pane.set_value(1)
|
|
||||||
end
|
|
||||||
|
|
||||||
TextBox{parent=clr_c_3,x=1,y=1,height=1,text="Settings saved!"}
|
|
||||||
PushButton{parent=clr_c_3,x=1,y=14,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray)}
|
|
||||||
PushButton{parent=clr_c_3,x=44,y=14,min_width=6,text="Home",callback=c_go_home,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
|
||||||
|
|
||||||
TextBox{parent=clr_c_4,x=1,y=1,height=5,text="Failed to save the settings file.\n\nThere may not be enough space for the modification or server file permissions may be denying writes."}
|
|
||||||
PushButton{parent=clr_c_4,x=1,y=14,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray)}
|
|
||||||
PushButton{parent=clr_c_4,x=44,y=14,min_width=6,text="Home",callback=c_go_home,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
|
||||||
|
|
||||||
--#endregion
|
|
||||||
|
|
||||||
--#region Summary and Saving
|
|
||||||
|
|
||||||
local sum_c_1 = Div{parent=summary,x=2,y=4,width=49}
|
|
||||||
local sum_c_2 = Div{parent=summary,x=2,y=4,width=49}
|
|
||||||
local sum_c_3 = Div{parent=summary,x=2,y=4,width=49}
|
|
||||||
local sum_c_4 = Div{parent=summary,x=2,y=4,width=49}
|
|
||||||
|
|
||||||
local sum_pane = MultiPane{parent=summary,x=1,y=4,panes={sum_c_1,sum_c_2,sum_c_3,sum_c_4}}
|
|
||||||
|
|
||||||
TextBox{parent=summary,x=1,y=2,height=1,text=" Summary",fg_bg=cpair(colors.black,colors.green)}
|
|
||||||
|
|
||||||
local setting_list = ListBox{parent=sum_c_1,x=1,y=1,height=12,width=49,scroll_height=100,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
|
|
||||||
|
|
||||||
local function back_from_settings()
|
|
||||||
if tool_ctl.viewing_config or tool_ctl.importing_legacy then
|
|
||||||
main_pane.set_value(1)
|
|
||||||
tool_ctl.viewing_config = false
|
|
||||||
tool_ctl.importing_legacy = false
|
|
||||||
tool_ctl.settings_apply.show()
|
|
||||||
else
|
|
||||||
main_pane.set_value(5)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param element graphics_element
|
|
||||||
---@param data any
|
|
||||||
local function try_set(element, data)
|
|
||||||
if data ~= nil then element.set_value(data) end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function save_and_continue()
|
|
||||||
for k, v in pairs(tmp_cfg) do settings.set(k, v) end
|
|
||||||
|
|
||||||
if settings.save("/reactor-plc.settings") then
|
|
||||||
load_settings(settings_cfg, true)
|
|
||||||
load_settings(ini_cfg)
|
|
||||||
|
|
||||||
try_set(networked, ini_cfg.Networked)
|
|
||||||
try_set(u_id, ini_cfg.UnitID)
|
|
||||||
try_set(en_em_cool, ini_cfg.EmerCoolEnable)
|
|
||||||
try_set(side, side_to_idx(ini_cfg.EmerCoolSide))
|
|
||||||
try_set(bundled, ini_cfg.EmerCoolColor ~= nil)
|
|
||||||
if ini_cfg.EmerCoolColor ~= nil then try_set(color, color_to_idx(ini_cfg.EmerCoolColor)) end
|
|
||||||
try_set(svr_chan, ini_cfg.SVR_Channel)
|
|
||||||
try_set(plc_chan, ini_cfg.PLC_Channel)
|
|
||||||
try_set(timeout, ini_cfg.ConnTimeout)
|
|
||||||
try_set(range, ini_cfg.TrustedRange)
|
|
||||||
try_set(key, ini_cfg.AuthKey)
|
|
||||||
try_set(mode, ini_cfg.LogMode)
|
|
||||||
try_set(path, ini_cfg.LogPath)
|
|
||||||
try_set(en_dbg, ini_cfg.LogDebug)
|
|
||||||
try_set(fp_theme, ini_cfg.FrontPanelTheme)
|
|
||||||
try_set(c_mode, ini_cfg.ColorMode)
|
|
||||||
|
|
||||||
tool_ctl.view_cfg.enable()
|
|
||||||
|
|
||||||
if tool_ctl.importing_legacy then
|
|
||||||
tool_ctl.importing_legacy = false
|
|
||||||
sum_pane.set_value(3)
|
|
||||||
else
|
|
||||||
sum_pane.set_value(2)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
sum_pane.set_value(4)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
PushButton{parent=sum_c_1,x=1,y=14,text="\x1b Back",callback=back_from_settings,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
|
||||||
tool_ctl.show_key_btn = PushButton{parent=sum_c_1,x=8,y=14,min_width=17,text="Unhide Auth Key",callback=function()tool_ctl.show_auth_key()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)}
|
|
||||||
tool_ctl.settings_apply = PushButton{parent=sum_c_1,x=43,y=14,min_width=7,text="Apply",callback=save_and_continue,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg}
|
|
||||||
|
|
||||||
TextBox{parent=sum_c_2,x=1,y=1,height=1,text="Settings saved!"}
|
|
||||||
|
|
||||||
local function go_home()
|
|
||||||
main_pane.set_value(1)
|
|
||||||
plc_pane.set_value(1)
|
|
||||||
net_pane.set_value(1)
|
|
||||||
clr_pane.set_value(1)
|
|
||||||
sum_pane.set_value(1)
|
|
||||||
end
|
|
||||||
|
|
||||||
PushButton{parent=sum_c_2,x=1,y=14,min_width=6,text="Home",callback=go_home,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
|
||||||
PushButton{parent=sum_c_2,x=44,y=14,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray)}
|
|
||||||
|
|
||||||
TextBox{parent=sum_c_3,x=1,y=1,height=2,text="The old config.lua file will now be deleted, then the configurator will exit."}
|
|
||||||
|
|
||||||
local function delete_legacy()
|
|
||||||
fs.delete("/reactor-plc/config.lua")
|
|
||||||
exit()
|
|
||||||
end
|
|
||||||
|
|
||||||
PushButton{parent=sum_c_3,x=1,y=14,min_width=8,text="Cancel",callback=go_home,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
|
||||||
PushButton{parent=sum_c_3,x=44,y=14,min_width=6,text="OK",callback=delete_legacy,fg_bg=cpair(colors.black,colors.green),active_fg_bg=cpair(colors.white,colors.gray)}
|
|
||||||
|
|
||||||
TextBox{parent=sum_c_4,x=1,y=1,height=5,text="Failed to save the settings file.\n\nThere may not be enough space for the modification or server file permissions may be denying writes."}
|
|
||||||
PushButton{parent=sum_c_4,x=1,y=14,min_width=6,text="Home",callback=go_home,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
|
||||||
PushButton{parent=sum_c_4,x=44,y=14,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray)}
|
|
||||||
|
|
||||||
--#endregion
|
|
||||||
|
|
||||||
-- Config Change Log
|
|
||||||
|
|
||||||
local cl = Div{parent=changelog,x=2,y=4,width=49}
|
local cl = Div{parent=changelog,x=2,y=4,width=49}
|
||||||
|
|
||||||
TextBox{parent=changelog,x=1,y=2,height=1,text=" Config Change Log",fg_bg=bw_fg_bg}
|
TextBox{parent=changelog,x=1,y=2,text=" Config Change Log",fg_bg=bw_fg_bg}
|
||||||
|
|
||||||
local c_log = ListBox{parent=cl,x=1,y=1,height=12,width=49,scroll_height=100,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
|
local c_log = ListBox{parent=cl,x=1,y=1,height=12,width=49,scroll_height=100,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
|
||||||
|
|
||||||
for _, change in ipairs(changes) do
|
for _, change in ipairs(changes) do
|
||||||
TextBox{parent=c_log,text=change[1],height=1,fg_bg=bw_fg_bg}
|
TextBox{parent=c_log,text=change[1],fg_bg=bw_fg_bg}
|
||||||
for _, v in ipairs(change[2]) do
|
for _, v in ipairs(change[2]) do
|
||||||
local e = Div{parent=c_log,height=#util.strwrap(v,46)}
|
local e = Div{parent=c_log,height=#util.strwrap(v,46)}
|
||||||
TextBox{parent=e,y=1,x=1,text="- ",height=1,fg_bg=cpair(colors.gray,colors.white)}
|
TextBox{parent=e,y=1,x=1,text="- ",fg_bg=cpair(colors.gray,colors.white)}
|
||||||
TextBox{parent=e,y=1,x=3,text=v,height=e.get_height(),fg_bg=cpair(colors.gray,colors.white)}
|
TextBox{parent=e,y=1,x=3,text=v,height=e.get_height(),fg_bg=cpair(colors.gray,colors.white)}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
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}
|
||||||
|
|
||||||
-- set tool functions now that we have the elements
|
--#endregion
|
||||||
|
|
||||||
function tool_ctl.set_networked(enable)
|
--#region Self-Check
|
||||||
tmp_cfg.Networked = enable
|
|
||||||
if enable then u_id.set_max(4) else u_id.set_max(999) end
|
|
||||||
end
|
|
||||||
|
|
||||||
function tool_ctl.bundled_emcool(en) if en then color.enable() else color.disable() end end
|
check.create(main_pane, settings_cfg, check_sys, style)
|
||||||
|
|
||||||
-- load a legacy config file
|
--#endregion
|
||||||
function tool_ctl.load_legacy()
|
|
||||||
local config = require("reactor-plc.config")
|
|
||||||
|
|
||||||
tmp_cfg.Networked = config.NETWORKED
|
|
||||||
tmp_cfg.UnitID = config.REACTOR_ID
|
|
||||||
tmp_cfg.EmerCoolEnable = type(config.EMERGENCY_COOL) == "table"
|
|
||||||
|
|
||||||
if tmp_cfg.EmerCoolEnable then
|
|
||||||
tmp_cfg.EmerCoolSide = config.EMERGENCY_COOL.side
|
|
||||||
tmp_cfg.EmerCoolColor = config.EMERGENCY_COOL.color
|
|
||||||
else
|
|
||||||
tmp_cfg.EmerCoolSide = nil
|
|
||||||
tmp_cfg.EmerCoolColor = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
tmp_cfg.SVR_Channel = config.SVR_CHANNEL
|
|
||||||
tmp_cfg.PLC_Channel = config.PLC_CHANNEL
|
|
||||||
tmp_cfg.ConnTimeout = config.COMMS_TIMEOUT
|
|
||||||
tmp_cfg.TrustedRange = config.TRUSTED_RANGE
|
|
||||||
tmp_cfg.AuthKey = config.AUTH_KEY or ""
|
|
||||||
tmp_cfg.LogMode = config.LOG_MODE
|
|
||||||
tmp_cfg.LogPath = config.LOG_PATH
|
|
||||||
tmp_cfg.LogDebug = config.LOG_DEBUG or false
|
|
||||||
|
|
||||||
tool_ctl.gen_summary(tmp_cfg)
|
|
||||||
sum_pane.set_value(1)
|
|
||||||
main_pane.set_value(6)
|
|
||||||
tool_ctl.importing_legacy = true
|
|
||||||
end
|
|
||||||
|
|
||||||
-- expose the auth key on the summary page
|
|
||||||
function tool_ctl.show_auth_key()
|
|
||||||
tool_ctl.show_key_btn.disable()
|
|
||||||
tool_ctl.auth_key_textbox.set_value(tool_ctl.auth_key_value)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- generate the summary list
|
|
||||||
---@param cfg plc_config
|
|
||||||
function tool_ctl.gen_summary(cfg)
|
|
||||||
setting_list.remove_all()
|
|
||||||
|
|
||||||
local alternate = false
|
|
||||||
local inner_width = setting_list.get_width() - 1
|
|
||||||
|
|
||||||
tool_ctl.show_key_btn.enable()
|
|
||||||
tool_ctl.auth_key_value = cfg.AuthKey or "" -- to show auth key
|
|
||||||
|
|
||||||
for i = 1, #fields do
|
|
||||||
local f = fields[i]
|
|
||||||
local height = 1
|
|
||||||
local label_w = string.len(f[2])
|
|
||||||
local val_max_w = (inner_width - label_w) + 1
|
|
||||||
local raw = cfg[f[1]]
|
|
||||||
local val = util.strval(raw)
|
|
||||||
|
|
||||||
if f[1] == "AuthKey" then val = string.rep("*", string.len(val))
|
|
||||||
elseif f[1] == "LogMode" then val = util.trinary(raw == log.MODE.APPEND, "append", "replace")
|
|
||||||
elseif f[1] == "EmerCoolColor" and raw ~= nil then val = rsio.color_name(raw)
|
|
||||||
elseif f[1] == "FrontPanelTheme" then
|
|
||||||
val = util.strval(themes.fp_theme_name(raw))
|
|
||||||
elseif f[1] == "ColorMode" then
|
|
||||||
val = util.strval(themes.color_mode_name(raw))
|
|
||||||
end
|
|
||||||
|
|
||||||
if val == "nil" then val = "<not set>" end
|
|
||||||
|
|
||||||
local c = util.trinary(alternate, g_lg_fg_bg, cpair(colors.gray,colors.white))
|
|
||||||
alternate = not alternate
|
|
||||||
|
|
||||||
if string.len(val) > val_max_w then
|
|
||||||
local lines = util.strwrap(val, inner_width)
|
|
||||||
height = #lines + 1
|
|
||||||
end
|
|
||||||
|
|
||||||
local line = Div{parent=setting_list,height=height,fg_bg=c}
|
|
||||||
TextBox{parent=line,text=f[2],width=string.len(f[2]),fg_bg=cpair(colors.black,line.get_fg_bg().bkg)}
|
|
||||||
|
|
||||||
local textbox
|
|
||||||
if height > 1 then
|
|
||||||
textbox = TextBox{parent=line,x=1,y=2,text=val,height=height-1,alignment=LEFT}
|
|
||||||
else
|
|
||||||
textbox = TextBox{parent=line,x=label_w+1,y=1,text=val,alignment=RIGHT}
|
|
||||||
end
|
|
||||||
|
|
||||||
if f[1] == "AuthKey" then tool_ctl.auth_key_textbox = textbox end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- reset terminal screen
|
-- reset terminal screen
|
||||||
@ -791,7 +261,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
|
||||||
@ -804,6 +274,8 @@ 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)
|
||||||
end
|
end
|
||||||
|
|
||||||
if event == "terminate" then return end
|
if event == "terminate" then return end
|
||||||
|
@ -40,7 +40,7 @@ local function init(panel)
|
|||||||
|
|
||||||
local disabled_fg = style.fp.disabled_fg
|
local disabled_fg = style.fp.disabled_fg
|
||||||
|
|
||||||
local header = TextBox{parent=panel,y=1,text="FISSION REACTOR PLC - UNIT ?",alignment=ALIGN.CENTER,height=1,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,12 +60,12 @@ 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,colors.gray}}
|
local network = RGBLED{parent=system,label="NETWORK",colors={colors.green,colors.red,colors.orange,colors.yellow,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
|
||||||
local nt_lnk = LEDPair{parent=system,label="NT LINKED",off=colors.red_off,c1=colors.red,c2=colors.green}
|
local nt_lnk = LEDPair{parent=system,label="NT LINKED",off=style.ind_bkg,c1=colors.red,c2=colors.green}
|
||||||
local nt_ver = LEDPair{parent=system,label="NT VERSION",off=colors.red_off,c1=colors.red,c2=colors.green}
|
local nt_ver = LEDPair{parent=system,label="NT VERSION",off=style.ind_bkg,c1=colors.red,c2=colors.green}
|
||||||
local nt_col = LED{parent=system,label="NT COLLISION",colors=ind_red}
|
local nt_col = LED{parent=system,label="NT COLLISION",colors=ind_red}
|
||||||
|
|
||||||
nt_lnk.register(databus.ps, "link_state", function (state)
|
nt_lnk.register(databus.ps, "link_state", function (state)
|
||||||
@ -115,7 +115,7 @@ local function init(panel)
|
|||||||
|
|
||||||
---@diagnostic disable-next-line: undefined-field
|
---@diagnostic disable-next-line: undefined-field
|
||||||
local comp_id = util.sprintf("(%d)", os.getComputerID())
|
local comp_id = util.sprintf("(%d)", os.getComputerID())
|
||||||
TextBox{parent=system,x=9,y=5,width=6,height=1,text=comp_id,fg_bg=disabled_fg}
|
TextBox{parent=system,x=9,y=5,width=6,text=comp_id,fg_bg=disabled_fg}
|
||||||
|
|
||||||
--
|
--
|
||||||
-- status & controls
|
-- status & controls
|
||||||
@ -148,8 +148,8 @@ local function init(panel)
|
|||||||
--
|
--
|
||||||
|
|
||||||
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=3,x=1,y=18,fg_bg=disabled_fg}
|
||||||
local fw_v = TextBox{parent=about,x=1,y=1,text="FW: v00.00.00",alignment=ALIGN.LEFT,height=1}
|
local fw_v = TextBox{parent=about,x=1,y=1,text="FW: v00.00.00"}
|
||||||
local comms_v = TextBox{parent=about,x=1,y=2,text="NT: v00.00.00",alignment=ALIGN.LEFT,height=1}
|
local comms_v = TextBox{parent=about,x=1,y=2,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)
|
||||||
|
@ -29,7 +29,13 @@ function style.set_theme(fp, color_mode)
|
|||||||
|
|
||||||
style.fp = themes.get_fp_style(style.theme)
|
style.fp = themes.get_fp_style(style.theme)
|
||||||
|
|
||||||
style.colorblind = color_mode ~= themes.COLOR_MODE.STANDARD
|
style.colorblind = color_mode ~= themes.COLOR_MODE.STANDARD and color_mode ~= themes.COLOR_MODE.STD_ON_BLACK
|
||||||
|
|
||||||
|
if color_mode == themes.COLOR_MODE.STANDARD or color_mode == themes.COLOR_MODE.BLUE_IND then
|
||||||
|
style.ind_bkg = colors.gray
|
||||||
|
else
|
||||||
|
style.ind_bkg = colors.black
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return style
|
return style
|
||||||
|
@ -6,6 +6,8 @@ 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 themes = require("graphics.themes")
|
||||||
|
|
||||||
local databus = require("reactor-plc.databus")
|
local databus = require("reactor-plc.databus")
|
||||||
|
|
||||||
local plc = {}
|
local plc = {}
|
||||||
@ -23,8 +25,8 @@ local RPS_LIMITS = const.RPS_LIMITS
|
|||||||
|
|
||||||
-- I sure hope the devs don't change this error message, not that it would have safety implications
|
-- I sure hope the devs don't change this error message, not that it would have safety implications
|
||||||
-- I wish they didn't change it to be like this
|
-- I wish they didn't change it to be like this
|
||||||
local PCALL_SCRAM_MSG = "pcall: Scram requires the reactor to be active."
|
local PCALL_SCRAM_MSG = "Scram requires the reactor to be active."
|
||||||
local PCALL_START_MSG = "pcall: Reactor is already active."
|
local PCALL_START_MSG = "Reactor is already active."
|
||||||
|
|
||||||
---@type plc_config
|
---@type plc_config
|
||||||
local config = {}
|
local config = {}
|
||||||
@ -55,41 +57,47 @@ function plc.load_config()
|
|||||||
config.FrontPanelTheme = settings.get("FrontPanelTheme")
|
config.FrontPanelTheme = settings.get("FrontPanelTheme")
|
||||||
config.ColorMode = settings.get("ColorMode")
|
config.ColorMode = settings.get("ColorMode")
|
||||||
|
|
||||||
|
return plc.validate_config(config)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- validate a PLC configuration
|
||||||
|
---@param cfg plc_config
|
||||||
|
function plc.validate_config(cfg)
|
||||||
local cfv = util.new_validator()
|
local cfv = util.new_validator()
|
||||||
|
|
||||||
cfv.assert_type_bool(config.Networked)
|
cfv.assert_type_bool(cfg.Networked)
|
||||||
cfv.assert_type_int(config.UnitID)
|
cfv.assert_type_int(cfg.UnitID)
|
||||||
cfv.assert_type_bool(config.EmerCoolEnable)
|
cfv.assert_type_bool(cfg.EmerCoolEnable)
|
||||||
|
|
||||||
if config.Networked == true then
|
if cfg.Networked == true then
|
||||||
cfv.assert_channel(config.SVR_Channel)
|
cfv.assert_channel(cfg.SVR_Channel)
|
||||||
cfv.assert_channel(config.PLC_Channel)
|
cfv.assert_channel(cfg.PLC_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_eq(len == 0 or len >= 8, true)
|
cfv.assert(len == 0 or len >= 8)
|
||||||
end
|
end
|
||||||
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, 4)
|
cfv.assert_range(cfg.ColorMode, 1, themes.COLOR_MODE.NUM_MODES)
|
||||||
|
|
||||||
-- check emergency coolant configuration if enabled
|
-- check emergency coolant configuration if enabled
|
||||||
if config.EmerCoolEnable then
|
if cfg.EmerCoolEnable then
|
||||||
cfv.assert_eq(rsio.is_valid_side(config.EmerCoolSide), true)
|
cfv.assert_eq(rsio.is_valid_side(cfg.EmerCoolSide), true)
|
||||||
cfv.assert_eq(config.EmerCoolColor == nil or rsio.is_color(config.EmerCoolColor), true)
|
cfv.assert_eq(cfg.EmerCoolColor == nil or rsio.is_color(cfg.EmerCoolColor), true)
|
||||||
end
|
end
|
||||||
|
|
||||||
return cfv.valid()
|
return cfv.valid()
|
||||||
@ -144,9 +152,9 @@ function plc.rps_init(reactor, is_formed)
|
|||||||
local function _check_and_handle_ppm_call(result)
|
local function _check_and_handle_ppm_call(result)
|
||||||
if result == ppm.ACCESS_FAULT then
|
if result == ppm.ACCESS_FAULT then
|
||||||
_set_fault()
|
_set_fault()
|
||||||
elseif result == ppm.UNDEFINED_FIELD then
|
|
||||||
_set_fault()
|
-- if undefined, then the reactor isn't formed
|
||||||
self.formed = false
|
if reactor.__p_last_fault() == ppm.UNDEFINED_FIELD then self.formed = false end
|
||||||
else return true end
|
else return true end
|
||||||
|
|
||||||
return false
|
return false
|
||||||
@ -305,7 +313,7 @@ function plc.rps_init(reactor, is_formed)
|
|||||||
log.info("RPS: reactor SCRAM")
|
log.info("RPS: reactor SCRAM")
|
||||||
|
|
||||||
reactor.scram()
|
reactor.scram()
|
||||||
if reactor.__p_is_faulted() and (reactor.__p_last_fault() ~= PCALL_SCRAM_MSG) then
|
if reactor.__p_is_faulted() and not string.find(reactor.__p_last_fault(), PCALL_SCRAM_MSG) then
|
||||||
log.error("RPS: failed reactor SCRAM")
|
log.error("RPS: failed reactor SCRAM")
|
||||||
return false
|
return false
|
||||||
else
|
else
|
||||||
@ -323,7 +331,7 @@ function plc.rps_init(reactor, is_formed)
|
|||||||
log.info("RPS: reactor start")
|
log.info("RPS: reactor start")
|
||||||
|
|
||||||
reactor.activate()
|
reactor.activate()
|
||||||
if reactor.__p_is_faulted() and (reactor.__p_last_fault() ~= PCALL_START_MSG) then
|
if reactor.__p_is_faulted() and not string.find(reactor.__p_last_fault(), PCALL_START_MSG) then
|
||||||
log.error("RPS: failed reactor start")
|
log.error("RPS: failed reactor start")
|
||||||
else
|
else
|
||||||
self.reactor_enabled = true
|
self.reactor_enabled = true
|
||||||
@ -522,8 +530,8 @@ end
|
|||||||
function plc.comms(version, nic, reactor, rps, conn_watchdog)
|
function plc.comms(version, nic, reactor, rps, conn_watchdog)
|
||||||
local self = {
|
local self = {
|
||||||
sv_addr = comms.BROADCAST,
|
sv_addr = comms.BROADCAST,
|
||||||
seq_num = 0,
|
seq_num = util.time_ms() * 10, -- unique per peer, restarting will not re-use seq nums due to message rate
|
||||||
r_seq_num = nil,
|
r_seq_num = nil, ---@type nil|integer
|
||||||
scrammed = false,
|
scrammed = false,
|
||||||
linked = false,
|
linked = false,
|
||||||
last_est_ack = ESTABLISH_ACK.ALLOW,
|
last_est_ack = ESTABLISH_ACK.ALLOW,
|
||||||
@ -569,33 +577,17 @@ function plc.comms(version, nic, reactor, rps, conn_watchdog)
|
|||||||
self.seq_num = self.seq_num + 1
|
self.seq_num = self.seq_num + 1
|
||||||
end
|
end
|
||||||
|
|
||||||
-- variable reactor status information, excluding heating rate
|
-- dynamic reactor status information, excluding heating rate
|
||||||
---@return table data_table, boolean faulted
|
---@return table data_table, boolean faulted
|
||||||
local function _reactor_status()
|
local function _get_reactor_status()
|
||||||
local fuel = nil
|
local fuel = nil
|
||||||
local waste = nil
|
local waste = nil
|
||||||
local coolant = nil
|
local coolant = nil
|
||||||
local hcoolant = nil
|
local hcoolant = nil
|
||||||
|
|
||||||
local data_table = {
|
local data_table = {}
|
||||||
false, -- getStatus
|
|
||||||
0, -- getBurnRate
|
reactor.__p_disable_afc()
|
||||||
0, -- getActualBurnRate
|
|
||||||
0, -- getTemperature
|
|
||||||
0, -- getDamagePercent
|
|
||||||
0, -- getBoilEfficiency
|
|
||||||
0, -- getEnvironmentalLoss
|
|
||||||
0, -- fuel_amnt
|
|
||||||
0, -- getFuelFilledPercentage
|
|
||||||
0, -- waste_amnt
|
|
||||||
0, -- getWasteFilledPercentage
|
|
||||||
"", -- coolant_name
|
|
||||||
0, -- coolant_amnt
|
|
||||||
0, -- getCoolantFilledPercentage
|
|
||||||
"", -- hcoolant_name
|
|
||||||
0, -- hcoolant_amnt
|
|
||||||
0 -- getHeatedCoolantFilledPercentage
|
|
||||||
}
|
|
||||||
|
|
||||||
local tasks = {
|
local tasks = {
|
||||||
function () data_table[1] = reactor.getStatus() end,
|
function () data_table[1] = reactor.getStatus() end,
|
||||||
@ -635,30 +627,32 @@ function plc.comms(version, nic, reactor, rps, conn_watchdog)
|
|||||||
data_table[16] = hcoolant.amount
|
data_table[16] = hcoolant.amount
|
||||||
end
|
end
|
||||||
|
|
||||||
|
reactor.__p_enable_afc()
|
||||||
|
|
||||||
return data_table, reactor.__p_is_faulted()
|
return data_table, reactor.__p_is_faulted()
|
||||||
end
|
end
|
||||||
|
|
||||||
-- update the status cache if changed
|
-- update the status cache if changed
|
||||||
---@return boolean changed
|
---@return boolean changed
|
||||||
local function _update_status_cache()
|
local function _update_status_cache()
|
||||||
local status, faulted = _reactor_status()
|
local status, faulted = _get_reactor_status()
|
||||||
local changed = false
|
local changed = false
|
||||||
|
|
||||||
if self.status_cache ~= nil then
|
if not faulted then
|
||||||
if not faulted then
|
if self.status_cache ~= nil then
|
||||||
for i = 1, #status do
|
for i = 1, #status do
|
||||||
if status[i] ~= self.status_cache[i] then
|
if status[i] ~= self.status_cache[i] then
|
||||||
changed = true
|
changed = true
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
else
|
||||||
|
changed = true
|
||||||
end
|
end
|
||||||
else
|
|
||||||
changed = true
|
|
||||||
end
|
|
||||||
|
|
||||||
if changed and not faulted then
|
if changed then
|
||||||
self.status_cache = status
|
self.status_cache = status
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return changed
|
return changed
|
||||||
@ -677,9 +671,11 @@ function plc.comms(version, nic, reactor, rps, conn_watchdog)
|
|||||||
_send(msg_type, { status })
|
_send(msg_type, { status })
|
||||||
end
|
end
|
||||||
|
|
||||||
-- send structure properties (these should not change, server will cache these)
|
-- send static structure properties, cached by server
|
||||||
local function _send_struct()
|
local function _send_struct()
|
||||||
local mek_data = { false, 0, 0, 0, types.new_zero_coordinate(), types.new_zero_coordinate(), 0, 0, 0, 0, 0, 0, 0, 0 }
|
local mek_data = {}
|
||||||
|
|
||||||
|
reactor.__p_disable_afc()
|
||||||
|
|
||||||
local tasks = {
|
local tasks = {
|
||||||
function () mek_data[1] = reactor.getLength() end,
|
function () mek_data[1] = reactor.getLength() end,
|
||||||
@ -703,6 +699,8 @@ function plc.comms(version, nic, reactor, rps, conn_watchdog)
|
|||||||
_send(RPLC_TYPE.MEK_STRUCT, mek_data)
|
_send(RPLC_TYPE.MEK_STRUCT, mek_data)
|
||||||
self.resend_build = false
|
self.resend_build = false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
reactor.__p_enable_afc()
|
||||||
end
|
end
|
||||||
|
|
||||||
-- PUBLIC FUNCTIONS --
|
-- PUBLIC FUNCTIONS --
|
||||||
@ -833,16 +831,16 @@ function plc.comms(version, nic, reactor, rps, conn_watchdog)
|
|||||||
if l_chan == config.PLC_Channel then
|
if l_chan == config.PLC_Channel then
|
||||||
-- check sequence number
|
-- check sequence number
|
||||||
if self.r_seq_num == nil then
|
if self.r_seq_num == nil then
|
||||||
self.r_seq_num = packet.scada_frame.seq_num()
|
self.r_seq_num = packet.scada_frame.seq_num() + 1
|
||||||
elseif self.linked and ((self.r_seq_num + 1) ~= packet.scada_frame.seq_num()) then
|
elseif self.r_seq_num ~= packet.scada_frame.seq_num() then
|
||||||
log.warning("sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. packet.scada_frame.seq_num())
|
log.warning("sequence out-of-order: next = " .. self.r_seq_num .. ", new = " .. packet.scada_frame.seq_num())
|
||||||
return
|
return
|
||||||
elseif self.linked and (src_addr ~= self.sv_addr) then
|
elseif self.linked and (src_addr ~= self.sv_addr) then
|
||||||
log.debug("received packet from unknown computer " .. src_addr .. " while linked (expected " .. self.sv_addr ..
|
log.debug("received packet from unknown computer " .. src_addr .. " while linked (expected " .. self.sv_addr ..
|
||||||
"); channel in use by another system?")
|
"); channel in use by another system?")
|
||||||
return
|
return
|
||||||
else
|
else
|
||||||
self.r_seq_num = packet.scada_frame.seq_num()
|
self.r_seq_num = packet.scada_frame.seq_num() + 1
|
||||||
end
|
end
|
||||||
|
|
||||||
-- feed the watchdog first so it doesn't uhh...eat our packets :)
|
-- feed the watchdog first so it doesn't uhh...eat our packets :)
|
||||||
@ -1028,10 +1026,9 @@ function plc.comms(version, nic, reactor, rps, conn_watchdog)
|
|||||||
println_ts("linked!")
|
println_ts("linked!")
|
||||||
log.info("supervisor establish request approved, linked to SV (CID#" .. src_addr .. ")")
|
log.info("supervisor establish request approved, linked to SV (CID#" .. src_addr .. ")")
|
||||||
|
|
||||||
-- link + reset remote sequence number and cache
|
-- link + reset cache
|
||||||
self.sv_addr = src_addr
|
self.sv_addr = src_addr
|
||||||
self.linked = true
|
self.linked = true
|
||||||
self.r_seq_num = nil
|
|
||||||
self.status_cache = nil
|
self.status_cache = nil
|
||||||
|
|
||||||
if plc_state.reactor_formed then _send_struct() end
|
if plc_state.reactor_formed then _send_struct() end
|
||||||
|
@ -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.7.0"
|
local R_PLC_VERSION = "v1.8.6"
|
||||||
|
|
||||||
local println = util.println
|
local println = util.println
|
||||||
local println_ts = util.println_ts
|
local println_ts = util.println_ts
|
||||||
@ -55,6 +55,7 @@ log.info("========================================")
|
|||||||
println(">> Reactor PLC " .. R_PLC_VERSION .. " <<")
|
println(">> Reactor PLC " .. R_PLC_VERSION .. " <<")
|
||||||
|
|
||||||
crash.set_env("reactor-plc", R_PLC_VERSION)
|
crash.set_env("reactor-plc", R_PLC_VERSION)
|
||||||
|
crash.dbg_log_env()
|
||||||
|
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
-- main application
|
-- main application
|
||||||
@ -144,13 +145,6 @@ local function main()
|
|||||||
println("init> fission reactor is not formed")
|
println("init> fission reactor is not formed")
|
||||||
log.warning("init> reactor logic adapter present, but reactor is not formed")
|
log.warning("init> reactor logic adapter present, but reactor is not formed")
|
||||||
|
|
||||||
plc_state.degraded = true
|
|
||||||
plc_state.reactor_formed = false
|
|
||||||
elseif smem_dev.reactor.getStatus() == ppm.UNDEFINED_FIELD then
|
|
||||||
-- reactor formed after ppm.mount_all was called
|
|
||||||
println("init> fission reactor was not formed")
|
|
||||||
log.warning("init> reactor reported formed, but multiblock functions are not available")
|
|
||||||
|
|
||||||
plc_state.degraded = true
|
plc_state.degraded = true
|
||||||
plc_state.reactor_formed = false
|
plc_state.reactor_formed = false
|
||||||
end
|
end
|
||||||
@ -185,6 +179,7 @@ local function main()
|
|||||||
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)
|
||||||
|
|
||||||
|
-- ...or not
|
||||||
if not plc_state.fp_ok then
|
if not plc_state.fp_ok then
|
||||||
println_ts(util.c("UI error: ", message))
|
println_ts(util.c("UI error: ", message))
|
||||||
println("init> running without front panel")
|
println("init> running without front panel")
|
||||||
|
@ -71,76 +71,49 @@ function threads.thread__main(smem, init)
|
|||||||
-- blink heartbeat indicator
|
-- blink heartbeat indicator
|
||||||
databus.heartbeat()
|
databus.heartbeat()
|
||||||
|
|
||||||
-- core clock tick
|
-- start next clock timer
|
||||||
if networked then
|
loop_clock.start()
|
||||||
-- start next clock timer
|
|
||||||
loop_clock.start()
|
|
||||||
|
|
||||||
-- send updated data
|
-- send updated data
|
||||||
if nic.is_connected() then
|
if networked and nic.is_connected() then
|
||||||
if plc_comms.is_linked() then
|
if plc_comms.is_linked() then
|
||||||
smem.q.mq_comms_tx.push_command(MQ__COMM_CMD.SEND_STATUS)
|
smem.q.mq_comms_tx.push_command(MQ__COMM_CMD.SEND_STATUS)
|
||||||
|
else
|
||||||
|
if ticks_to_update == 0 then
|
||||||
|
plc_comms.send_link_req()
|
||||||
|
ticks_to_update = LINK_TICKS
|
||||||
else
|
else
|
||||||
if ticks_to_update == 0 then
|
ticks_to_update = ticks_to_update - 1
|
||||||
plc_comms.send_link_req()
|
|
||||||
ticks_to_update = LINK_TICKS
|
|
||||||
else
|
|
||||||
ticks_to_update = ticks_to_update - 1
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- are we now formed after waiting to be formed?
|
-- check for formed state change
|
||||||
if (not plc_state.reactor_formed) and rps.is_formed() then
|
if (not plc_state.reactor_formed) and rps.is_formed() then
|
||||||
-- push a connect event and unmount it from the PPM
|
-- reactor now formed
|
||||||
local iface = ppm.get_iface(plc_dev.reactor)
|
plc_state.reactor_formed = true
|
||||||
if iface then
|
|
||||||
log.info("unmounting and remounting unformed reactor")
|
|
||||||
ppm.unmount(plc_dev.reactor)
|
|
||||||
|
|
||||||
local type, device = ppm.mount(iface)
|
println_ts("reactor is now formed.")
|
||||||
|
log.info("reactor is now formed")
|
||||||
|
|
||||||
if type == "fissionReactorLogicAdapter" and device ~= nil then
|
-- SCRAM newly formed reactor
|
||||||
-- reconnect reactor
|
smem.q.mq_rps.push_command(MQ__RPS_CMD.SCRAM)
|
||||||
plc_dev.reactor = device
|
|
||||||
|
|
||||||
-- we need to assume formed here as we cannot check in this main loop
|
-- determine if we are still in a degraded state
|
||||||
-- RPS will identify if it isn't and this will get set false later
|
if (not networked) or nic.is_connected() then
|
||||||
plc_state.reactor_formed = true
|
plc_state.degraded = false
|
||||||
|
|
||||||
println_ts("reactor reconnected.")
|
|
||||||
log.info("reactor reconnected")
|
|
||||||
|
|
||||||
-- SCRAM newly connected reactor
|
|
||||||
smem.q.mq_rps.push_command(MQ__RPS_CMD.SCRAM)
|
|
||||||
|
|
||||||
-- determine if we are still in a degraded state
|
|
||||||
if (not networked) or nic.is_connected() then
|
|
||||||
plc_state.degraded = false
|
|
||||||
end
|
|
||||||
|
|
||||||
rps.reconnect_reactor(plc_dev.reactor)
|
|
||||||
if networked then
|
|
||||||
plc_comms.reconnect_reactor(plc_dev.reactor)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- partial reset of RPS, specific to becoming formed
|
|
||||||
rps.reset_formed()
|
|
||||||
else
|
|
||||||
-- fully lost the reactor now :(
|
|
||||||
println_ts("reactor lost (failed reconnect)!")
|
|
||||||
log.error("reactor lost (failed reconnect)")
|
|
||||||
|
|
||||||
plc_state.no_reactor = true
|
|
||||||
plc_state.degraded = true
|
|
||||||
end
|
|
||||||
else
|
|
||||||
log.error("failed to get interface of previously connected reactor", true)
|
|
||||||
end
|
end
|
||||||
elseif not rps.is_formed() then
|
|
||||||
|
-- partial reset of RPS, specific to becoming formed
|
||||||
|
-- without this, auto control can't resume on chunk load
|
||||||
|
rps.reset_formed()
|
||||||
|
elseif plc_state.reactor_formed and not rps.is_formed() then
|
||||||
-- reactor no longer formed
|
-- reactor no longer formed
|
||||||
|
println_ts("reactor is no longer formed.")
|
||||||
|
log.info("reactor is no longer formed")
|
||||||
|
|
||||||
plc_state.reactor_formed = false
|
plc_state.reactor_formed = false
|
||||||
|
plc_state.degraded = true
|
||||||
end
|
end
|
||||||
|
|
||||||
-- update indicators
|
-- update indicators
|
||||||
@ -230,7 +203,8 @@ function threads.thread__main(smem, init)
|
|||||||
plc_comms.reconnect_reactor(plc_dev.reactor)
|
plc_comms.reconnect_reactor(plc_dev.reactor)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- partial reset of RPS, specific to becoming formed
|
-- partial reset of RPS, specific to becoming formed/reconnected
|
||||||
|
-- without this, auto control can't resume on chunk load
|
||||||
rps.reset_formed()
|
rps.reset_formed()
|
||||||
end
|
end
|
||||||
elseif networked and type == "modem" then
|
elseif networked and type == "modem" then
|
||||||
@ -368,9 +342,9 @@ function threads.thread__rps(smem)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- if we are in standalone mode, continuously reset RPS
|
-- if we are in standalone mode and the front panel isn't working, continuously reset RPS
|
||||||
-- RPS will trip again if there are faults, but if it isn't cleared, the user can't re-enable
|
-- RPS will trip again if there are faults, but if it isn't cleared, the user can't re-enable
|
||||||
if not networked then rps.reset(true) end
|
if not (networked or smem.plc_state.fp_ok) then rps.reset(true) end
|
||||||
|
|
||||||
-- check safety (SCRAM occurs if tripped)
|
-- check safety (SCRAM occurs if tripped)
|
||||||
if not plc_state.no_reactor then
|
if not plc_state.no_reactor then
|
||||||
@ -662,8 +636,9 @@ function threads.thread__setpoint_control(smem)
|
|||||||
if (type(cur_burn_rate) == "number") and (setpoints.burn_rate ~= cur_burn_rate) and rps.is_active() then
|
if (type(cur_burn_rate) == "number") and (setpoints.burn_rate ~= cur_burn_rate) and rps.is_active() then
|
||||||
last_burn_sp = setpoints.burn_rate
|
last_burn_sp = setpoints.burn_rate
|
||||||
|
|
||||||
-- update without ramp if <= 2.5 mB/t change
|
-- update without ramp if <= 2.5 mB/t increase
|
||||||
running = math.abs(setpoints.burn_rate - cur_burn_rate) > 2.5
|
-- no need to ramp down, as the ramp up poses the safety risks
|
||||||
|
running = (setpoints.burn_rate - cur_burn_rate) > 2.5
|
||||||
|
|
||||||
if running then
|
if running then
|
||||||
log.debug(util.c("SPCTL: starting burn rate ramp from ", cur_burn_rate, " mB/t to ", setpoints.burn_rate, " mB/t"))
|
log.debug(util.c("SPCTL: starting burn rate ramp from ", cur_burn_rate, " mB/t to ", setpoints.burn_rate, " mB/t"))
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
-- Configuration GUI
|
-- Configuration GUI
|
||||||
--
|
--
|
||||||
|
|
||||||
|
local constants = require("scada-common.constants")
|
||||||
local log = require("scada-common.log")
|
local log = require("scada-common.log")
|
||||||
local ppm = require("scada-common.ppm")
|
local ppm = require("scada-common.ppm")
|
||||||
local rsio = require("scada-common.rsio")
|
local rsio = require("scada-common.rsio")
|
||||||
@ -33,51 +34,58 @@ local tri = util.trinary
|
|||||||
local cpair = core.cpair
|
local cpair = core.cpair
|
||||||
|
|
||||||
local IO = rsio.IO
|
local IO = rsio.IO
|
||||||
|
local IO_LVL = rsio.IO_LVL
|
||||||
|
local IO_MODE = rsio.IO_MODE
|
||||||
|
|
||||||
local LEFT = core.ALIGN.LEFT
|
local LEFT = core.ALIGN.LEFT
|
||||||
local CENTER = core.ALIGN.CENTER
|
local CENTER = core.ALIGN.CENTER
|
||||||
local RIGHT = core.ALIGN.RIGHT
|
local RIGHT = core.ALIGN.RIGHT
|
||||||
|
|
||||||
-- rsio port descriptions
|
-- rsio port descriptions
|
||||||
local PORT_DESC = {
|
local PORT_DESC_MAP = {
|
||||||
"Facility SCRAM",
|
{ IO.F_SCRAM, "Facility SCRAM" },
|
||||||
"Facility Acknowledge",
|
{ IO.F_ACK, "Facility Acknowledge" },
|
||||||
"Reactor SCRAM",
|
{ IO.R_SCRAM, "Reactor SCRAM" },
|
||||||
"Reactor RPS Reset",
|
{ IO.R_RESET, "Reactor RPS Reset" },
|
||||||
"Reactor Enable",
|
{ IO.R_ENABLE, "Reactor Enable" },
|
||||||
"Unit Acknowledge",
|
{ IO.U_ACK, "Unit Acknowledge" },
|
||||||
"Facility Alarm (high prio)",
|
{ IO.F_ALARM, "Facility Alarm (high prio)" },
|
||||||
"Facility Alarm (any)",
|
{ IO.F_ALARM_ANY, "Facility Alarm (any)" },
|
||||||
"Waste Plutonium Valve",
|
{ IO.F_MATRIX_LOW, "Induction Matrix < " .. (100 * constants.RS_THRESHOLDS.IMATRIX_CHARGE_LOW) .. "%" },
|
||||||
"Waste Polonium Valve",
|
{ IO.F_MATRIX_HIGH, "Induction Matrix > " .. (100 * constants.RS_THRESHOLDS.IMATRIX_CHARGE_HIGH) .. "%" },
|
||||||
"Waste Po Pellets Valve",
|
{ IO.F_MATRIX_CHG, "Induction Matrix Charge %" },
|
||||||
"Waste Antimatter Valve",
|
{ IO.WASTE_PU, "Waste Plutonium Valve" },
|
||||||
"Reactor Active",
|
{ IO.WASTE_PO, "Waste Polonium Valve" },
|
||||||
"Reactor in Auto Control",
|
{ IO.WASTE_POPL, "Waste Po Pellets Valve" },
|
||||||
"RPS Tripped",
|
{ IO.WASTE_AM, "Waste Antimatter Valve" },
|
||||||
"RPS Auto SCRAM",
|
{ IO.R_ACTIVE, "Reactor Active" },
|
||||||
"RPS High Damage",
|
{ IO.R_AUTO_CTRL, "Reactor in Auto Control" },
|
||||||
"RPS High Temperature",
|
{ IO.R_SCRAMMED, "RPS Tripped" },
|
||||||
"RPS Low Coolant",
|
{ IO.R_AUTO_SCRAM, "RPS Auto SCRAM" },
|
||||||
"RPS Excess Heated Coolant",
|
{ IO.R_HIGH_DMG, "RPS High Damage" },
|
||||||
"RPS Excess Waste",
|
{ IO.R_HIGH_TEMP, "RPS High Temperature" },
|
||||||
"RPS Insufficient Fuel",
|
{ IO.R_LOW_COOLANT, "RPS Low Coolant" },
|
||||||
"RPS PLC Fault",
|
{ IO.R_EXCESS_HC, "RPS Excess Heated Coolant" },
|
||||||
"RPS Supervisor Timeout",
|
{ IO.R_EXCESS_WS, "RPS Excess Waste" },
|
||||||
"Unit Alarm",
|
{ IO.R_INSUFF_FUEL, "RPS Insufficient Fuel" },
|
||||||
"Unit Emergency Cool. Valve"
|
{ IO.R_PLC_FAULT, "RPS PLC Fault" },
|
||||||
|
{ IO.R_PLC_TIMEOUT, "RPS Supervisor Timeout" },
|
||||||
|
{ IO.U_ALARM, "Unit Alarm" },
|
||||||
|
{ IO.U_EMER_COOL, "Unit Emergency 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 }
|
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 }
|
||||||
|
|
||||||
assert(#PORT_DESC == rsio.NUM_PORTS)
|
assert(#PORT_DESC_MAP == rsio.NUM_PORTS)
|
||||||
assert(#PORT_DSGN == rsio.NUM_PORTS)
|
assert(#PORT_DSGN == rsio.NUM_PORTS)
|
||||||
|
|
||||||
-- 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.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.10.2", { "Re-organized peripheral configuration UI, resulting in some input fields being re-ordered" } }
|
||||||
}
|
}
|
||||||
|
|
||||||
---@class rtu_rs_definition
|
---@class rtu_rs_definition
|
||||||
@ -151,7 +159,6 @@ local tool_ctl = {
|
|||||||
p_idx = nil, ---@type graphics_element
|
p_idx = nil, ---@type graphics_element
|
||||||
p_unit = nil, ---@type graphics_element
|
p_unit = nil, ---@type graphics_element
|
||||||
p_assign_btn = nil, ---@type graphics_element
|
p_assign_btn = nil, ---@type graphics_element
|
||||||
p_assign_end = nil, ---@type graphics_element
|
|
||||||
p_desc = nil, ---@type graphics_element
|
p_desc = nil, ---@type graphics_element
|
||||||
p_desc_ext = nil, ---@type graphics_element
|
p_desc_ext = nil, ---@type graphics_element
|
||||||
p_err = nil, ---@type graphics_element
|
p_err = nil, ---@type graphics_element
|
||||||
@ -257,7 +264,7 @@ local function config_view(display)
|
|||||||
---@diagnostic disable-next-line: undefined-field
|
---@diagnostic disable-next-line: undefined-field
|
||||||
local function exit() os.queueEvent("terminate") end
|
local function exit() os.queueEvent("terminate") end
|
||||||
|
|
||||||
TextBox{parent=display,y=1,text="RTU Gateway Configurator",alignment=CENTER,height=1,fg_bg=style.header}
|
TextBox{parent=display,y=1,text="RTU Gateway Configurator",alignment=CENTER,fg_bg=style.header}
|
||||||
|
|
||||||
local root_pane_div = Div{parent=display,x=1,y=2}
|
local root_pane_div = Div{parent=display,x=1,y=2}
|
||||||
|
|
||||||
@ -278,7 +285,7 @@ local function config_view(display)
|
|||||||
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 has 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 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)}
|
||||||
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."}
|
||||||
@ -321,7 +328,7 @@ local function config_view(display)
|
|||||||
end
|
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}
|
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)}
|
||||||
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=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}
|
||||||
|
|
||||||
if not tool_ctl.has_config then
|
if not tool_ctl.has_config then
|
||||||
@ -337,7 +344,7 @@ local function config_view(display)
|
|||||||
|
|
||||||
local spkr_c = Div{parent=spkr_cfg,x=2,y=4,width=49}
|
local spkr_c = Div{parent=spkr_cfg,x=2,y=4,width=49}
|
||||||
|
|
||||||
TextBox{parent=spkr_cfg,x=1,y=2,height=1,text=" Speaker Configuration",fg_bg=cpair(colors.black,colors.cyan)}
|
TextBox{parent=spkr_cfg,x=1,y=2,text=" Speaker Configuration",fg_bg=cpair(colors.black,colors.cyan)}
|
||||||
|
|
||||||
TextBox{parent=spkr_c,x=1,y=1,height=2,text="Speakers can be connected to this RTU gateway without RTU unit configuration entries."}
|
TextBox{parent=spkr_c,x=1,y=1,height=2,text="Speakers can be connected to this RTU gateway without RTU unit configuration entries."}
|
||||||
TextBox{parent=spkr_c,x=1,y=4,height=3,text="You can change the speaker audio volume from the default. The range is 0.0 to 3.0, where 1.0 is standard volume."}
|
TextBox{parent=spkr_c,x=1,y=4,height=3,text="You can change the speaker audio volume from the default. The range is 0.0 to 3.0, where 1.0 is standard volume."}
|
||||||
@ -346,7 +353,7 @@ local function config_view(display)
|
|||||||
|
|
||||||
TextBox{parent=spkr_c,x=1,y=10,height=3,text="Note: alarm sine waves are at half scale so that multiple will be required to reach full scale.",fg_bg=g_lg_fg_bg}
|
TextBox{parent=spkr_c,x=1,y=10,height=3,text="Note: alarm sine waves are at half scale so that multiple will be required to reach full scale.",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
local s_vol_err = TextBox{parent=spkr_c,x=8,y=14,height=1,width=35,text="Please set a volume.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
local s_vol_err = TextBox{parent=spkr_c,x=8,y=14,width=35,text="Please set a volume.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||||
|
|
||||||
local function submit_vol()
|
local function submit_vol()
|
||||||
local vol = tonumber(s_vol.get_value())
|
local vol = tonumber(s_vol.get_value())
|
||||||
@ -370,19 +377,19 @@ local function config_view(display)
|
|||||||
|
|
||||||
local net_pane = MultiPane{parent=net_cfg,x=1,y=4,panes={net_c_1,net_c_2,net_c_3}}
|
local net_pane = MultiPane{parent=net_cfg,x=1,y=4,panes={net_c_1,net_c_2,net_c_3}}
|
||||||
|
|
||||||
TextBox{parent=net_cfg,x=1,y=2,height=1,text=" Network Configuration",fg_bg=cpair(colors.black,colors.lightBlue)}
|
TextBox{parent=net_cfg,x=1,y=2,text=" Network Configuration",fg_bg=cpair(colors.black,colors.lightBlue)}
|
||||||
|
|
||||||
TextBox{parent=net_c_1,x=1,y=1,height=1,text="Please set the network channels below."}
|
TextBox{parent=net_c_1,x=1,y=1,text="Please set the network channels below."}
|
||||||
TextBox{parent=net_c_1,x=1,y=3,height=4,text="Each of the 5 uniquely named channels, including the 2 below, must be the same for each device in this SCADA network. For multiplayer servers, it is recommended to not use the default channels.",fg_bg=g_lg_fg_bg}
|
TextBox{parent=net_c_1,x=1,y=3,height=4,text="Each of the 5 uniquely named channels, including the 2 below, must be the same for each device in this SCADA network. For multiplayer servers, it is recommended to not use the default channels.",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=net_c_1,x=1,y=8,height=1,text="Supervisor Channel"}
|
TextBox{parent=net_c_1,x=1,y=8,text="Supervisor Channel"}
|
||||||
local svr_chan = NumberField{parent=net_c_1,x=1,y=9,width=7,default=ini_cfg.SVR_Channel,min=1,max=65535,fg_bg=bw_fg_bg}
|
local svr_chan = NumberField{parent=net_c_1,x=1,y=9,width=7,default=ini_cfg.SVR_Channel,min=1,max=65535,fg_bg=bw_fg_bg}
|
||||||
TextBox{parent=net_c_1,x=9,y=9,height=4,text="[SVR_CHANNEL]",fg_bg=g_lg_fg_bg}
|
TextBox{parent=net_c_1,x=9,y=9,height=4,text="[SVR_CHANNEL]",fg_bg=g_lg_fg_bg}
|
||||||
TextBox{parent=net_c_1,x=1,y=11,height=1,text="RTU Channel"}
|
TextBox{parent=net_c_1,x=1,y=11,text="RTU Channel"}
|
||||||
local rtu_chan = NumberField{parent=net_c_1,x=1,y=12,width=7,default=ini_cfg.RTU_Channel,min=1,max=65535,fg_bg=bw_fg_bg}
|
local rtu_chan = NumberField{parent=net_c_1,x=1,y=12,width=7,default=ini_cfg.RTU_Channel,min=1,max=65535,fg_bg=bw_fg_bg}
|
||||||
TextBox{parent=net_c_1,x=9,y=12,height=4,text="[RTU_CHANNEL]",fg_bg=g_lg_fg_bg}
|
TextBox{parent=net_c_1,x=9,y=12,height=4,text="[RTU_CHANNEL]",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
local chan_err = TextBox{parent=net_c_1,x=8,y=14,height=1,width=35,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
local chan_err = TextBox{parent=net_c_1,x=8,y=14,width=35,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||||
|
|
||||||
local function submit_channels()
|
local function submit_channels()
|
||||||
local svr_c = tonumber(svr_chan.get_value())
|
local svr_c = tonumber(svr_chan.get_value())
|
||||||
@ -404,16 +411,16 @@ local function config_view(display)
|
|||||||
PushButton{parent=net_c_1,x=1,y=14,text="\x1b Back",callback=function()main_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=net_c_1,x=1,y=14,text="\x1b Back",callback=function()main_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
PushButton{parent=net_c_1,x=44,y=14,text="Next \x1a",callback=submit_channels,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=net_c_1,x=44,y=14,text="Next \x1a",callback=submit_channels,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=net_c_2,x=1,y=1,height=1,text="Connection Timeout"}
|
TextBox{parent=net_c_2,x=1,y=1,text="Connection Timeout"}
|
||||||
local timeout = NumberField{parent=net_c_2,x=1,y=2,width=7,default=ini_cfg.ConnTimeout,min=2,max=25,max_chars=6,max_frac_digits=2,allow_decimal=true,fg_bg=bw_fg_bg}
|
local timeout = NumberField{parent=net_c_2,x=1,y=2,width=7,default=ini_cfg.ConnTimeout,min=2,max=25,max_chars=6,max_frac_digits=2,allow_decimal=true,fg_bg=bw_fg_bg}
|
||||||
TextBox{parent=net_c_2,x=9,y=2,height=2,text="seconds (default 5)",fg_bg=g_lg_fg_bg}
|
TextBox{parent=net_c_2,x=9,y=2,height=2,text="seconds (default 5)",fg_bg=g_lg_fg_bg}
|
||||||
TextBox{parent=net_c_2,x=1,y=3,height=4,text="You generally do not want or need to modify this. On slow servers, you can increase this to make the system wait longer before assuming a disconnection.",fg_bg=g_lg_fg_bg}
|
TextBox{parent=net_c_2,x=1,y=3,height=4,text="You generally do not want or need to modify this. On slow servers, you can increase this to make the system wait longer before assuming a disconnection.",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=net_c_2,x=1,y=8,height=1,text="Trusted Range"}
|
TextBox{parent=net_c_2,x=1,y=8,text="Trusted Range"}
|
||||||
local range = NumberField{parent=net_c_2,x=1,y=9,width=10,default=ini_cfg.TrustedRange,min=0,max_chars=20,allow_decimal=true,fg_bg=bw_fg_bg}
|
local range = NumberField{parent=net_c_2,x=1,y=9,width=10,default=ini_cfg.TrustedRange,min=0,max_chars=20,allow_decimal=true,fg_bg=bw_fg_bg}
|
||||||
TextBox{parent=net_c_2,x=1,y=10,height=4,text="Setting this to a value larger than 0 prevents connections with devices that many meters (blocks) away in any direction.",fg_bg=g_lg_fg_bg}
|
TextBox{parent=net_c_2,x=1,y=10,height=4,text="Setting this to a value larger than 0 prevents connections with devices that many meters (blocks) away in any direction.",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
local p2_err = TextBox{parent=net_c_2,x=8,y=14,height=1,width=35,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
local p2_err = TextBox{parent=net_c_2,x=8,y=14,width=35,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||||
|
|
||||||
local function submit_ct_tr()
|
local function submit_ct_tr()
|
||||||
local timeout_val = tonumber(timeout.get_value())
|
local timeout_val = tonumber(timeout.get_value())
|
||||||
@ -438,17 +445,17 @@ local function config_view(display)
|
|||||||
TextBox{parent=net_c_3,x=1,y=1,height=2,text="Optionally, set the facility authentication key below. Do NOT use one of your passwords."}
|
TextBox{parent=net_c_3,x=1,y=1,height=2,text="Optionally, set the facility authentication key below. Do NOT use one of your passwords."}
|
||||||
TextBox{parent=net_c_3,x=1,y=4,height=6,text="This enables verifying that messages are authentic, so it is intended for security on multiplayer servers. All devices on the same network MUST use the same key if any device has a key. This does result in some extra compution (can slow things down).",fg_bg=g_lg_fg_bg}
|
TextBox{parent=net_c_3,x=1,y=4,height=6,text="This enables verifying that messages are authentic, so it is intended for security on multiplayer servers. All devices on the same network MUST use the same key if any device has a key. This does result in some extra compution (can slow things down).",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=net_c_3,x=1,y=11,height=1,text="Facility Auth Key"}
|
TextBox{parent=net_c_3,x=1,y=11,text="Facility Auth Key"}
|
||||||
local key, _, censor = TextField{parent=net_c_3,x=1,y=12,max_len=64,value=ini_cfg.AuthKey,width=32,height=1,fg_bg=bw_fg_bg}
|
local key, _, censor = TextField{parent=net_c_3,x=1,y=12,max_len=64,value=ini_cfg.AuthKey,width=32,height=1,fg_bg=bw_fg_bg}
|
||||||
|
|
||||||
local function censor_key(enable) censor(util.trinary(enable, "*", nil)) end
|
local function censor_key(enable) censor(tri(enable, "*", nil)) end
|
||||||
|
|
||||||
local hide_key = CheckBox{parent=net_c_3,x=34,y=12,label="Hide",box_fg_bg=cpair(colors.lightBlue,colors.black),callback=censor_key}
|
local hide_key = CheckBox{parent=net_c_3,x=34,y=12,label="Hide",box_fg_bg=cpair(colors.lightBlue,colors.black),callback=censor_key}
|
||||||
|
|
||||||
hide_key.set_value(true)
|
hide_key.set_value(true)
|
||||||
censor_key(true)
|
censor_key(true)
|
||||||
|
|
||||||
local key_err = TextBox{parent=net_c_3,x=8,y=14,height=1,width=35,text="Key must be at least 8 characters.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
local key_err = TextBox{parent=net_c_3,x=8,y=14,width=35,text="Key must be at least 8 characters.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||||
|
|
||||||
local function submit_auth()
|
local function submit_auth()
|
||||||
local v = key.get_value()
|
local v = key.get_value()
|
||||||
@ -468,20 +475,20 @@ local function config_view(display)
|
|||||||
|
|
||||||
local log_c_1 = Div{parent=log_cfg,x=2,y=4,width=49}
|
local log_c_1 = Div{parent=log_cfg,x=2,y=4,width=49}
|
||||||
|
|
||||||
TextBox{parent=log_cfg,x=1,y=2,height=1,text=" Logging Configuration",fg_bg=cpair(colors.black,colors.pink)}
|
TextBox{parent=log_cfg,x=1,y=2,text=" Logging Configuration",fg_bg=cpair(colors.black,colors.pink)}
|
||||||
|
|
||||||
TextBox{parent=log_c_1,x=1,y=1,height=1,text="Please configure logging below."}
|
TextBox{parent=log_c_1,x=1,y=1,text="Please configure logging below."}
|
||||||
|
|
||||||
TextBox{parent=log_c_1,x=1,y=3,height=1,text="Log File Mode"}
|
TextBox{parent=log_c_1,x=1,y=3,text="Log File Mode"}
|
||||||
local mode = RadioButton{parent=log_c_1,x=1,y=4,default=ini_cfg.LogMode+1,options={"Append on Startup","Replace on Startup"},callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.pink}
|
local mode = RadioButton{parent=log_c_1,x=1,y=4,default=ini_cfg.LogMode+1,options={"Append on Startup","Replace on Startup"},callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.pink}
|
||||||
|
|
||||||
TextBox{parent=log_c_1,x=1,y=7,height=1,text="Log File Path"}
|
TextBox{parent=log_c_1,x=1,y=7,text="Log File Path"}
|
||||||
local path = TextField{parent=log_c_1,x=1,y=8,width=49,height=1,value=ini_cfg.LogPath,max_len=128,fg_bg=bw_fg_bg}
|
local path = TextField{parent=log_c_1,x=1,y=8,width=49,height=1,value=ini_cfg.LogPath,max_len=128,fg_bg=bw_fg_bg}
|
||||||
|
|
||||||
local en_dbg = CheckBox{parent=log_c_1,x=1,y=10,default=ini_cfg.LogDebug,label="Enable Logging Debug Messages",box_fg_bg=cpair(colors.pink,colors.black)}
|
local en_dbg = CheckBox{parent=log_c_1,x=1,y=10,default=ini_cfg.LogDebug,label="Enable Logging Debug Messages",box_fg_bg=cpair(colors.pink,colors.black)}
|
||||||
TextBox{parent=log_c_1,x=3,y=11,height=2,text="This results in much larger log files. It is best to only use this when there is a problem.",fg_bg=g_lg_fg_bg}
|
TextBox{parent=log_c_1,x=3,y=11,height=2,text="This results in much larger log files. It is best to only use this when there is a problem.",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
local path_err = TextBox{parent=log_c_1,x=8,y=14,height=1,width=35,text="Please provide a log file path.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
local path_err = TextBox{parent=log_c_1,x=8,y=14,width=35,text="Please provide a log file path.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||||
|
|
||||||
local function submit_log()
|
local function submit_log()
|
||||||
if path.get_value() ~= "" then
|
if path.get_value() ~= "" then
|
||||||
@ -509,20 +516,35 @@ local function config_view(display)
|
|||||||
|
|
||||||
local clr_pane = MultiPane{parent=clr_cfg,x=1,y=4,panes={clr_c_1,clr_c_2,clr_c_3,clr_c_4}}
|
local clr_pane = MultiPane{parent=clr_cfg,x=1,y=4,panes={clr_c_1,clr_c_2,clr_c_3,clr_c_4}}
|
||||||
|
|
||||||
TextBox{parent=clr_cfg,x=1,y=2,height=1,text=" Color Configuration",fg_bg=cpair(colors.black,colors.magenta)}
|
TextBox{parent=clr_cfg,x=1,y=2,text=" Color Configuration",fg_bg=cpair(colors.black,colors.magenta)}
|
||||||
|
|
||||||
TextBox{parent=clr_c_1,x=1,y=1,height=2,text="Here you can select the color theme for the front panel."}
|
TextBox{parent=clr_c_1,x=1,y=1,height=2,text="Here you can select the color theme for the front panel."}
|
||||||
TextBox{parent=clr_c_1,x=1,y=4,height=2,text="Click 'Accessibility' below to access colorblind assistive options.",fg_bg=g_lg_fg_bg}
|
TextBox{parent=clr_c_1,x=1,y=4,height=2,text="Click 'Accessibility' below to access colorblind assistive options.",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=clr_c_1,x=1,y=7,height=1,text="Front Panel Theme"}
|
TextBox{parent=clr_c_1,x=1,y=7,text="Front Panel Theme"}
|
||||||
local fp_theme = RadioButton{parent=clr_c_1,x=1,y=8,default=ini_cfg.FrontPanelTheme,options=themes.FP_THEME_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.magenta}
|
local fp_theme = RadioButton{parent=clr_c_1,x=1,y=8,default=ini_cfg.FrontPanelTheme,options=themes.FP_THEME_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.magenta}
|
||||||
|
|
||||||
TextBox{parent=clr_c_2,x=1,y=1,height=6,text="By default, this project uses green/red heavily to distinguish ok and not, with some indicators also using multiple colors. By selecting a color blindness below, blues will be used instead of greens on indicators and multi-color indicators will be split up as space permits."}
|
TextBox{parent=clr_c_2,x=1,y=1,height=6,text="This system uses color heavily to distinguish ok and not, with some indicators using many colors. By selecting a mode below, indicators will change as shown. For non-standard modes, indicators with more than two colors will be split up."}
|
||||||
|
|
||||||
|
TextBox{parent=clr_c_2,x=21,y=7,text="Preview"}
|
||||||
|
local _ = IndLight{parent=clr_c_2,x=21,y=8,label="Good",colors=cpair(colors.black,colors.green)}
|
||||||
|
_ = IndLight{parent=clr_c_2,x=21,y=9,label="Warning",colors=cpair(colors.black,colors.yellow)}
|
||||||
|
_ = IndLight{parent=clr_c_2,x=21,y=10,label="Bad",colors=cpair(colors.black,colors.red)}
|
||||||
|
local b_off = IndLight{parent=clr_c_2,x=21,y=11,label="Off",colors=cpair(colors.black,colors.black),hidden=true}
|
||||||
|
local g_off = IndLight{parent=clr_c_2,x=21,y=11,label="Off",colors=cpair(colors.gray,colors.gray),hidden=true}
|
||||||
|
|
||||||
local function recolor(value)
|
local function recolor(value)
|
||||||
local c = themes.smooth_stone.color_modes[value]
|
local c = themes.smooth_stone.color_modes[value]
|
||||||
|
|
||||||
if value == 1 then
|
if value == themes.COLOR_MODE.STANDARD or value == themes.COLOR_MODE.BLUE_IND then
|
||||||
|
b_off.hide()
|
||||||
|
g_off.show()
|
||||||
|
else
|
||||||
|
g_off.hide()
|
||||||
|
b_off.show()
|
||||||
|
end
|
||||||
|
|
||||||
|
if #c == 0 then
|
||||||
for i = 1, #style.colors do term.setPaletteColor(style.colors[i].c, style.colors[i].hex) end
|
for i = 1, #style.colors do term.setPaletteColor(style.colors[i].c, style.colors[i].hex) end
|
||||||
else
|
else
|
||||||
term.setPaletteColor(colors.green, c[1].hex)
|
term.setPaletteColor(colors.green, c[1].hex)
|
||||||
@ -531,20 +553,15 @@ local function config_view(display)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
TextBox{parent=clr_c_2,x=1,y=8,height=1,text="Color Mode"}
|
TextBox{parent=clr_c_2,x=1,y=7,width=10,text="Color Mode"}
|
||||||
local c_mode = RadioButton{parent=clr_c_2,x=1,y=9,default=ini_cfg.ColorMode,options=themes.COLOR_MODE_NAMES,callback=recolor,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.magenta}
|
local c_mode = RadioButton{parent=clr_c_2,x=1,y=8,default=ini_cfg.ColorMode,options=themes.COLOR_MODE_NAMES,callback=recolor,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.magenta}
|
||||||
|
|
||||||
TextBox{parent=clr_c_2,x=20,y=8,height=1,text="Preview"}
|
TextBox{parent=clr_c_2,x=21,y=13,height=2,width=18,text="Note: exact color varies by theme.",fg_bg=g_lg_fg_bg}
|
||||||
local _ = IndLight{parent=clr_c_2,x=20,y=9,label="Good",colors=cpair(colors.black,colors.green)}
|
|
||||||
_ = IndLight{parent=clr_c_2,x=20,y=10,label="Warning",colors=cpair(colors.black,colors.yellow)}
|
|
||||||
_ = IndLight{parent=clr_c_2,x=20,y=11,label="Bad",colors=cpair(colors.black,colors.red)}
|
|
||||||
|
|
||||||
TextBox{parent=clr_c_2,x=1,y=14,height=6,text="Note: exact color varies by theme.",fg_bg=g_lg_fg_bg}
|
|
||||||
|
|
||||||
PushButton{parent=clr_c_2,x=44,y=14,min_width=6,text="Done",callback=function()clr_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=clr_c_2,x=44,y=14,min_width=6,text="Done",callback=function()clr_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
local function back_from_colors()
|
local function back_from_colors()
|
||||||
main_pane.set_value(util.trinary(tool_ctl.jumped_to_color, 1, 4))
|
main_pane.set_value(tri(tool_ctl.jumped_to_color, 1, 4))
|
||||||
tool_ctl.jumped_to_color = false
|
tool_ctl.jumped_to_color = false
|
||||||
recolor(1)
|
recolor(1)
|
||||||
end
|
end
|
||||||
@ -586,7 +603,7 @@ local function config_view(display)
|
|||||||
|
|
||||||
tool_ctl.color_apply.hide(true)
|
tool_ctl.color_apply.hide(true)
|
||||||
|
|
||||||
TextBox{parent=clr_c_3,x=1,y=1,height=1,text="Settings saved!"}
|
TextBox{parent=clr_c_3,x=1,y=1,text="Settings saved!"}
|
||||||
PushButton{parent=clr_c_3,x=1,y=14,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray)}
|
PushButton{parent=clr_c_3,x=1,y=14,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray)}
|
||||||
PushButton{parent=clr_c_3,x=44,y=14,min_width=6,text="Home",callback=function()tool_ctl.go_home()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=clr_c_3,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}
|
||||||
|
|
||||||
@ -608,7 +625,7 @@ local function config_view(display)
|
|||||||
|
|
||||||
local sum_pane = MultiPane{parent=summary,x=1,y=4,panes={sum_c_1,sum_c_2,sum_c_3,sum_c_4,sum_c_5,sum_c_6,sum_c_7}}
|
local sum_pane = MultiPane{parent=summary,x=1,y=4,panes={sum_c_1,sum_c_2,sum_c_3,sum_c_4,sum_c_5,sum_c_6,sum_c_7}}
|
||||||
|
|
||||||
TextBox{parent=summary,x=1,y=2,height=1,text=" Summary",fg_bg=cpair(colors.black,colors.green)}
|
TextBox{parent=summary,x=1,y=2,text=" Summary",fg_bg=cpair(colors.black,colors.green)}
|
||||||
|
|
||||||
local setting_list = ListBox{parent=sum_c_1,x=1,y=1,height=12,width=49,scroll_height=100,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
|
local setting_list = ListBox{parent=sum_c_1,x=1,y=1,height=12,width=49,scroll_height=100,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
|
||||||
|
|
||||||
@ -633,8 +650,11 @@ local function config_view(display)
|
|||||||
|
|
||||||
---@param exclude_conns boolean? true to exclude saving peripheral/redstone connections
|
---@param exclude_conns boolean? true to exclude saving peripheral/redstone connections
|
||||||
local function save_and_continue(exclude_conns)
|
local function save_and_continue(exclude_conns)
|
||||||
for k, v in pairs(tmp_cfg) do
|
for _, field in ipairs(fields) do
|
||||||
if not (exclude_conns and (k == "Peripherals" or k == "Redstone")) then settings.set(k, v) end
|
local k, v = field[1], tmp_cfg[field[1]]
|
||||||
|
if not (exclude_conns and (k == "Peripherals" or k == "Redstone")) then
|
||||||
|
if v == nil then settings.unset(k) else settings.set(k, v) end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- always set these if missing
|
-- always set these if missing
|
||||||
@ -681,13 +701,13 @@ local function config_view(display)
|
|||||||
tool_ctl.settings_confirm = PushButton{parent=sum_c_1,x=41,y=14,min_width=9,text="Confirm",callback=function()sum_pane.set_value(2)end,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg}
|
tool_ctl.settings_confirm = PushButton{parent=sum_c_1,x=41,y=14,min_width=9,text="Confirm",callback=function()sum_pane.set_value(2)end,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg}
|
||||||
tool_ctl.settings_confirm.hide()
|
tool_ctl.settings_confirm.hide()
|
||||||
|
|
||||||
TextBox{parent=sum_c_2,x=1,y=1,height=1,text="The following peripherals will be imported:"}
|
TextBox{parent=sum_c_2,x=1,y=1,text="The following peripherals will be imported:"}
|
||||||
local peri_import_list = ListBox{parent=sum_c_2,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)}
|
local peri_import_list = ListBox{parent=sum_c_2,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)}
|
||||||
|
|
||||||
PushButton{parent=sum_c_2,x=1,y=14,text="\x1b Back",callback=function()sum_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=sum_c_2,x=1,y=14,text="\x1b Back",callback=function()sum_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
PushButton{parent=sum_c_2,x=41,y=14,min_width=9,text="Confirm",callback=function()sum_pane.set_value(3)end,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=sum_c_2,x=41,y=14,min_width=9,text="Confirm",callback=function()sum_pane.set_value(3)end,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=sum_c_3,x=1,y=1,height=1,text="The following redstone entries will be imported:"}
|
TextBox{parent=sum_c_3,x=1,y=1,text="The following redstone entries will be imported:"}
|
||||||
local rs_import_list = ListBox{parent=sum_c_3,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)}
|
local rs_import_list = ListBox{parent=sum_c_3,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)}
|
||||||
|
|
||||||
PushButton{parent=sum_c_3,x=1,y=14,text="\x1b Back",callback=function()sum_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=sum_c_3,x=1,y=14,text="\x1b Back",callback=function()sum_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
@ -703,7 +723,7 @@ local function config_view(display)
|
|||||||
show_rs_conns()
|
show_rs_conns()
|
||||||
end
|
end
|
||||||
|
|
||||||
TextBox{parent=sum_c_4,x=1,y=1,height=1,text="Settings saved!"}
|
TextBox{parent=sum_c_4,x=1,y=1,text="Settings saved!"}
|
||||||
TextBox{parent=sum_c_4,x=1,y=3,height=4,text="Remember to configure any peripherals or redstone that you have connected to this RTU gateway if you have not already done so, or if you have added, removed, or modified any of them."}
|
TextBox{parent=sum_c_4,x=1,y=3,height=4,text="Remember to configure any peripherals or redstone that you have connected to this RTU gateway if you have not already done so, or if you have added, removed, or modified any of them."}
|
||||||
PushButton{parent=sum_c_4,x=1,y=8,min_width=24,text="Peripheral Connections",callback=jump_peri_conns,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=sum_c_4,x=1,y=8,min_width=24,text="Peripheral Connections",callback=jump_peri_conns,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=btn_act_fg_bg}
|
||||||
PushButton{parent=sum_c_4,x=1,y=10,min_width=22,text="Redstone Connections",callback=jump_rs_conns,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=sum_c_4,x=1,y=10,min_width=22,text="Redstone Connections",callback=jump_rs_conns,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=btn_act_fg_bg}
|
||||||
@ -735,15 +755,15 @@ local function config_view(display)
|
|||||||
|
|
||||||
local cl = Div{parent=changelog,x=2,y=4,width=49}
|
local cl = Div{parent=changelog,x=2,y=4,width=49}
|
||||||
|
|
||||||
TextBox{parent=changelog,x=1,y=2,height=1,text=" Config Change Log",fg_bg=bw_fg_bg}
|
TextBox{parent=changelog,x=1,y=2,text=" Config Change Log",fg_bg=bw_fg_bg}
|
||||||
|
|
||||||
local c_log = ListBox{parent=cl,x=1,y=1,height=12,width=49,scroll_height=100,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
|
local c_log = ListBox{parent=cl,x=1,y=1,height=12,width=49,scroll_height=100,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
|
||||||
|
|
||||||
for _, change in ipairs(changes) do
|
for _, change in ipairs(changes) do
|
||||||
TextBox{parent=c_log,text=change[1],height=1,fg_bg=bw_fg_bg}
|
TextBox{parent=c_log,text=change[1],fg_bg=bw_fg_bg}
|
||||||
for _, v in ipairs(change[2]) do
|
for _, v in ipairs(change[2]) do
|
||||||
local e = Div{parent=c_log,height=#util.strwrap(v,46)}
|
local e = Div{parent=c_log,height=#util.strwrap(v,46)}
|
||||||
TextBox{parent=e,y=1,x=1,text="- ",height=1,fg_bg=cpair(colors.gray,colors.white)}
|
TextBox{parent=e,y=1,x=1,text="- ",fg_bg=cpair(colors.gray,colors.white)}
|
||||||
TextBox{parent=e,y=1,x=3,text=v,height=e.get_height(),fg_bg=cpair(colors.gray,colors.white)}
|
TextBox{parent=e,y=1,x=3,text=v,height=e.get_height(),fg_bg=cpair(colors.gray,colors.white)}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -764,7 +784,7 @@ local function config_view(display)
|
|||||||
|
|
||||||
local peri_pane = MultiPane{parent=peri_cfg,x=1,y=4,panes={peri_c_1,peri_c_2,peri_c_3,peri_c_4,peri_c_5,peri_c_6,peri_c_7}}
|
local peri_pane = MultiPane{parent=peri_cfg,x=1,y=4,panes={peri_c_1,peri_c_2,peri_c_3,peri_c_4,peri_c_5,peri_c_6,peri_c_7}}
|
||||||
|
|
||||||
TextBox{parent=peri_cfg,x=1,y=2,height=1,text=" Peripheral Connections",fg_bg=cpair(colors.black,colors.purple)}
|
TextBox{parent=peri_cfg,x=1,y=2,text=" Peripheral Connections",fg_bg=cpair(colors.black,colors.purple)}
|
||||||
|
|
||||||
local peri_list = ListBox{parent=peri_c_1,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 peri_list = ListBox{parent=peri_c_1,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)}
|
||||||
|
|
||||||
@ -790,7 +810,7 @@ local function config_view(display)
|
|||||||
PushButton{parent=peri_c_1,x=35,y=14,min_width=7,text="Add +",callback=function()peri_pane.set_value(2)end,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=peri_c_1,x=35,y=14,min_width=7,text="Add +",callback=function()peri_pane.set_value(2)end,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg}
|
||||||
PushButton{parent=peri_c_1,x=43,y=14,min_width=7,text="Apply",callback=peri_apply,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=peri_c_1,x=43,y=14,min_width=7,text="Apply",callback=peri_apply,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=peri_c_2,x=1,y=1,height=1,text="Select one of the below devices to use."}
|
TextBox{parent=peri_c_2,x=1,y=1,text="Select one of the below devices to use."}
|
||||||
|
|
||||||
tool_ctl.ppm_devs = ListBox{parent=peri_c_2,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)}
|
tool_ctl.ppm_devs = ListBox{parent=peri_c_2,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)}
|
||||||
|
|
||||||
@ -811,53 +831,35 @@ local function config_view(display)
|
|||||||
tool_ctl.p_name_msg.set_value("Configuring peripheral on '" .. name .. "':")
|
tool_ctl.p_name_msg.set_value("Configuring peripheral on '" .. name .. "':")
|
||||||
tool_ctl.p_desc_ext.set_value("")
|
tool_ctl.p_desc_ext.set_value("")
|
||||||
|
|
||||||
|
local function reposition(prompt, idx_x, idx_max, unit_x, unit_y, desc_y)
|
||||||
|
tool_ctl.p_prompt.set_value(prompt)
|
||||||
|
tool_ctl.p_idx.reposition(idx_x, 4)
|
||||||
|
tool_ctl.p_idx.enable()
|
||||||
|
tool_ctl.p_idx.set_max(idx_max)
|
||||||
|
tool_ctl.p_idx.show()
|
||||||
|
tool_ctl.p_unit.reposition(unit_x, unit_y)
|
||||||
|
tool_ctl.p_unit.enable()
|
||||||
|
tool_ctl.p_unit.show()
|
||||||
|
tool_ctl.p_desc.reposition(1, desc_y)
|
||||||
|
end
|
||||||
|
|
||||||
if type == "boilerValve" then
|
if type == "boilerValve" then
|
||||||
tool_ctl.p_prompt.set_value("This is the # boiler for reactor unit # .")
|
reposition("This is reactor unit # 's # boiler.", 31, 2, 23, 4, 7)
|
||||||
tool_ctl.p_idx.show()
|
|
||||||
tool_ctl.p_idx.redraw()
|
|
||||||
tool_ctl.p_idx.enable()
|
|
||||||
tool_ctl.p_idx.set_max(2)
|
|
||||||
tool_ctl.p_unit.reposition(44, 4)
|
|
||||||
tool_ctl.p_unit.enable()
|
|
||||||
tool_ctl.p_unit.show()
|
|
||||||
tool_ctl.p_assign_btn.hide(true)
|
tool_ctl.p_assign_btn.hide(true)
|
||||||
tool_ctl.p_assign_end.hide(true)
|
tool_ctl.p_desc.set_value("Each unit can have at most 2 boilers. Boiler #1 shows up first on the main display, followed by boiler #2 below it. The numberings are per unit (unit 1 and unit 2 would both have a boiler #1 if each had one boiler) and can be split amongst multiple RTUs (one has #1, another has #2).")
|
||||||
tool_ctl.p_desc.reposition(1, 7)
|
|
||||||
tool_ctl.p_desc.set_value("Each unit can have at most 2 boilers. Boiler #1 shows up first on the main display, followed by boiler #2 below it. These numberings are independent of which RTU they are connected to. For example, one RTU can have boiler #1 and another can have #2, but both cannot have #1.")
|
|
||||||
elseif type == "turbineValve" then
|
elseif type == "turbineValve" then
|
||||||
tool_ctl.p_prompt.set_value("This is the # turbine for reactor unit # .")
|
reposition("This is reactor unit # 's # turbine.", 31, 3, 23, 4, 7)
|
||||||
tool_ctl.p_idx.show()
|
|
||||||
tool_ctl.p_idx.redraw()
|
|
||||||
tool_ctl.p_idx.enable()
|
|
||||||
tool_ctl.p_idx.set_max(3)
|
|
||||||
tool_ctl.p_unit.reposition(45, 4)
|
|
||||||
tool_ctl.p_unit.enable()
|
|
||||||
tool_ctl.p_unit.show()
|
|
||||||
tool_ctl.p_assign_btn.hide(true)
|
tool_ctl.p_assign_btn.hide(true)
|
||||||
tool_ctl.p_assign_end.hide(true)
|
tool_ctl.p_desc.set_value("Each unit can have at most 3 turbines. Turbine #1 shows up first on the main display, followed by #2 then #3 below it. The numberings are per unit (unit 1 and unit 2 would both have a turbine #1) and can be split amongst multiple RTUs (one has #1, another has #2).")
|
||||||
tool_ctl.p_desc.reposition(1, 7)
|
|
||||||
tool_ctl.p_desc.set_value("Each unit can have at most 3 turbines. Turbine #1 shows up first on the main display, followed by #2 then #3 below it. These numberings are independent of which RTU they are connected to. For example, one RTU can have turbine #1 and another can have #2, but both cannot have #1.")
|
|
||||||
elseif type == "solarNeutronActivator" then
|
elseif type == "solarNeutronActivator" then
|
||||||
|
reposition("This SNA is for reactor unit # .", 46, 1, 31, 4, 7)
|
||||||
tool_ctl.p_idx.hide()
|
tool_ctl.p_idx.hide()
|
||||||
tool_ctl.p_prompt.set_value("This SNA is for reactor unit # .")
|
|
||||||
tool_ctl.p_unit.reposition(31, 4)
|
|
||||||
tool_ctl.p_unit.enable()
|
|
||||||
tool_ctl.p_unit.show()
|
|
||||||
tool_ctl.p_assign_btn.hide(true)
|
tool_ctl.p_assign_btn.hide(true)
|
||||||
tool_ctl.p_assign_end.hide(true)
|
|
||||||
tool_ctl.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.")
|
tool_ctl.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.")
|
||||||
elseif type == "dynamicValve" then
|
elseif type == "dynamicValve" then
|
||||||
tool_ctl.p_prompt.set_value("This is the # dynamic tank for...")
|
reposition("This is the below system's # dynamic tank.", 29, 4, 17, 6, 8)
|
||||||
tool_ctl.p_assign_btn.show()
|
tool_ctl.p_assign_btn.show()
|
||||||
tool_ctl.p_assign_btn.redraw()
|
tool_ctl.p_assign_btn.redraw()
|
||||||
tool_ctl.p_assign_end.show()
|
|
||||||
tool_ctl.p_assign_end.redraw()
|
|
||||||
tool_ctl.p_idx.show()
|
|
||||||
tool_ctl.p_idx.redraw()
|
|
||||||
tool_ctl.p_idx.set_max(4)
|
|
||||||
tool_ctl.p_unit.reposition(18, 6)
|
|
||||||
tool_ctl.p_unit.enable()
|
|
||||||
tool_ctl.p_unit.show()
|
|
||||||
|
|
||||||
if tool_ctl.p_assign_btn.get_value() == 1 then
|
if tool_ctl.p_assign_btn.get_value() == 1 then
|
||||||
tool_ctl.p_idx.enable()
|
tool_ctl.p_idx.enable()
|
||||||
@ -868,30 +870,19 @@ local function config_view(display)
|
|||||||
tool_ctl.p_unit.enable()
|
tool_ctl.p_unit.enable()
|
||||||
end
|
end
|
||||||
|
|
||||||
tool_ctl.p_desc.reposition(1, 8)
|
|
||||||
tool_ctl.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.")
|
tool_ctl.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" then
|
||||||
tool_ctl.p_prompt.set_value("This is the # environment detector for...")
|
reposition("This is the below system's # env. detector.", 29, 99, 17, 6, 8)
|
||||||
tool_ctl.p_assign_btn.show()
|
tool_ctl.p_assign_btn.show()
|
||||||
tool_ctl.p_assign_btn.redraw()
|
tool_ctl.p_assign_btn.redraw()
|
||||||
tool_ctl.p_assign_end.show()
|
|
||||||
tool_ctl.p_assign_end.redraw()
|
|
||||||
tool_ctl.p_idx.show()
|
|
||||||
tool_ctl.p_idx.redraw()
|
|
||||||
tool_ctl.p_idx.set_max(99)
|
|
||||||
tool_ctl.p_unit.reposition(18, 6)
|
|
||||||
tool_ctl.p_unit.enable()
|
|
||||||
tool_ctl.p_unit.show()
|
|
||||||
if tool_ctl.p_assign_btn.get_value() == 1 then tool_ctl.p_unit.disable() else tool_ctl.p_unit.enable() end
|
if tool_ctl.p_assign_btn.get_value() == 1 then tool_ctl.p_unit.disable() else tool_ctl.p_unit.enable() end
|
||||||
tool_ctl.p_desc.reposition(1, 8)
|
|
||||||
tool_ctl.p_desc.set_value("You can connect more than one environment detector for a particular unit or the facility. In that case, the maximum radiation reading from those assigned to that particular unit or the facility will be used for alarms and display.")
|
tool_ctl.p_desc.set_value("You can connect more than one environment detector for a particular unit or the facility. In that case, the maximum radiation reading from those assigned to that particular unit or the facility will be used for alarms and display.")
|
||||||
elseif type == "inductionPort" or type == "spsPort" then
|
elseif type == "inductionPort" or type == "spsPort" then
|
||||||
local dev = util.trinary(type == "inductionPort", "induction matrix", "SPS")
|
local dev = tri(type == "inductionPort", "induction matrix", "SPS")
|
||||||
tool_ctl.p_idx.hide(true)
|
tool_ctl.p_idx.hide(true)
|
||||||
tool_ctl.p_unit.hide(true)
|
tool_ctl.p_unit.hide(true)
|
||||||
tool_ctl.p_prompt.set_value("This is the " .. dev .. " for the facility.")
|
tool_ctl.p_prompt.set_value("This is the " .. dev .. " for the facility.")
|
||||||
tool_ctl.p_assign_btn.hide(true)
|
tool_ctl.p_assign_btn.hide(true)
|
||||||
tool_ctl.p_assign_end.hide(true)
|
|
||||||
tool_ctl.p_desc.reposition(1, 7)
|
tool_ctl.p_desc.reposition(1, 7)
|
||||||
tool_ctl.p_desc.set_value("There can only be one of these devices per SCADA network, so it will be assigned as the sole " .. dev .. " for the facility. There must only be one of these across all the RTUs you have.")
|
tool_ctl.p_desc.set_value("There can only be one of these devices per SCADA network, so it will be assigned as the sole " .. dev .. " for the facility. There must only be one of these across all the RTUs you have.")
|
||||||
else
|
else
|
||||||
@ -912,13 +903,13 @@ local function config_view(display)
|
|||||||
tool_ctl.ppm_devs.remove_all()
|
tool_ctl.ppm_devs.remove_all()
|
||||||
for name, entry in pairs(mounts) do
|
for name, entry in pairs(mounts) do
|
||||||
if util.table_contains(RTU_DEV_TYPES, entry.type) then
|
if util.table_contains(RTU_DEV_TYPES, entry.type) then
|
||||||
local bkg = util.trinary(alternate, colors.white, colors.lightGray)
|
local bkg = tri(alternate, colors.white, colors.lightGray)
|
||||||
|
|
||||||
---@cast entry ppm_entry
|
---@cast entry ppm_entry
|
||||||
local line = Div{parent=tool_ctl.ppm_devs,height=2,fg_bg=cpair(colors.black,bkg)}
|
local line = Div{parent=tool_ctl.ppm_devs,height=2,fg_bg=cpair(colors.black,bkg)}
|
||||||
PushButton{parent=line,x=1,y=1,min_width=9,alignment=LEFT,height=1,text="> SELECT",callback=function()tool_ctl.peri_cfg_manual=false;new_peri(name,entry.type)end,fg_bg=cpair(colors.black,colors.purple),active_fg_bg=cpair(colors.white,colors.black)}
|
PushButton{parent=line,x=1,y=1,min_width=9,alignment=LEFT,height=1,text="> SELECT",callback=function()tool_ctl.peri_cfg_manual=false;new_peri(name,entry.type)end,fg_bg=cpair(colors.black,colors.purple),active_fg_bg=cpair(colors.white,colors.black)}
|
||||||
TextBox{parent=line,x=11,y=1,height=1,text=name,fg_bg=cpair(colors.black,bkg)}
|
TextBox{parent=line,x=11,y=1,text=name,fg_bg=cpair(colors.black,bkg)}
|
||||||
TextBox{parent=line,x=11,y=2,height=1,text=entry.type,fg_bg=cpair(colors.gray,bkg)}
|
TextBox{parent=line,x=11,y=2,text=entry.type,fg_bg=cpair(colors.gray,bkg)}
|
||||||
|
|
||||||
alternate = not alternate
|
alternate = not alternate
|
||||||
end
|
end
|
||||||
@ -931,7 +922,7 @@ local function config_view(display)
|
|||||||
TextBox{parent=peri_c_3,x=1,y=6,height=4,text="Peripheral Name"}
|
TextBox{parent=peri_c_3,x=1,y=6,height=4,text="Peripheral Name"}
|
||||||
local p_name = TextField{parent=peri_c_3,x=1,y=7,width=49,height=1,max_len=128,fg_bg=bw_fg_bg}
|
local p_name = TextField{parent=peri_c_3,x=1,y=7,width=49,height=1,max_len=128,fg_bg=bw_fg_bg}
|
||||||
local p_type = Radio2D{parent=peri_c_3,x=1,y=9,rows=4,columns=2,default=1,options=RTU_DEV_TYPES,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.purple}
|
local p_type = Radio2D{parent=peri_c_3,x=1,y=9,rows=4,columns=2,default=1,options=RTU_DEV_TYPES,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.purple}
|
||||||
local man_p_err = TextBox{parent=peri_c_3,x=8,y=14,height=1,width=35,text="Please enter a peripheral name.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
local man_p_err = TextBox{parent=peri_c_3,x=8,y=14,width=35,text="Please enter a peripheral name.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||||
man_p_err.hide(true)
|
man_p_err.hide(true)
|
||||||
|
|
||||||
local function submit_manual_peri()
|
local function submit_manual_peri()
|
||||||
@ -948,11 +939,10 @@ local function config_view(display)
|
|||||||
|
|
||||||
tool_ctl.p_name_msg = TextBox{parent=peri_c_4,x=1,y=1,height=2,text=""}
|
tool_ctl.p_name_msg = TextBox{parent=peri_c_4,x=1,y=1,height=2,text=""}
|
||||||
tool_ctl.p_prompt = TextBox{parent=peri_c_4,x=1,y=4,height=2,text=""}
|
tool_ctl.p_prompt = TextBox{parent=peri_c_4,x=1,y=4,height=2,text=""}
|
||||||
tool_ctl.p_idx = NumberField{parent=peri_c_4,x=14,y=4,width=4,max_chars=2,min=1,max=2,default=1,fg_bg=bw_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)}
|
tool_ctl.p_idx = NumberField{parent=peri_c_4,x=31,y=4,width=4,max_chars=2,min=1,max=2,default=1,fg_bg=bw_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)}
|
||||||
tool_ctl.p_assign_btn = RadioButton{parent=peri_c_4,x=1,y=5,default=1,options={"the facility.","a unit. (unit #"},callback=function(v)tool_ctl.p_assign(v)end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.purple}
|
tool_ctl.p_assign_btn = RadioButton{parent=peri_c_4,x=1,y=5,default=1,options={"the facility","reactor unit #"},callback=function(v)tool_ctl.p_assign(v)end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.purple}
|
||||||
tool_ctl.p_assign_end = TextBox{parent=peri_c_4,x=22,y=6,height=6,width=1,text=")"}
|
|
||||||
|
|
||||||
tool_ctl.p_unit = NumberField{parent=peri_c_4,x=44,y=4,width=4,max_chars=2,min=1,max=4,default=1,fg_bg=bw_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)}
|
tool_ctl.p_unit = NumberField{parent=peri_c_4,x=23,y=4,width=4,max_chars=2,min=1,max=4,default=1,fg_bg=bw_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)}
|
||||||
tool_ctl.p_unit.disable()
|
tool_ctl.p_unit.disable()
|
||||||
|
|
||||||
function tool_ctl.p_assign(opt)
|
function tool_ctl.p_assign(opt)
|
||||||
@ -971,7 +961,7 @@ local function config_view(display)
|
|||||||
tool_ctl.p_desc = TextBox{parent=peri_c_4,x=1,y=7,height=6,text="",fg_bg=g_lg_fg_bg}
|
tool_ctl.p_desc = TextBox{parent=peri_c_4,x=1,y=7,height=6,text="",fg_bg=g_lg_fg_bg}
|
||||||
tool_ctl.p_desc_ext = TextBox{parent=peri_c_4,x=1,y=6,height=7,text="",fg_bg=g_lg_fg_bg}
|
tool_ctl.p_desc_ext = TextBox{parent=peri_c_4,x=1,y=6,height=7,text="",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
tool_ctl.p_err = TextBox{parent=peri_c_4,x=8,y=14,height=1,width=32,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
tool_ctl.p_err = TextBox{parent=peri_c_4,x=8,y=14,width=32,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||||
tool_ctl.p_err.hide(true)
|
tool_ctl.p_err.hide(true)
|
||||||
|
|
||||||
local function back_from_peri_opts()
|
local function back_from_peri_opts()
|
||||||
@ -1056,7 +1046,7 @@ local function config_view(display)
|
|||||||
PushButton{parent=peri_c_4,x=1,y=14,text="\x1b Back",callback=back_from_peri_opts,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=peri_c_4,x=1,y=14,text="\x1b Back",callback=back_from_peri_opts,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
PushButton{parent=peri_c_4,x=41,y=14,min_width=9,text="Confirm",callback=save_peri_entry,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=peri_c_4,x=41,y=14,min_width=9,text="Confirm",callback=save_peri_entry,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=peri_c_5,x=1,y=1,height=1,text="Settings saved!"}
|
TextBox{parent=peri_c_5,x=1,y=1,text="Settings saved!"}
|
||||||
PushButton{parent=peri_c_5,x=1,y=14,text="\x1b Back",callback=function()peri_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=peri_c_5,x=1,y=14,text="\x1b Back",callback=function()peri_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
PushButton{parent=peri_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=peri_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}
|
||||||
|
|
||||||
@ -1074,12 +1064,13 @@ local function config_view(display)
|
|||||||
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_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}}
|
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}}
|
||||||
|
|
||||||
TextBox{parent=rs_cfg,x=1,y=2,height=1,text=" Redstone Connections",fg_bg=cpair(colors.black,colors.red)}
|
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,height=1,text=" port side/color unit/facility",fg_bg=g_lg_fg_bg}
|
TextBox{parent=rs_c_1,x=1,y=1,text=" port side/color unit/facility",fg_bg=g_lg_fg_bg}
|
||||||
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)}
|
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)}
|
||||||
|
|
||||||
local function rs_revert()
|
local function rs_revert()
|
||||||
@ -1107,7 +1098,7 @@ local function config_view(display)
|
|||||||
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."}
|
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_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=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}
|
||||||
|
|
||||||
TextBox{parent=rs_c_2,x=1,y=1,height=1,text="Select one of the below ports to use."}
|
TextBox{parent=rs_c_2,x=1,y=1,text="Select one of the below ports to use."}
|
||||||
|
|
||||||
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)}
|
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)}
|
||||||
|
|
||||||
@ -1132,9 +1123,23 @@ local function config_view(display)
|
|||||||
text = "You selected the ALL_WASTE shortcut."
|
text = "You selected the ALL_WASTE shortcut."
|
||||||
else
|
else
|
||||||
tool_ctl.rs_cfg_shortcut.hide(true)
|
tool_ctl.rs_cfg_shortcut.hide(true)
|
||||||
tool_ctl.rs_cfg_side_l.set_value(util.trinary(rsio.get_io_dir(port) == rsio.IO_DIR.IN, "Input Side", "Output Side"))
|
tool_ctl.rs_cfg_side_l.set_value(tri(rsio.get_io_dir(port) == rsio.IO_DIR.IN, "Input Side", "Output Side"))
|
||||||
tool_ctl.rs_cfg_color.show()
|
tool_ctl.rs_cfg_color.show()
|
||||||
text = "You selected " .. rsio.to_string(port) .. " (for "
|
|
||||||
|
local io_type = "analog input "
|
||||||
|
local io_mode = rsio.get_io_mode(port)
|
||||||
|
local inv = tri(rsio.digital_is_active(port, IO_LVL.LOW) == true, "inverted ", "")
|
||||||
|
|
||||||
|
if io_mode == IO_MODE.DIGITAL_IN then
|
||||||
|
io_type = inv .. "digital input "
|
||||||
|
elseif io_mode == IO_MODE.DIGITAL_OUT then
|
||||||
|
io_type = inv .. "digital output "
|
||||||
|
elseif io_mode == IO_MODE.ANALOG_OUT then
|
||||||
|
io_type = "analog output "
|
||||||
|
end
|
||||||
|
|
||||||
|
text = "You selected the " .. io_type .. rsio.to_string(port) .. " (for "
|
||||||
|
|
||||||
if PORT_DSGN[port] == 1 then
|
if PORT_DSGN[port] == 1 then
|
||||||
text = text .. "a unit)."
|
text = text .. "a unit)."
|
||||||
tool_ctl.rs_cfg_unit_l.show()
|
tool_ctl.rs_cfg_unit_l.show()
|
||||||
@ -1154,27 +1159,37 @@ local function config_view(display)
|
|||||||
-- add entries to redstone option list
|
-- add entries to redstone option list
|
||||||
local all_w_macro = Div{parent=rs_ports,height=1}
|
local all_w_macro = Div{parent=rs_ports,height=1}
|
||||||
PushButton{parent=all_w_macro,x=1,y=1,min_width=14,alignment=LEFT,height=1,text=">ALL_WASTE",callback=function()new_rs(-1)end,fg_bg=cpair(colors.black,colors.green),active_fg_bg=cpair(colors.white,colors.black)}
|
PushButton{parent=all_w_macro,x=1,y=1,min_width=14,alignment=LEFT,height=1,text=">ALL_WASTE",callback=function()new_rs(-1)end,fg_bg=cpair(colors.black,colors.green),active_fg_bg=cpair(colors.white,colors.black)}
|
||||||
TextBox{parent=all_w_macro,x=16,y=1,width=5,height=1,text="[n/a]",fg_bg=cpair(colors.lightGray,colors.white)}
|
TextBox{parent=all_w_macro,x=16,y=1,width=5,text="[n/a]",fg_bg=cpair(colors.lightGray,colors.white)}
|
||||||
TextBox{parent=all_w_macro,x=22,y=1,height=1,text="Create all 4 waste entries",fg_bg=cpair(colors.gray,colors.white)}
|
TextBox{parent=all_w_macro,x=22,y=1,text="Create all 4 waste entries",fg_bg=cpair(colors.gray,colors.white)}
|
||||||
|
|
||||||
for i = 1, rsio.NUM_PORTS do
|
for i = 1, rsio.NUM_PORTS do
|
||||||
local name = rsio.to_string(i)
|
local p = PORT_DESC_MAP[i][1]
|
||||||
local io_dir = util.trinary(rsio.get_io_dir(i) == rsio.IO_DIR.IN, "[in]", "[out]")
|
local name = rsio.to_string(p)
|
||||||
local btn_color = util.trinary(rsio.get_io_dir(i) == rsio.IO_DIR.IN, colors.yellow, colors.lightBlue)
|
local io_dir = tri(rsio.get_io_dir(p) == rsio.IO_DIR.IN, "[in]", "[out]")
|
||||||
|
local btn_color = tri(rsio.get_io_dir(p) == rsio.IO_DIR.IN, colors.yellow, colors.lightBlue)
|
||||||
|
|
||||||
local entry = Div{parent=rs_ports,height=1}
|
local entry = Div{parent=rs_ports,height=1}
|
||||||
PushButton{parent=entry,x=1,y=1,min_width=14,alignment=LEFT,height=1,text=">"..name,callback=function()new_rs(i)end,fg_bg=cpair(colors.black,btn_color),active_fg_bg=cpair(colors.white,colors.black)}
|
PushButton{parent=entry,x=1,y=1,min_width=14,alignment=LEFT,height=1,text=">"..name,callback=function()new_rs(p)end,fg_bg=cpair(colors.black,btn_color),active_fg_bg=cpair(colors.white,colors.black)}
|
||||||
TextBox{parent=entry,x=16,y=1,width=5,height=1,text=io_dir,fg_bg=cpair(colors.lightGray,colors.white)}
|
TextBox{parent=entry,x=16,y=1,width=5,text=io_dir,fg_bg=cpair(colors.lightGray,colors.white)}
|
||||||
TextBox{parent=entry,x=22,y=1,height=1,text=PORT_DESC[i],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_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}
|
||||||
|
|
||||||
tool_ctl.rs_cfg_selection = TextBox{parent=rs_c_3,x=1,y=1,height=1,text=""}
|
tool_ctl.rs_cfg_selection = TextBox{parent=rs_c_3,x=1,y=1,height=2,text=""}
|
||||||
|
|
||||||
tool_ctl.rs_cfg_unit_l = TextBox{parent=rs_c_3,x=27,y=3,width=7,height=1,text="Unit ID"}
|
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}
|
||||||
tool_ctl.rs_cfg_unit = NumberField{parent=rs_c_3,x=27,y=4,width=10,max_chars=2,min=1,max=4,fg_bg=bw_fg_bg}
|
|
||||||
|
|
||||||
tool_ctl.rs_cfg_side_l = TextBox{parent=rs_c_3,x=1,y=3,width=11,height=1,text="Output Side"}
|
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"}
|
||||||
local side = Radio2D{parent=rs_c_3,x=1,y=4,rows=2,columns=3,default=1,options=side_options,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.red}
|
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}
|
||||||
|
|
||||||
|
tool_ctl.rs_cfg_side_l = TextBox{parent=rs_c_3,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}
|
||||||
|
|
||||||
|
tool_ctl.rs_cfg_unit_l = TextBox{parent=rs_c_3,x=25,y=7,width=7,text="Unit ID"}
|
||||||
|
tool_ctl.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}
|
||||||
|
|
||||||
local function set_bundled(bundled)
|
local function set_bundled(bundled)
|
||||||
if bundled then tool_ctl.rs_cfg_color.enable() else tool_ctl.rs_cfg_color.disable() end
|
if bundled then tool_ctl.rs_cfg_color.enable() else tool_ctl.rs_cfg_color.disable() end
|
||||||
@ -1187,7 +1202,7 @@ local function config_view(display)
|
|||||||
tool_ctl.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}
|
tool_ctl.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}
|
||||||
tool_ctl.rs_cfg_color.disable()
|
tool_ctl.rs_cfg_color.disable()
|
||||||
|
|
||||||
local rs_err = TextBox{parent=rs_c_3,x=8,y=14,height=1,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_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}
|
||||||
rs_err.hide(true)
|
rs_err.hide(true)
|
||||||
|
|
||||||
local function back_from_rs_opts()
|
local function back_from_rs_opts()
|
||||||
@ -1205,10 +1220,10 @@ local function config_view(display)
|
|||||||
if port >= 0 then
|
if port >= 0 then
|
||||||
---@type rtu_rs_definition
|
---@type rtu_rs_definition
|
||||||
local def = {
|
local def = {
|
||||||
unit = util.trinary(PORT_DSGN[port] == 1, u, nil),
|
unit = tri(PORT_DSGN[port] == 1, u, nil),
|
||||||
port = port,
|
port = port,
|
||||||
side = side_options_map[side.get_value()],
|
side = side_options_map[side.get_value()],
|
||||||
color = util.trinary(bundled.get_value(), color_options_map[tool_ctl.rs_cfg_color.get_value()], nil)
|
color = tri(bundled.get_value(), color_options_map[tool_ctl.rs_cfg_color.get_value()], nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
if tool_ctl.rs_cfg_editing == false then
|
if tool_ctl.rs_cfg_editing == false then
|
||||||
@ -1222,10 +1237,10 @@ local function config_view(display)
|
|||||||
local default_colors = { colors.red, colors.orange, colors.yellow, colors.lime }
|
local default_colors = { colors.red, colors.orange, colors.yellow, colors.lime }
|
||||||
for i = 0, 3 do
|
for i = 0, 3 do
|
||||||
table.insert(tmp_cfg.Redstone, {
|
table.insert(tmp_cfg.Redstone, {
|
||||||
unit = util.trinary(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 = util.trinary(bundled.get_value(), side_options_map[side.get_value()], default_sides[i + 1]),
|
side = tri(bundled.get_value(), side_options_map[side.get_value()], default_sides[i + 1]),
|
||||||
color = util.trinary(bundled.get_value(), default_colors[i + 1], nil)
|
color = tri(bundled.get_value(), default_colors[i + 1], nil)
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -1243,7 +1258,7 @@ local function config_view(display)
|
|||||||
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_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_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}
|
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}
|
||||||
|
|
||||||
TextBox{parent=rs_c_4,x=1,y=1,height=1,text="Settings saved!"}
|
TextBox{parent=rs_c_4,x=1,y=1,text="Settings saved!"}
|
||||||
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=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}
|
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}
|
||||||
|
|
||||||
@ -1278,7 +1293,7 @@ local function config_view(display)
|
|||||||
peri_import_list.remove_all()
|
peri_import_list.remove_all()
|
||||||
for _, entry in ipairs(config.RTU_DEVICES) do
|
for _, entry in ipairs(config.RTU_DEVICES) do
|
||||||
local for_facility = entry.for_reactor == 0
|
local for_facility = entry.for_reactor == 0
|
||||||
local ini_unit = util.trinary(for_facility, nil, entry.for_reactor)
|
local ini_unit = tri(for_facility, nil, entry.for_reactor)
|
||||||
|
|
||||||
local def = { name = entry.name, unit = ini_unit, index = entry.index }
|
local def = { name = entry.name, unit = ini_unit, index = entry.index }
|
||||||
local mount = mounts[def.name] ---@type ppm_entry|nil
|
local mount = mounts[def.name] ---@type ppm_entry|nil
|
||||||
@ -1344,9 +1359,9 @@ local function config_view(display)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local line = Div{parent=peri_import_list,height=3}
|
local line = Div{parent=peri_import_list,height=3}
|
||||||
TextBox{parent=line,x=1,y=1,height=1,text="@ "..def.name,fg_bg=cpair(colors.black,colors.white)}
|
TextBox{parent=line,x=1,y=1,text="@ "..def.name,fg_bg=cpair(colors.black,colors.white)}
|
||||||
TextBox{parent=line,x=1,y=2,height=1,text=status,fg_bg=cpair(color,colors.white)}
|
TextBox{parent=line,x=1,y=2,text=status,fg_bg=cpair(color,colors.white)}
|
||||||
TextBox{parent=line,x=1,y=3,height=1,text=desc,fg_bg=cpair(colors.gray,colors.white)}
|
TextBox{parent=line,x=1,y=3,text=desc,fg_bg=cpair(colors.gray,colors.white)}
|
||||||
end
|
end
|
||||||
|
|
||||||
rs_import_list.remove_all()
|
rs_import_list.remove_all()
|
||||||
@ -1357,7 +1372,7 @@ local function config_view(display)
|
|||||||
table.insert(tmp_cfg.Redstone, def)
|
table.insert(tmp_cfg.Redstone, def)
|
||||||
|
|
||||||
local name = rsio.to_string(def.port)
|
local name = rsio.to_string(def.port)
|
||||||
local io_dir = util.trinary(rsio.get_io_dir(def.port) == rsio.IO_DIR.IN, "\x1a", "\x1b")
|
local io_dir = tri(rsio.get_io_dir(def.port) == rsio.IO_DIR.IN, "\x1a", "\x1b")
|
||||||
local conn = def.side
|
local conn = def.side
|
||||||
local unit = "facility"
|
local unit = "facility"
|
||||||
|
|
||||||
@ -1365,10 +1380,10 @@ local function config_view(display)
|
|||||||
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 line = Div{parent=rs_import_list,height=1}
|
local line = Div{parent=rs_import_list,height=1}
|
||||||
TextBox{parent=line,x=1,y=1,width=1,height=1,text=io_dir,fg_bg=cpair(colors.lightGray,colors.white)}
|
TextBox{parent=line,x=1,y=1,width=1,text=io_dir,fg_bg=cpair(colors.lightGray,colors.white)}
|
||||||
TextBox{parent=line,x=2,y=1,width=14,height=1,text=name}
|
TextBox{parent=line,x=2,y=1,width=14,text=name}
|
||||||
TextBox{parent=line,x=18,y=1,width=string.len(conn),height=1,text=conn,fg_bg=cpair(colors.gray,colors.white)}
|
TextBox{parent=line,x=18,y=1,width=string.len(conn),text=conn,fg_bg=cpair(colors.gray,colors.white)}
|
||||||
TextBox{parent=line,x=40,y=1,height=1,text=unit,fg_bg=cpair(colors.gray,colors.white)}
|
TextBox{parent=line,x=40,y=1,text=unit,fg_bg=cpair(colors.gray,colors.white)}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -1420,7 +1435,7 @@ local function config_view(display)
|
|||||||
local val = util.strval(raw)
|
local val = util.strval(raw)
|
||||||
|
|
||||||
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 = tri(raw == log.MODE.APPEND, "append", "replace")
|
||||||
elseif f[1] == "FrontPanelTheme" then
|
elseif f[1] == "FrontPanelTheme" then
|
||||||
val = util.strval(themes.fp_theme_name(raw))
|
val = util.strval(themes.fp_theme_name(raw))
|
||||||
elseif f[1] == "ColorMode" then
|
elseif f[1] == "ColorMode" then
|
||||||
@ -1429,7 +1444,7 @@ local function config_view(display)
|
|||||||
|
|
||||||
if val == "nil" then val = "<not set>" end
|
if val == "nil" then val = "<not set>" end
|
||||||
|
|
||||||
local c = util.trinary(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 then
|
||||||
@ -1503,9 +1518,9 @@ local function config_view(display)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local entry = Div{parent=peri_list,height=3}
|
local entry = Div{parent=peri_list,height=3}
|
||||||
TextBox{parent=entry,x=1,y=1,height=1,text="@ "..def.name,fg_bg=cpair(colors.black,colors.white)}
|
TextBox{parent=entry,x=1,y=1,text="@ "..def.name,fg_bg=cpair(colors.black,colors.white)}
|
||||||
TextBox{parent=entry,x=1,y=2,height=1,text=" \x1a "..t_str,fg_bg=cpair(colors.gray,colors.white)}
|
TextBox{parent=entry,x=1,y=2,text=" \x1a "..t_str,fg_bg=cpair(colors.gray,colors.white)}
|
||||||
TextBox{parent=entry,x=1,y=3,height=1,text=desc,fg_bg=cpair(colors.gray,colors.white)}
|
TextBox{parent=entry,x=1,y=3,text=desc,fg_bg=cpair(colors.gray,colors.white)}
|
||||||
local edit_btn = PushButton{parent=entry,x=41,y=2,min_width=8,height=1,text="EDIT",callback=function()edit_peri_entry(i,def,t or "")end,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)}
|
local edit_btn = PushButton{parent=entry,x=41,y=2,min_width=8,height=1,text="EDIT",callback=function()edit_peri_entry(i,def,t or "")end,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)}
|
||||||
PushButton{parent=entry,x=41,y=3,min_width=8,height=1,text="DELETE",callback=function()delete_peri_entry(i)end,fg_bg=cpair(colors.black,colors.red),active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=entry,x=41,y=3,min_width=8,height=1,text="DELETE",callback=function()delete_peri_entry(i)end,fg_bg=cpair(colors.black,colors.red),active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
@ -1543,7 +1558,7 @@ local function config_view(display)
|
|||||||
end
|
end
|
||||||
|
|
||||||
tool_ctl.rs_cfg_selection.set_value(text)
|
tool_ctl.rs_cfg_selection.set_value(text)
|
||||||
tool_ctl.rs_cfg_side_l.set_value(util.trinary(rsio.get_io_dir(def.port) == rsio.IO_DIR.IN, "Input Side", "Output Side"))
|
tool_ctl.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)
|
bundled.set_value(def.color ~= nil)
|
||||||
tool_ctl.rs_cfg_color.set_value(value)
|
tool_ctl.rs_cfg_color.set_value(value)
|
||||||
@ -1564,17 +1579,17 @@ local function config_view(display)
|
|||||||
local def = cfg.Redstone[i] ---@type rtu_rs_definition
|
local def = cfg.Redstone[i] ---@type rtu_rs_definition
|
||||||
|
|
||||||
local name = rsio.to_string(def.port)
|
local name = rsio.to_string(def.port)
|
||||||
local io_dir = util.trinary(rsio.get_io_mode(def.port) == rsio.IO_DIR.IN, "\x1a", "\x1b")
|
local io_dir = tri(rsio.get_io_mode(def.port) == rsio.IO_DIR.IN, "\x1a", "\x1b")
|
||||||
local conn = def.side
|
local conn = def.side
|
||||||
local unit = util.strval(def.unit or "F")
|
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,height=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(colors.lightGray,colors.white)}
|
||||||
TextBox{parent=entry,x=2,y=1,width=14,height=1,text=name}
|
TextBox{parent=entry,x=2,y=1,width=14,text=name}
|
||||||
TextBox{parent=entry,x=16,y=1,width=string.len(conn),height=1,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,height=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}
|
||||||
end
|
end
|
||||||
@ -1627,9 +1642,11 @@ function configurator.configure(ask_config)
|
|||||||
elseif event == "paste" then
|
elseif event == "paste" then
|
||||||
display.handle_paste(param1)
|
display.handle_paste(param1)
|
||||||
elseif event == "peripheral_detach" then
|
elseif event == "peripheral_detach" then
|
||||||
|
---@diagnostic disable-next-line: discard-returns
|
||||||
ppm.handle_unmount(param1)
|
ppm.handle_unmount(param1)
|
||||||
tool_ctl.update_peri_list()
|
tool_ctl.update_peri_list()
|
||||||
elseif event == "peripheral" then
|
elseif event == "peripheral" then
|
||||||
|
---@diagnostic disable-next-line: discard-returns
|
||||||
ppm.mount(param1)
|
ppm.mount(param1)
|
||||||
tool_ctl.update_peri_list()
|
tool_ctl.update_peri_list()
|
||||||
end
|
end
|
||||||
|
@ -9,58 +9,49 @@ local boilerv_rtu = {}
|
|||||||
function boilerv_rtu.new(boiler)
|
function boilerv_rtu.new(boiler)
|
||||||
local unit = rtu.init_unit(boiler)
|
local unit = rtu.init_unit(boiler)
|
||||||
|
|
||||||
-- disable auto fault clearing
|
|
||||||
boiler.__p_clear_fault()
|
|
||||||
boiler.__p_disable_afc()
|
|
||||||
|
|
||||||
-- discrete inputs --
|
-- discrete inputs --
|
||||||
unit.connect_di(boiler.isFormed)
|
unit.connect_di("isFormed")
|
||||||
|
|
||||||
-- coils --
|
-- coils --
|
||||||
-- none
|
-- none
|
||||||
|
|
||||||
-- input registers --
|
-- input registers --
|
||||||
-- multiblock properties
|
-- multiblock properties
|
||||||
unit.connect_input_reg(boiler.getLength)
|
unit.connect_input_reg("getLength")
|
||||||
unit.connect_input_reg(boiler.getWidth)
|
unit.connect_input_reg("getWidth")
|
||||||
unit.connect_input_reg(boiler.getHeight)
|
unit.connect_input_reg("getHeight")
|
||||||
unit.connect_input_reg(boiler.getMinPos)
|
unit.connect_input_reg("getMinPos")
|
||||||
unit.connect_input_reg(boiler.getMaxPos)
|
unit.connect_input_reg("getMaxPos")
|
||||||
-- build properties
|
-- build properties
|
||||||
unit.connect_input_reg(boiler.getBoilCapacity)
|
unit.connect_input_reg("getBoilCapacity")
|
||||||
unit.connect_input_reg(boiler.getSteamCapacity)
|
unit.connect_input_reg("getSteamCapacity")
|
||||||
unit.connect_input_reg(boiler.getWaterCapacity)
|
unit.connect_input_reg("getWaterCapacity")
|
||||||
unit.connect_input_reg(boiler.getHeatedCoolantCapacity)
|
unit.connect_input_reg("getHeatedCoolantCapacity")
|
||||||
unit.connect_input_reg(boiler.getCooledCoolantCapacity)
|
unit.connect_input_reg("getCooledCoolantCapacity")
|
||||||
unit.connect_input_reg(boiler.getSuperheaters)
|
unit.connect_input_reg("getSuperheaters")
|
||||||
unit.connect_input_reg(boiler.getMaxBoilRate)
|
unit.connect_input_reg("getMaxBoilRate")
|
||||||
-- current state
|
-- current state
|
||||||
unit.connect_input_reg(boiler.getTemperature)
|
unit.connect_input_reg("getTemperature")
|
||||||
unit.connect_input_reg(boiler.getBoilRate)
|
unit.connect_input_reg("getBoilRate")
|
||||||
unit.connect_input_reg(boiler.getEnvironmentalLoss)
|
unit.connect_input_reg("getEnvironmentalLoss")
|
||||||
-- tanks
|
-- tanks
|
||||||
unit.connect_input_reg(boiler.getSteam)
|
unit.connect_input_reg("getSteam")
|
||||||
unit.connect_input_reg(boiler.getSteamNeeded)
|
unit.connect_input_reg("getSteamNeeded")
|
||||||
unit.connect_input_reg(boiler.getSteamFilledPercentage)
|
unit.connect_input_reg("getSteamFilledPercentage")
|
||||||
unit.connect_input_reg(boiler.getWater)
|
unit.connect_input_reg("getWater")
|
||||||
unit.connect_input_reg(boiler.getWaterNeeded)
|
unit.connect_input_reg("getWaterNeeded")
|
||||||
unit.connect_input_reg(boiler.getWaterFilledPercentage)
|
unit.connect_input_reg("getWaterFilledPercentage")
|
||||||
unit.connect_input_reg(boiler.getHeatedCoolant)
|
unit.connect_input_reg("getHeatedCoolant")
|
||||||
unit.connect_input_reg(boiler.getHeatedCoolantNeeded)
|
unit.connect_input_reg("getHeatedCoolantNeeded")
|
||||||
unit.connect_input_reg(boiler.getHeatedCoolantFilledPercentage)
|
unit.connect_input_reg("getHeatedCoolantFilledPercentage")
|
||||||
unit.connect_input_reg(boiler.getCooledCoolant)
|
unit.connect_input_reg("getCooledCoolant")
|
||||||
unit.connect_input_reg(boiler.getCooledCoolantNeeded)
|
unit.connect_input_reg("getCooledCoolantNeeded")
|
||||||
unit.connect_input_reg(boiler.getCooledCoolantFilledPercentage)
|
unit.connect_input_reg("getCooledCoolantFilledPercentage")
|
||||||
|
|
||||||
-- holding registers --
|
-- holding registers --
|
||||||
-- none
|
-- none
|
||||||
|
|
||||||
-- check if any calls faulted
|
return unit.interface(), false
|
||||||
local faulted = boiler.__p_is_faulted()
|
|
||||||
boiler.__p_clear_fault()
|
|
||||||
boiler.__p_enable_afc()
|
|
||||||
|
|
||||||
return unit.interface(), faulted
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return boilerv_rtu
|
return boilerv_rtu
|
||||||
|
@ -9,12 +9,8 @@ local dynamicv_rtu = {}
|
|||||||
function dynamicv_rtu.new(dynamic_tank)
|
function dynamicv_rtu.new(dynamic_tank)
|
||||||
local unit = rtu.init_unit(dynamic_tank)
|
local unit = rtu.init_unit(dynamic_tank)
|
||||||
|
|
||||||
-- disable auto fault clearing
|
|
||||||
dynamic_tank.__p_clear_fault()
|
|
||||||
dynamic_tank.__p_disable_afc()
|
|
||||||
|
|
||||||
-- discrete inputs --
|
-- discrete inputs --
|
||||||
unit.connect_di(dynamic_tank.isFormed)
|
unit.connect_di("isFormed")
|
||||||
|
|
||||||
-- coils --
|
-- coils --
|
||||||
unit.connect_coil(function () dynamic_tank.incrementContainerEditMode() end, function () end)
|
unit.connect_coil(function () dynamic_tank.incrementContainerEditMode() end, function () end)
|
||||||
@ -22,27 +18,22 @@ function dynamicv_rtu.new(dynamic_tank)
|
|||||||
|
|
||||||
-- input registers --
|
-- input registers --
|
||||||
-- multiblock properties
|
-- multiblock properties
|
||||||
unit.connect_input_reg(dynamic_tank.getLength)
|
unit.connect_input_reg("getLength")
|
||||||
unit.connect_input_reg(dynamic_tank.getWidth)
|
unit.connect_input_reg("getWidth")
|
||||||
unit.connect_input_reg(dynamic_tank.getHeight)
|
unit.connect_input_reg("getHeight")
|
||||||
unit.connect_input_reg(dynamic_tank.getMinPos)
|
unit.connect_input_reg("getMinPos")
|
||||||
unit.connect_input_reg(dynamic_tank.getMaxPos)
|
unit.connect_input_reg("getMaxPos")
|
||||||
-- build properties
|
-- build properties
|
||||||
unit.connect_input_reg(dynamic_tank.getTankCapacity)
|
unit.connect_input_reg("getTankCapacity")
|
||||||
unit.connect_input_reg(dynamic_tank.getChemicalTankCapacity)
|
unit.connect_input_reg("getChemicalTankCapacity")
|
||||||
-- tanks/containers
|
-- tanks/containers
|
||||||
unit.connect_input_reg(dynamic_tank.getStored)
|
unit.connect_input_reg("getStored")
|
||||||
unit.connect_input_reg(dynamic_tank.getFilledPercentage)
|
unit.connect_input_reg("getFilledPercentage")
|
||||||
|
|
||||||
-- holding registers --
|
-- holding registers --
|
||||||
unit.connect_holding_reg(dynamic_tank.getContainerEditMode, dynamic_tank.setContainerEditMode)
|
unit.connect_holding_reg("getContainerEditMode", "setContainerEditMode")
|
||||||
|
|
||||||
-- check if any calls faulted
|
return unit.interface(), false
|
||||||
local faulted = dynamic_tank.__p_is_faulted()
|
|
||||||
dynamic_tank.__p_clear_fault()
|
|
||||||
dynamic_tank.__p_enable_afc()
|
|
||||||
|
|
||||||
return unit.interface(), faulted
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return dynamicv_rtu
|
return dynamicv_rtu
|
||||||
|
@ -9,45 +9,36 @@ local imatrix_rtu = {}
|
|||||||
function imatrix_rtu.new(imatrix)
|
function imatrix_rtu.new(imatrix)
|
||||||
local unit = rtu.init_unit(imatrix)
|
local unit = rtu.init_unit(imatrix)
|
||||||
|
|
||||||
-- disable auto fault clearing
|
|
||||||
imatrix.__p_clear_fault()
|
|
||||||
imatrix.__p_disable_afc()
|
|
||||||
|
|
||||||
-- discrete inputs --
|
-- discrete inputs --
|
||||||
unit.connect_di(imatrix.isFormed)
|
unit.connect_di("isFormed")
|
||||||
|
|
||||||
-- coils --
|
-- coils --
|
||||||
-- none
|
-- none
|
||||||
|
|
||||||
-- input registers --
|
-- input registers --
|
||||||
-- multiblock properties
|
-- multiblock properties
|
||||||
unit.connect_input_reg(imatrix.getLength)
|
unit.connect_input_reg("getLength")
|
||||||
unit.connect_input_reg(imatrix.getWidth)
|
unit.connect_input_reg("getWidth")
|
||||||
unit.connect_input_reg(imatrix.getHeight)
|
unit.connect_input_reg("getHeight")
|
||||||
unit.connect_input_reg(imatrix.getMinPos)
|
unit.connect_input_reg("getMinPos")
|
||||||
unit.connect_input_reg(imatrix.getMaxPos)
|
unit.connect_input_reg("getMaxPos")
|
||||||
-- build properties
|
-- build properties
|
||||||
unit.connect_input_reg(imatrix.getMaxEnergy)
|
unit.connect_input_reg("getMaxEnergy")
|
||||||
unit.connect_input_reg(imatrix.getTransferCap)
|
unit.connect_input_reg("getTransferCap")
|
||||||
unit.connect_input_reg(imatrix.getInstalledCells)
|
unit.connect_input_reg("getInstalledCells")
|
||||||
unit.connect_input_reg(imatrix.getInstalledProviders)
|
unit.connect_input_reg("getInstalledProviders")
|
||||||
-- I/O rates
|
-- I/O rates
|
||||||
unit.connect_input_reg(imatrix.getLastInput)
|
unit.connect_input_reg("getLastInput")
|
||||||
unit.connect_input_reg(imatrix.getLastOutput)
|
unit.connect_input_reg("getLastOutput")
|
||||||
-- tanks
|
-- tanks
|
||||||
unit.connect_input_reg(imatrix.getEnergy)
|
unit.connect_input_reg("getEnergy")
|
||||||
unit.connect_input_reg(imatrix.getEnergyNeeded)
|
unit.connect_input_reg("getEnergyNeeded")
|
||||||
unit.connect_input_reg(imatrix.getEnergyFilledPercentage)
|
unit.connect_input_reg("getEnergyFilledPercentage")
|
||||||
|
|
||||||
-- holding registers --
|
-- holding registers --
|
||||||
-- none
|
-- none
|
||||||
|
|
||||||
-- check if any calls faulted
|
return unit.interface(), false
|
||||||
local faulted = imatrix.__p_is_faulted()
|
|
||||||
imatrix.__p_clear_fault()
|
|
||||||
imatrix.__p_enable_afc()
|
|
||||||
|
|
||||||
return unit.interface(), faulted
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return imatrix_rtu
|
return imatrix_rtu
|
||||||
|
@ -9,50 +9,41 @@ local sps_rtu = {}
|
|||||||
function sps_rtu.new(sps)
|
function sps_rtu.new(sps)
|
||||||
local unit = rtu.init_unit(sps)
|
local unit = rtu.init_unit(sps)
|
||||||
|
|
||||||
-- disable auto fault clearing
|
|
||||||
sps.__p_clear_fault()
|
|
||||||
sps.__p_disable_afc()
|
|
||||||
|
|
||||||
-- discrete inputs --
|
-- discrete inputs --
|
||||||
unit.connect_di(sps.isFormed)
|
unit.connect_di("isFormed")
|
||||||
|
|
||||||
-- coils --
|
-- coils --
|
||||||
-- none
|
-- none
|
||||||
|
|
||||||
-- input registers --
|
-- input registers --
|
||||||
-- multiblock properties
|
-- multiblock properties
|
||||||
unit.connect_input_reg(sps.getLength)
|
unit.connect_input_reg("getLength")
|
||||||
unit.connect_input_reg(sps.getWidth)
|
unit.connect_input_reg("getWidth")
|
||||||
unit.connect_input_reg(sps.getHeight)
|
unit.connect_input_reg("getHeight")
|
||||||
unit.connect_input_reg(sps.getMinPos)
|
unit.connect_input_reg("getMinPos")
|
||||||
unit.connect_input_reg(sps.getMaxPos)
|
unit.connect_input_reg("getMaxPos")
|
||||||
-- build properties
|
-- build properties
|
||||||
unit.connect_input_reg(sps.getCoils)
|
unit.connect_input_reg("getCoils")
|
||||||
unit.connect_input_reg(sps.getInputCapacity)
|
unit.connect_input_reg("getInputCapacity")
|
||||||
unit.connect_input_reg(sps.getOutputCapacity)
|
unit.connect_input_reg("getOutputCapacity")
|
||||||
unit.connect_input_reg(sps.getMaxEnergy)
|
unit.connect_input_reg("getMaxEnergy")
|
||||||
-- current state
|
-- current state
|
||||||
unit.connect_input_reg(sps.getProcessRate)
|
unit.connect_input_reg("getProcessRate")
|
||||||
-- tanks
|
-- tanks
|
||||||
unit.connect_input_reg(sps.getInput)
|
unit.connect_input_reg("getInput")
|
||||||
unit.connect_input_reg(sps.getInputNeeded)
|
unit.connect_input_reg("getInputNeeded")
|
||||||
unit.connect_input_reg(sps.getInputFilledPercentage)
|
unit.connect_input_reg("getInputFilledPercentage")
|
||||||
unit.connect_input_reg(sps.getOutput)
|
unit.connect_input_reg("getOutput")
|
||||||
unit.connect_input_reg(sps.getOutputNeeded)
|
unit.connect_input_reg("getOutputNeeded")
|
||||||
unit.connect_input_reg(sps.getOutputFilledPercentage)
|
unit.connect_input_reg("getOutputFilledPercentage")
|
||||||
unit.connect_input_reg(sps.getEnergy)
|
unit.connect_input_reg("getEnergy")
|
||||||
unit.connect_input_reg(sps.getEnergyNeeded)
|
unit.connect_input_reg("getEnergyNeeded")
|
||||||
unit.connect_input_reg(sps.getEnergyFilledPercentage)
|
unit.connect_input_reg("getEnergyFilledPercentage")
|
||||||
|
|
||||||
-- holding registers --
|
-- holding registers --
|
||||||
-- none
|
-- none
|
||||||
|
|
||||||
-- check if any calls faulted
|
return unit.interface(), false
|
||||||
local faulted = sps.__p_is_faulted()
|
|
||||||
sps.__p_clear_fault()
|
|
||||||
sps.__p_enable_afc()
|
|
||||||
|
|
||||||
return unit.interface(), faulted
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return sps_rtu
|
return sps_rtu
|
||||||
|
@ -9,12 +9,8 @@ local turbinev_rtu = {}
|
|||||||
function turbinev_rtu.new(turbine)
|
function turbinev_rtu.new(turbine)
|
||||||
local unit = rtu.init_unit(turbine)
|
local unit = rtu.init_unit(turbine)
|
||||||
|
|
||||||
-- disable auto fault clearing
|
|
||||||
turbine.__p_clear_fault()
|
|
||||||
turbine.__p_disable_afc()
|
|
||||||
|
|
||||||
-- discrete inputs --
|
-- discrete inputs --
|
||||||
unit.connect_di(turbine.isFormed)
|
unit.connect_di("isFormed")
|
||||||
|
|
||||||
-- coils --
|
-- coils --
|
||||||
unit.connect_coil(function () turbine.incrementDumpingMode() end, function () end)
|
unit.connect_coil(function () turbine.incrementDumpingMode() end, function () end)
|
||||||
@ -22,44 +18,39 @@ function turbinev_rtu.new(turbine)
|
|||||||
|
|
||||||
-- input registers --
|
-- input registers --
|
||||||
-- multiblock properties
|
-- multiblock properties
|
||||||
unit.connect_input_reg(turbine.getLength)
|
unit.connect_input_reg("getLength")
|
||||||
unit.connect_input_reg(turbine.getWidth)
|
unit.connect_input_reg("getWidth")
|
||||||
unit.connect_input_reg(turbine.getHeight)
|
unit.connect_input_reg("getHeight")
|
||||||
unit.connect_input_reg(turbine.getMinPos)
|
unit.connect_input_reg("getMinPos")
|
||||||
unit.connect_input_reg(turbine.getMaxPos)
|
unit.connect_input_reg("getMaxPos")
|
||||||
-- build properties
|
-- build properties
|
||||||
unit.connect_input_reg(turbine.getBlades)
|
unit.connect_input_reg("getBlades")
|
||||||
unit.connect_input_reg(turbine.getCoils)
|
unit.connect_input_reg("getCoils")
|
||||||
unit.connect_input_reg(turbine.getVents)
|
unit.connect_input_reg("getVents")
|
||||||
unit.connect_input_reg(turbine.getDispersers)
|
unit.connect_input_reg("getDispersers")
|
||||||
unit.connect_input_reg(turbine.getCondensers)
|
unit.connect_input_reg("getCondensers")
|
||||||
unit.connect_input_reg(turbine.getSteamCapacity)
|
unit.connect_input_reg("getSteamCapacity")
|
||||||
unit.connect_input_reg(turbine.getMaxEnergy)
|
unit.connect_input_reg("getMaxEnergy")
|
||||||
unit.connect_input_reg(turbine.getMaxFlowRate)
|
unit.connect_input_reg("getMaxFlowRate")
|
||||||
unit.connect_input_reg(turbine.getMaxProduction)
|
unit.connect_input_reg("getMaxProduction")
|
||||||
unit.connect_input_reg(turbine.getMaxWaterOutput)
|
unit.connect_input_reg("getMaxWaterOutput")
|
||||||
-- current state
|
-- current state
|
||||||
unit.connect_input_reg(turbine.getFlowRate)
|
unit.connect_input_reg("getFlowRate")
|
||||||
unit.connect_input_reg(turbine.getProductionRate)
|
unit.connect_input_reg("getProductionRate")
|
||||||
unit.connect_input_reg(turbine.getLastSteamInputRate)
|
unit.connect_input_reg("getLastSteamInputRate")
|
||||||
unit.connect_input_reg(turbine.getDumpingMode)
|
unit.connect_input_reg("getDumpingMode")
|
||||||
-- tanks/containers
|
-- tanks/containers
|
||||||
unit.connect_input_reg(turbine.getSteam)
|
unit.connect_input_reg("getSteam")
|
||||||
unit.connect_input_reg(turbine.getSteamNeeded)
|
unit.connect_input_reg("getSteamNeeded")
|
||||||
unit.connect_input_reg(turbine.getSteamFilledPercentage)
|
unit.connect_input_reg("getSteamFilledPercentage")
|
||||||
unit.connect_input_reg(turbine.getEnergy)
|
unit.connect_input_reg("getEnergy")
|
||||||
unit.connect_input_reg(turbine.getEnergyNeeded)
|
unit.connect_input_reg("getEnergyNeeded")
|
||||||
unit.connect_input_reg(turbine.getEnergyFilledPercentage)
|
unit.connect_input_reg("getEnergyFilledPercentage")
|
||||||
|
|
||||||
-- holding registers --
|
-- holding registers --
|
||||||
unit.connect_holding_reg(turbine.getDumpingMode, turbine.setDumpingMode)
|
unit.connect_holding_reg("getDumpingMode", "setDumpingMode")
|
||||||
|
|
||||||
-- check if any calls faulted
|
return unit.interface(), false
|
||||||
local faulted = turbine.__p_is_faulted()
|
|
||||||
turbine.__p_clear_fault()
|
|
||||||
turbine.__p_enable_afc()
|
|
||||||
|
|
||||||
return unit.interface(), faulted
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return turbinev_rtu
|
return turbinev_rtu
|
||||||
|
@ -35,7 +35,7 @@ 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
|
||||||
|
|
||||||
TextBox{parent=panel,y=1,text="RTU GATEWAY",alignment=ALIGN.CENTER,height=1,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
|
||||||
@ -53,12 +53,12 @@ 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,colors.gray}}
|
local network = RGBLED{parent=system,label="NETWORK",colors={colors.green,colors.red,colors.orange,colors.yellow,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
|
||||||
local nt_lnk = LEDPair{parent=system,label="NT LINKED",off=colors.red_off,c1=colors.red,c2=colors.green}
|
local nt_lnk = LEDPair{parent=system,label="NT LINKED",off=style.ind_bkg,c1=colors.red,c2=colors.green}
|
||||||
local nt_ver = LEDPair{parent=system,label="NT VERSION",off=colors.red_off,c1=colors.red,c2=colors.green}
|
local nt_ver = LEDPair{parent=system,label="NT VERSION",off=style.ind_bkg,c1=colors.red,c2=colors.green}
|
||||||
|
|
||||||
nt_lnk.register(databus.ps, "link_state", function (state)
|
nt_lnk.register(databus.ps, "link_state", function (state)
|
||||||
local value = 2
|
local value = 2
|
||||||
@ -98,9 +98,9 @@ local function init(panel, units)
|
|||||||
|
|
||||||
---@diagnostic disable-next-line: undefined-field
|
---@diagnostic disable-next-line: undefined-field
|
||||||
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,height=1,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",height=1,width=8,fg_bg=style.fp.text_fg}
|
TextBox{parent=system,x=1,y=14,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=14,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)
|
||||||
|
|
||||||
@ -109,8 +109,8 @@ local function init(panel, units)
|
|||||||
--
|
--
|
||||||
|
|
||||||
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=3,x=1,y=18,fg_bg=disabled_fg}
|
||||||
local fw_v = TextBox{parent=about,x=1,y=1,text="FW: v00.00.00",alignment=ALIGN.LEFT,height=1}
|
local fw_v = TextBox{parent=about,x=1,y=1,text="FW: v00.00.00"}
|
||||||
local comms_v = TextBox{parent=about,x=1,y=2,text="NT: v00.00.00",alignment=ALIGN.LEFT,height=1}
|
local comms_v = TextBox{parent=about,x=1,y=2,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)
|
||||||
@ -126,7 +126,7 @@ local function init(panel, units)
|
|||||||
|
|
||||||
-- 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),height=1}
|
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=ind_grn}
|
||||||
rt_unit.register(databus.ps, "routine__unit_" .. i, rt_unit.update)
|
rt_unit.register(databus.ps, "routine__unit_" .. i, rt_unit.update)
|
||||||
end
|
end
|
||||||
@ -144,13 +144,13 @@ local function init(panel, units)
|
|||||||
|
|
||||||
-- 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(t) return util.c(UNIT_TYPE_LABELS[t + 1], " ", util.trinary(util.is_int(unit.index), unit.index, "")) end
|
||||||
local name_box = TextBox{parent=unit_hw_statuses,y=i,x=3,text=get_name(unit.type),width=15,height=1}
|
local name_box = TextBox{parent=unit_hw_statuses,y=i,x=3,text=get_name(unit.type),width=15}
|
||||||
|
|
||||||
name_box.register(databus.ps, "unit_type_" .. i, function (t) name_box.set_value(get_name(t)) end)
|
name_box.register(databus.ps, "unit_type_" .. i, function (t) name_box.set_value(get_name(t)) end)
|
||||||
|
|
||||||
-- assignment (unit # or facility)
|
-- assignment (unit # or facility)
|
||||||
local for_unit = util.trinary(unit.reactor == 0, "\x1a FACIL ", "\x1a UNIT " .. unit.reactor)
|
local for_unit = util.trinary(unit.reactor == 0, "\x1a FACIL ", "\x1a UNIT " .. unit.reactor)
|
||||||
TextBox{parent=unit_hw_statuses,y=i,x=19,text=for_unit,height=1,fg_bg=disabled_fg}
|
TextBox{parent=unit_hw_statuses,y=i,x=19,text=for_unit,fg_bg=disabled_fg}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -28,7 +28,13 @@ function style.set_theme(fp, color_mode)
|
|||||||
|
|
||||||
style.fp = themes.get_fp_style(style.theme)
|
style.fp = themes.get_fp_style(style.theme)
|
||||||
|
|
||||||
style.colorblind = color_mode ~= themes.COLOR_MODE.STANDARD
|
style.colorblind = color_mode ~= themes.COLOR_MODE.STANDARD and color_mode ~= themes.COLOR_MODE.STD_ON_BLACK
|
||||||
|
|
||||||
|
if color_mode == themes.COLOR_MODE.STANDARD or color_mode == themes.COLOR_MODE.BLUE_IND then
|
||||||
|
style.ind_bkg = colors.gray
|
||||||
|
else
|
||||||
|
style.ind_bkg = colors.black
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return style
|
return style
|
||||||
|
58
rtu/rtu.lua
58
rtu/rtu.lua
@ -5,6 +5,8 @@ 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 themes = require("graphics.themes")
|
||||||
|
|
||||||
local databus = require("rtu.databus")
|
local databus = require("rtu.databus")
|
||||||
local modbus = require("rtu.modbus")
|
local modbus = require("rtu.modbus")
|
||||||
|
|
||||||
@ -58,7 +60,7 @@ function rtu.load_config()
|
|||||||
|
|
||||||
if type(config.AuthKey) == "string" then
|
if type(config.AuthKey) == "string" then
|
||||||
local len = string.len(config.AuthKey)
|
local len = string.len(config.AuthKey)
|
||||||
cfv.assert_eq(len == 0 or len >= 8, true)
|
cfv.assert(len == 0 or len >= 8)
|
||||||
end
|
end
|
||||||
|
|
||||||
cfv.assert_type_int(config.LogMode)
|
cfv.assert_type_int(config.LogMode)
|
||||||
@ -69,7 +71,7 @@ function rtu.load_config()
|
|||||||
cfv.assert_type_int(config.FrontPanelTheme)
|
cfv.assert_type_int(config.FrontPanelTheme)
|
||||||
cfv.assert_range(config.FrontPanelTheme, 1, 2)
|
cfv.assert_range(config.FrontPanelTheme, 1, 2)
|
||||||
cfv.assert_type_int(config.ColorMode)
|
cfv.assert_type_int(config.ColorMode)
|
||||||
cfv.assert_range(config.ColorMode, 1, 4)
|
cfv.assert_range(config.ColorMode, 1, themes.COLOR_MODE.NUM_MODES)
|
||||||
|
|
||||||
cfv.assert_type_table(config.Peripherals)
|
cfv.assert_type_table(config.Peripherals)
|
||||||
cfv.assert_type_table(config.Redstone)
|
cfv.assert_type_table(config.Redstone)
|
||||||
@ -92,6 +94,8 @@ function rtu.init_unit(device)
|
|||||||
|
|
||||||
local insert = table.insert
|
local insert = table.insert
|
||||||
|
|
||||||
|
local stub = function () log.warning("tried to call an RTU function stub") end
|
||||||
|
|
||||||
---@class rtu_device
|
---@class rtu_device
|
||||||
local public = {}
|
local public = {}
|
||||||
|
|
||||||
@ -113,13 +117,26 @@ function rtu.init_unit(device)
|
|||||||
return self.io_count_cache[1], self.io_count_cache[2], self.io_count_cache[3], self.io_count_cache[4]
|
return self.io_count_cache[1], self.io_count_cache[2], self.io_count_cache[3], self.io_count_cache[4]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- pass a function through or generate one to call a function by name from the device
|
||||||
|
---@param f function|string function or device function name
|
||||||
|
local function _as_func(f)
|
||||||
|
if type(f) == "string" then
|
||||||
|
local name = f
|
||||||
|
if device then
|
||||||
|
f = function (...) return device[name](...) end
|
||||||
|
else f = stub end
|
||||||
|
end
|
||||||
|
|
||||||
|
return f
|
||||||
|
end
|
||||||
|
|
||||||
-- discrete inputs: single bit read-only
|
-- discrete inputs: single bit read-only
|
||||||
|
|
||||||
-- connect discrete input
|
-- connect discrete input
|
||||||
---@param f function
|
---@param f function|string function or function name
|
||||||
---@return integer count count of discrete inputs
|
---@return integer count count of discrete inputs
|
||||||
function protected.connect_di(f)
|
function protected.connect_di(f)
|
||||||
insert(self.discrete_inputs, { read = f })
|
insert(self.discrete_inputs, { read = _as_func(f) })
|
||||||
_count_io()
|
_count_io()
|
||||||
return #self.discrete_inputs
|
return #self.discrete_inputs
|
||||||
end
|
end
|
||||||
@ -135,11 +152,11 @@ function rtu.init_unit(device)
|
|||||||
-- coils: single bit read-write
|
-- coils: single bit read-write
|
||||||
|
|
||||||
-- connect coil
|
-- connect coil
|
||||||
---@param f_read function
|
---@param f_read function|string function or function name
|
||||||
---@param f_write function
|
---@param f_write function|string function or function name
|
||||||
---@return integer count count of coils
|
---@return integer count count of coils
|
||||||
function protected.connect_coil(f_read, f_write)
|
function protected.connect_coil(f_read, f_write)
|
||||||
insert(self.coils, { read = f_read, write = f_write })
|
insert(self.coils, { read = _as_func(f_read), write = _as_func(f_write) })
|
||||||
_count_io()
|
_count_io()
|
||||||
return #self.coils
|
return #self.coils
|
||||||
end
|
end
|
||||||
@ -164,10 +181,10 @@ function rtu.init_unit(device)
|
|||||||
-- input registers: multi-bit read-only
|
-- input registers: multi-bit read-only
|
||||||
|
|
||||||
-- connect input register
|
-- connect input register
|
||||||
---@param f function
|
---@param f function|string function or function name
|
||||||
---@return integer count count of input registers
|
---@return integer count count of input registers
|
||||||
function protected.connect_input_reg(f)
|
function protected.connect_input_reg(f)
|
||||||
insert(self.input_regs, { read = f })
|
insert(self.input_regs, { read = _as_func(f) })
|
||||||
_count_io()
|
_count_io()
|
||||||
return #self.input_regs
|
return #self.input_regs
|
||||||
end
|
end
|
||||||
@ -183,11 +200,11 @@ function rtu.init_unit(device)
|
|||||||
-- holding registers: multi-bit read-write
|
-- holding registers: multi-bit read-write
|
||||||
|
|
||||||
-- connect holding register
|
-- connect holding register
|
||||||
---@param f_read function
|
---@param f_read function|string function or function name
|
||||||
---@param f_write function
|
---@param f_write function|string function or function name
|
||||||
---@return integer count count of holding registers
|
---@return integer count count of holding registers
|
||||||
function protected.connect_holding_reg(f_read, f_write)
|
function protected.connect_holding_reg(f_read, f_write)
|
||||||
insert(self.holding_regs, { read = f_read, write = f_write })
|
insert(self.holding_regs, { read = _as_func(f_read), write = _as_func(f_write) })
|
||||||
_count_io()
|
_count_io()
|
||||||
return #self.holding_regs
|
return #self.holding_regs
|
||||||
end
|
end
|
||||||
@ -267,8 +284,8 @@ end
|
|||||||
function rtu.comms(version, nic, conn_watchdog)
|
function rtu.comms(version, nic, conn_watchdog)
|
||||||
local self = {
|
local self = {
|
||||||
sv_addr = comms.BROADCAST,
|
sv_addr = comms.BROADCAST,
|
||||||
seq_num = 0,
|
seq_num = util.time_ms() * 10, -- unique per peer, restarting will not re-use seq nums due to message rate
|
||||||
r_seq_num = nil,
|
r_seq_num = nil, ---@type nil|integer
|
||||||
txn_id = 0,
|
txn_id = 0,
|
||||||
last_est_ack = ESTABLISH_ACK.ALLOW
|
last_est_ack = ESTABLISH_ACK.ALLOW
|
||||||
}
|
}
|
||||||
@ -425,16 +442,16 @@ function rtu.comms(version, nic, conn_watchdog)
|
|||||||
if l_chan == config.RTU_Channel then
|
if l_chan == config.RTU_Channel then
|
||||||
-- check sequence number
|
-- check sequence number
|
||||||
if self.r_seq_num == nil then
|
if self.r_seq_num == nil then
|
||||||
self.r_seq_num = packet.scada_frame.seq_num()
|
self.r_seq_num = packet.scada_frame.seq_num() + 1
|
||||||
elseif rtu_state.linked and ((self.r_seq_num + 1) ~= packet.scada_frame.seq_num()) then
|
elseif self.r_seq_num ~= packet.scada_frame.seq_num() then
|
||||||
log.warning("sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. packet.scada_frame.seq_num())
|
log.warning("sequence out-of-order: next = " .. self.r_seq_num .. ", new = " .. packet.scada_frame.seq_num())
|
||||||
return
|
return
|
||||||
elseif rtu_state.linked and (src_addr ~= self.sv_addr) then
|
elseif rtu_state.linked and (src_addr ~= self.sv_addr) then
|
||||||
log.debug("received packet from unknown computer " .. src_addr .. " while linked (expected " .. self.sv_addr ..
|
log.debug("received packet from unknown computer " .. src_addr .. " while linked (expected " .. self.sv_addr ..
|
||||||
"); channel in use by another system?")
|
"); channel in use by another system?")
|
||||||
return
|
return
|
||||||
else
|
else
|
||||||
self.r_seq_num = packet.scada_frame.seq_num()
|
self.r_seq_num = packet.scada_frame.seq_num() + 1
|
||||||
end
|
end
|
||||||
|
|
||||||
-- feed watchdog on valid sequence number
|
-- feed watchdog on valid sequence number
|
||||||
@ -539,7 +556,6 @@ function rtu.comms(version, nic, conn_watchdog)
|
|||||||
-- establish allowed
|
-- establish allowed
|
||||||
rtu_state.linked = true
|
rtu_state.linked = true
|
||||||
self.sv_addr = packet.scada_frame.src_addr()
|
self.sv_addr = packet.scada_frame.src_addr()
|
||||||
self.r_seq_num = nil
|
|
||||||
println_ts("supervisor connection established")
|
println_ts("supervisor connection established")
|
||||||
log.info("supervisor connection established")
|
log.info("supervisor connection established")
|
||||||
else
|
else
|
||||||
@ -555,7 +571,9 @@ function rtu.comms(version, nic, conn_watchdog)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
public.unlink(rtu_state)
|
-- unlink
|
||||||
|
self.sv_addr = comms.BROADCAST
|
||||||
|
rtu_state.linked = false
|
||||||
end
|
end
|
||||||
|
|
||||||
self.last_est_ack = est_ack
|
self.last_est_ack = est_ack
|
||||||
|
@ -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.8.0"
|
local RTU_VERSION = "v1.10.6"
|
||||||
|
|
||||||
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
|
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
|
||||||
local RTU_UNIT_HW_STATE = databus.RTU_UNIT_HW_STATE
|
local RTU_UNIT_HW_STATE = databus.RTU_UNIT_HW_STATE
|
||||||
@ -71,6 +71,7 @@ log.info("========================================")
|
|||||||
println(">> RTU GATEWAY " .. RTU_VERSION .. " <<")
|
println(">> RTU GATEWAY " .. RTU_VERSION .. " <<")
|
||||||
|
|
||||||
crash.set_env("rtu", RTU_VERSION)
|
crash.set_env("rtu", RTU_VERSION)
|
||||||
|
crash.dbg_log_env()
|
||||||
|
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
-- main application
|
-- main application
|
||||||
@ -92,14 +93,6 @@ local function main()
|
|||||||
network.init_mac(config.AuthKey)
|
network.init_mac(config.AuthKey)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- get modem
|
|
||||||
local modem = ppm.get_wireless_modem()
|
|
||||||
if modem == nil then
|
|
||||||
println("boot> wireless modem not found")
|
|
||||||
log.fatal("no wireless modem on startup")
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
-- generate alarm tones
|
-- generate alarm tones
|
||||||
audio.generate_tones()
|
audio.generate_tones()
|
||||||
|
|
||||||
@ -115,14 +108,15 @@ local function main()
|
|||||||
|
|
||||||
-- RTU gateway devices (not RTU units)
|
-- RTU gateway devices (not RTU units)
|
||||||
rtu_dev = {
|
rtu_dev = {
|
||||||
|
modem = ppm.get_wireless_modem(),
|
||||||
sounders = {}
|
sounders = {}
|
||||||
},
|
},
|
||||||
|
|
||||||
-- system objects
|
-- system objects
|
||||||
rtu_sys = {
|
rtu_sys = {
|
||||||
nic = network.nic(modem),
|
nic = nil, ---@type nic
|
||||||
rtu_comms = nil, ---@type rtu_comms
|
rtu_comms = nil, ---@type rtu_comms
|
||||||
conn_watchdog = nil, ---@type watchdog
|
conn_watchdog = nil, ---@type watchdog
|
||||||
units = {}
|
units = {}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -133,8 +127,9 @@ local function main()
|
|||||||
}
|
}
|
||||||
|
|
||||||
local smem_sys = __shared_memory.rtu_sys
|
local smem_sys = __shared_memory.rtu_sys
|
||||||
|
local smem_dev = __shared_memory.rtu_dev
|
||||||
|
|
||||||
databus.tx_hw_modem(true)
|
local rtu_state = __shared_memory.rtu_state
|
||||||
|
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
-- interpret config and init units
|
-- interpret config and init units
|
||||||
@ -342,7 +337,7 @@ local function main()
|
|||||||
is_multiblock = true
|
is_multiblock = true
|
||||||
formed = device.isFormed()
|
formed = device.isFormed()
|
||||||
|
|
||||||
if formed == ppm.UNDEFINED_FIELD or 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.fatal(util.c("sys_config> failed to check if '", name, "' is a formed boiler multiblock"))
|
||||||
return false
|
return false
|
||||||
@ -357,7 +352,7 @@ local function main()
|
|||||||
is_multiblock = true
|
is_multiblock = true
|
||||||
formed = device.isFormed()
|
formed = device.isFormed()
|
||||||
|
|
||||||
if formed == ppm.UNDEFINED_FIELD or 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.fatal(util.c("sys_config> failed to check if '", name, "' is a formed turbine multiblock"))
|
||||||
return false
|
return false
|
||||||
@ -377,7 +372,7 @@ local function main()
|
|||||||
is_multiblock = true
|
is_multiblock = true
|
||||||
formed = device.isFormed()
|
formed = device.isFormed()
|
||||||
|
|
||||||
if formed == ppm.UNDEFINED_FIELD or 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.fatal(util.c("sys_config> failed to check if '", name, "' is a formed dynamic tank multiblock"))
|
||||||
return false
|
return false
|
||||||
@ -391,7 +386,7 @@ local function main()
|
|||||||
is_multiblock = true
|
is_multiblock = true
|
||||||
formed = device.isFormed()
|
formed = device.isFormed()
|
||||||
|
|
||||||
if formed == ppm.UNDEFINED_FIELD or 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.fatal(util.c("sys_config> failed to check if '", name, "' is a formed induction matrix multiblock"))
|
||||||
return false
|
return false
|
||||||
@ -405,7 +400,7 @@ local function main()
|
|||||||
is_multiblock = true
|
is_multiblock = true
|
||||||
formed = device.isFormed()
|
formed = device.isFormed()
|
||||||
|
|
||||||
if formed == ppm.UNDEFINED_FIELD or 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.fatal(util.c("sys_config> failed to check if '", name, "' is a formed SPS multiblock"))
|
||||||
return false
|
return false
|
||||||
@ -471,7 +466,8 @@ local function main()
|
|||||||
for_message = util.c("reactor ", for_reactor)
|
for_message = util.c("reactor ", for_reactor)
|
||||||
end
|
end
|
||||||
|
|
||||||
log.info(util.c("sys_config> initialized RTU unit #", #units, ": ", name, " (", types.rtu_type_to_string(rtu_type), ") [", index, "] for ", for_message))
|
local index_str = util.trinary(index ~= nil, util.c(" [", index, "]"), "")
|
||||||
|
log.info(util.c("sys_config> initialized RTU unit #", #units, ": ", name, " (", types.rtu_type_to_string(rtu_type), ")", index_str, " for ", for_message))
|
||||||
|
|
||||||
rtu_unit.uid = #units
|
rtu_unit.uid = #units
|
||||||
|
|
||||||
@ -499,8 +495,6 @@ local function main()
|
|||||||
-- start system
|
-- start system
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
|
|
||||||
local rtu_state = __shared_memory.rtu_state
|
|
||||||
|
|
||||||
log.debug("boot> running sys_config()")
|
log.debug("boot> running sys_config()")
|
||||||
|
|
||||||
if sys_config() then
|
if sys_config() then
|
||||||
@ -515,23 +509,33 @@ local function main()
|
|||||||
log.info("startup> running in headless mode without front panel")
|
log.info("startup> running in headless mode without front panel")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- check modem
|
||||||
|
if smem_dev.modem == nil then
|
||||||
|
println("startup> wireless modem not found")
|
||||||
|
log.fatal("no wireless modem on startup")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
databus.tx_hw_modem(true)
|
||||||
|
|
||||||
-- find and setup all speakers
|
-- find and setup all speakers
|
||||||
local speakers = ppm.get_all_devices("speaker")
|
local speakers = ppm.get_all_devices("speaker")
|
||||||
for _, s in pairs(speakers) do
|
for _, s in pairs(speakers) do
|
||||||
local sounder = rtu.init_sounder(s)
|
local sounder = rtu.init_sounder(s)
|
||||||
|
|
||||||
table.insert(__shared_memory.rtu_dev.sounders, sounder)
|
table.insert(smem_dev.sounders, sounder)
|
||||||
|
|
||||||
log.debug(util.c("startup> added speaker, attached as ", sounder.name))
|
log.debug(util.c("startup> added speaker, attached as ", sounder.name))
|
||||||
end
|
end
|
||||||
|
|
||||||
databus.tx_hw_spkr_count(#__shared_memory.rtu_dev.sounders)
|
databus.tx_hw_spkr_count(#smem_dev.sounders)
|
||||||
|
|
||||||
-- 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")
|
||||||
|
|
||||||
-- setup comms
|
-- setup comms
|
||||||
|
smem_sys.nic = network.nic(smem_dev.modem)
|
||||||
smem_sys.rtu_comms = rtu.comms(RTU_VERSION, smem_sys.nic, smem_sys.conn_watchdog)
|
smem_sys.rtu_comms = rtu.comms(RTU_VERSION, smem_sys.nic, smem_sys.conn_watchdog)
|
||||||
log.debug("startup> comms init")
|
log.debug("startup> comms init")
|
||||||
|
|
||||||
|
@ -517,82 +517,23 @@ function threads.thread__unit_comms(smem, unit)
|
|||||||
|
|
||||||
-- check if multiblock is still formed if this is a multiblock
|
-- check if multiblock is still formed if this is a multiblock
|
||||||
if unit.is_multiblock and (util.time_ms() - last_f_check > 250) then
|
if unit.is_multiblock and (util.time_ms() - last_f_check > 250) then
|
||||||
local is_formed = unit.device.isFormed()
|
|
||||||
|
|
||||||
last_f_check = util.time_ms()
|
last_f_check = util.time_ms()
|
||||||
|
|
||||||
|
local is_formed = unit.device.isFormed()
|
||||||
|
|
||||||
if unit.formed == nil then
|
if unit.formed == nil then
|
||||||
unit.formed = is_formed
|
unit.formed = is_formed
|
||||||
if is_formed then unit.hw_state = UNIT_HW_STATE.OK end
|
if is_formed then unit.hw_state = UNIT_HW_STATE.OK end
|
||||||
|
elseif not unit.formed then
|
||||||
|
unit.hw_state = UNIT_HW_STATE.UNFORMED
|
||||||
end
|
end
|
||||||
|
|
||||||
if not unit.formed then unit.hw_state = UNIT_HW_STATE.UNFORMED end
|
if (is_formed == true) and not unit.formed then
|
||||||
|
unit.hw_state = UNIT_HW_STATE.OK
|
||||||
if (not unit.formed) and is_formed then
|
log.info(util.c(detail_name, " is now formed"))
|
||||||
-- newly re-formed
|
rtu_comms.send_remounted(unit.uid)
|
||||||
local iface = ppm.get_iface(unit.device)
|
elseif (is_formed == false) and unit.formed then
|
||||||
if iface then
|
log.warning(util.c(detail_name, " is no longer formed"))
|
||||||
log.info(util.c("unmounting and remounting reformed RTU unit ", detail_name))
|
|
||||||
|
|
||||||
ppm.unmount(unit.device)
|
|
||||||
|
|
||||||
local type, device = ppm.mount(iface)
|
|
||||||
local faulted = false
|
|
||||||
|
|
||||||
if device ~= nil then
|
|
||||||
if type == "boilerValve" and unit.type == RTU_UNIT_TYPE.BOILER_VALVE then
|
|
||||||
-- boiler multiblock
|
|
||||||
unit.device = device
|
|
||||||
unit.rtu, faulted = boilerv_rtu.new(device)
|
|
||||||
unit.formed = device.isFormed()
|
|
||||||
unit.modbus_io = modbus.new(unit.rtu, true)
|
|
||||||
elseif type == "turbineValve" and unit.type == RTU_UNIT_TYPE.TURBINE_VALVE then
|
|
||||||
-- turbine multiblock
|
|
||||||
unit.device = device
|
|
||||||
unit.rtu, faulted = turbinev_rtu.new(device)
|
|
||||||
unit.formed = device.isFormed()
|
|
||||||
unit.modbus_io = modbus.new(unit.rtu, true)
|
|
||||||
elseif type == "dynamicValve" and unit.type == RTU_UNIT_TYPE.DYNAMIC_VALVE then
|
|
||||||
-- dynamic tank multiblock
|
|
||||||
unit.device = device
|
|
||||||
unit.rtu, faulted = dynamicv_rtu.new(device)
|
|
||||||
unit.formed = device.isFormed()
|
|
||||||
unit.modbus_io = modbus.new(unit.rtu, true)
|
|
||||||
elseif type == "inductionPort" and unit.type == RTU_UNIT_TYPE.IMATRIX then
|
|
||||||
-- induction matrix multiblock
|
|
||||||
unit.device = device
|
|
||||||
unit.rtu, faulted = imatrix_rtu.new(device)
|
|
||||||
unit.formed = device.isFormed()
|
|
||||||
unit.modbus_io = modbus.new(unit.rtu, true)
|
|
||||||
elseif type == "spsPort" and unit.type == RTU_UNIT_TYPE.SPS then
|
|
||||||
-- SPS multiblock
|
|
||||||
unit.device = device
|
|
||||||
unit.rtu, faulted = sps_rtu.new(device)
|
|
||||||
unit.formed = device.isFormed()
|
|
||||||
unit.modbus_io = modbus.new(unit.rtu, true)
|
|
||||||
else
|
|
||||||
log.error("illegal remount of non-multiblock RTU or type change attempted for " .. short_name, true)
|
|
||||||
end
|
|
||||||
|
|
||||||
if unit.formed and faulted then
|
|
||||||
-- something is still wrong = can't mark as formed yet
|
|
||||||
unit.formed = false
|
|
||||||
unit.hw_state = UNIT_HW_STATE.UNFORMED
|
|
||||||
log.info(util.c("assuming ", unit.name, " is not formed due to PPM faults while initializing"))
|
|
||||||
else
|
|
||||||
unit.hw_state = UNIT_HW_STATE.OK
|
|
||||||
rtu_comms.send_remounted(unit.uid)
|
|
||||||
end
|
|
||||||
|
|
||||||
local type_name = types.rtu_type_to_string(unit.type)
|
|
||||||
log.info(util.c("reconnected the ", type_name, " on interface ", unit.name))
|
|
||||||
else
|
|
||||||
-- fully lost the peripheral now :(
|
|
||||||
log.error(util.c(unit.name, " lost (failed reconnect)"))
|
|
||||||
end
|
|
||||||
else
|
|
||||||
log.error("failed to get interface of previously connected RTU unit " .. detail_name, true)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
unit.formed = is_formed
|
unit.formed = is_formed
|
||||||
|
@ -16,8 +16,9 @@ local max_distance = nil
|
|||||||
---@class comms
|
---@class comms
|
||||||
local comms = {}
|
local comms = {}
|
||||||
|
|
||||||
-- protocol/data version (protocol/data independent changes tracked by util.lua version)
|
-- protocol/data versions (protocol/data independent changes tracked by util.lua version)
|
||||||
comms.version = "2.4.5"
|
comms.version = "3.0.0"
|
||||||
|
comms.api_version = "0.0.3"
|
||||||
|
|
||||||
---@enum PROTOCOL
|
---@enum PROTOCOL
|
||||||
local PROTOCOL = {
|
local PROTOCOL = {
|
||||||
@ -64,7 +65,9 @@ local CRDN_TYPE = {
|
|||||||
FAC_CMD = 3, -- faility command
|
FAC_CMD = 3, -- faility command
|
||||||
UNIT_BUILDS = 4, -- build of each reactor unit (reactor + RTUs)
|
UNIT_BUILDS = 4, -- build of each reactor unit (reactor + RTUs)
|
||||||
UNIT_STATUSES = 5, -- state of each of the reactor units
|
UNIT_STATUSES = 5, -- state of each of the reactor units
|
||||||
UNIT_CMD = 6 -- command a reactor unit
|
UNIT_CMD = 6, -- command a reactor unit
|
||||||
|
API_GET_FAC = 7, -- API: get all the facility data
|
||||||
|
API_GET_UNIT = 8 -- API: get reactor unit data
|
||||||
}
|
}
|
||||||
|
|
||||||
---@enum ESTABLISH_ACK
|
---@enum ESTABLISH_ACK
|
||||||
@ -72,7 +75,8 @@ local ESTABLISH_ACK = {
|
|||||||
ALLOW = 0, -- link approved
|
ALLOW = 0, -- link approved
|
||||||
DENY = 1, -- link denied
|
DENY = 1, -- link denied
|
||||||
COLLISION = 2, -- link denied due to existing active link
|
COLLISION = 2, -- link denied due to existing active link
|
||||||
BAD_VERSION = 3 -- link denied due to comms version mismatch
|
BAD_VERSION = 3, -- link denied due to comms version mismatch
|
||||||
|
BAD_API_VERSION = 4 -- link denied due to api version mismatch
|
||||||
}
|
}
|
||||||
|
|
||||||
---@enum DEVICE_TYPE device types for establish messages
|
---@enum DEVICE_TYPE device types for establish messages
|
||||||
@ -93,7 +97,8 @@ local FAC_COMMAND = {
|
|||||||
START = 2, -- start automatic process control
|
START = 2, -- start automatic process control
|
||||||
ACK_ALL_ALARMS = 3, -- acknowledge all alarms on all units
|
ACK_ALL_ALARMS = 3, -- acknowledge all alarms on all units
|
||||||
SET_WASTE_MODE = 4, -- set automatic waste processing mode
|
SET_WASTE_MODE = 4, -- set automatic waste processing mode
|
||||||
SET_PU_FB = 5 -- set plutonium fallback mode
|
SET_PU_FB = 5, -- set plutonium fallback mode
|
||||||
|
SET_SPS_LP = 6 -- set SPS at low power mode
|
||||||
}
|
}
|
||||||
|
|
||||||
---@enum UNIT_COMMAND
|
---@enum UNIT_COMMAND
|
||||||
@ -235,6 +240,8 @@ function comms.scada_packet()
|
|||||||
---@nodiscard
|
---@nodiscard
|
||||||
function public.modem_event() return self.modem_msg_in end
|
function public.modem_event() return self.modem_msg_in end
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
|
function public.raw_header() return { self.src_addr, self.dest_addr, self.seq_num, self.protocol } end
|
||||||
|
---@nodiscard
|
||||||
function public.raw_sendable() return self.raw end
|
function public.raw_sendable() return self.raw end
|
||||||
|
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
@ -273,7 +280,7 @@ function comms.authd_packet()
|
|||||||
src_addr = comms.BROADCAST,
|
src_addr = comms.BROADCAST,
|
||||||
dest_addr = comms.BROADCAST,
|
dest_addr = comms.BROADCAST,
|
||||||
mac = "",
|
mac = "",
|
||||||
payload = ""
|
payload = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
---@class authd_packet
|
---@class authd_packet
|
||||||
@ -281,14 +288,13 @@ function comms.authd_packet()
|
|||||||
|
|
||||||
-- make an authenticated SCADA packet
|
-- make an authenticated SCADA packet
|
||||||
---@param s_packet scada_packet scada packet to authenticate
|
---@param s_packet scada_packet scada packet to authenticate
|
||||||
---@param mac function message authentication function
|
---@param mac function message authentication hash function
|
||||||
function public.make(s_packet, mac)
|
function public.make(s_packet, mac)
|
||||||
self.valid = true
|
self.valid = true
|
||||||
self.src_addr = s_packet.src_addr()
|
self.src_addr = s_packet.src_addr()
|
||||||
self.dest_addr = s_packet.dest_addr()
|
self.dest_addr = s_packet.dest_addr()
|
||||||
self.payload = textutils.serialize(s_packet.raw_sendable(), { allow_repetitions = true, compact = true })
|
self.mac = mac(textutils.serialize(s_packet.raw_header(), { allow_repetitions = true, compact = true }))
|
||||||
self.mac = mac(self.payload)
|
self.raw = { self.src_addr, self.dest_addr, self.mac, s_packet.raw_sendable() }
|
||||||
self.raw = { self.src_addr, self.dest_addr, self.mac, self.payload }
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- parse in a modem message as an authenticated SCADA packet
|
-- parse in a modem message as an authenticated SCADA packet
|
||||||
@ -325,14 +331,14 @@ function comms.authd_packet()
|
|||||||
self.src_addr = nil
|
self.src_addr = nil
|
||||||
self.dest_addr = nil
|
self.dest_addr = nil
|
||||||
self.mac = ""
|
self.mac = ""
|
||||||
self.payload = ""
|
self.payload = {}
|
||||||
end
|
end
|
||||||
|
|
||||||
-- check if this packet is destined for this device
|
-- check if this packet is destined for this device
|
||||||
local is_destination = (self.dest_addr == comms.BROADCAST) or (self.dest_addr == COMPUTER_ID)
|
local is_destination = (self.dest_addr == comms.BROADCAST) or (self.dest_addr == COMPUTER_ID)
|
||||||
|
|
||||||
self.valid = is_destination and type(self.src_addr) == "number" and type(self.dest_addr) == "number" and
|
self.valid = is_destination and type(self.src_addr) == "number" and type(self.dest_addr) == "number" and
|
||||||
type(self.mac) == "string" and type(self.payload) == "string"
|
type(self.mac) == "string" and type(self.payload) == "table"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ local annunc = {}
|
|||||||
annunc.RCSFlowLow_H2O = -3.2 -- flow < -3.2 mB/s
|
annunc.RCSFlowLow_H2O = -3.2 -- flow < -3.2 mB/s
|
||||||
annunc.RCSFlowLow_NA = -2.0 -- flow < -2.0 mB/s
|
annunc.RCSFlowLow_NA = -2.0 -- flow < -2.0 mB/s
|
||||||
annunc.CoolantLevelLow = 0.4 -- fill < 40%
|
annunc.CoolantLevelLow = 0.4 -- fill < 40%
|
||||||
annunc.ReactorTempHigh = 1000 -- temp > 1000K
|
annunc.OpTempTolerance = 5 -- high temp if >= operational temp + X
|
||||||
annunc.ReactorHighDeltaT = 50 -- rate > 50K/s
|
annunc.ReactorHighDeltaT = 50 -- rate > 50K/s
|
||||||
annunc.FuelLevelLow = 0.05 -- fill <= 5%
|
annunc.FuelLevelLow = 0.05 -- fill <= 5%
|
||||||
annunc.WasteLevelHigh = 0.80 -- fill >= 80%
|
annunc.WasteLevelHigh = 0.80 -- fill >= 80%
|
||||||
@ -52,7 +52,6 @@ local alarms = {}
|
|||||||
|
|
||||||
-- unit alarms
|
-- unit alarms
|
||||||
|
|
||||||
alarms.HIGH_TEMP = 1150 -- temp >= 1150K
|
|
||||||
alarms.HIGH_WASTE = 0.85 -- fill > 85%
|
alarms.HIGH_WASTE = 0.85 -- fill > 85%
|
||||||
alarms.HIGH_RADIATION = 0.00005 -- 50 uSv/h, not yet damaging but this isn't good
|
alarms.HIGH_RADIATION = 0.00005 -- 50 uSv/h, not yet damaging but this isn't good
|
||||||
|
|
||||||
@ -66,21 +65,48 @@ constants.ALARM_LIMITS = alarms
|
|||||||
|
|
||||||
--#endregion
|
--#endregion
|
||||||
|
|
||||||
|
--#region Supervisor Redstone Activation Thresholds
|
||||||
|
|
||||||
|
---@class _rs_threshold_constants
|
||||||
|
local rs = {}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
constants.RS_THRESHOLDS = rs
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
--#region Supervisor Constants
|
--#region Supervisor Constants
|
||||||
|
|
||||||
-- milliseconds until turbine flow is assumed to be stable enough to enable coolant checks
|
-- milliseconds until coolant flow is assumed to be stable enough to enable certain coolant checks
|
||||||
constants.FLOW_STABILITY_DELAY_MS = 15000
|
constants.FLOW_STABILITY_DELAY_MS = 10000
|
||||||
|
|
||||||
-- Notes on Radiation
|
-- Notes on Radiation
|
||||||
-- - background radiation 0.0000001 Sv/h (99.99 nSv/h)
|
-- - background radiation 0.0000001 Sv/h (99.99 nSv/h)
|
||||||
-- - "green tint" radiation 0.00001 Sv/h (10 uSv/h)
|
-- - "green tint" radiation 0.00001 Sv/h (10 uSv/h)
|
||||||
-- - damaging radiation 0.00006 Sv/h (60 uSv/h)
|
-- - damaging radiation 0.00006 Sv/h (60 uSv/h)
|
||||||
constants.LOW_RADIATION = 0.00001
|
constants.LOW_RADIATION = 0.00001
|
||||||
constants.HAZARD_RADIATION = 0.00006
|
constants.HAZARD_RADIATION = 0.00006
|
||||||
constants.HIGH_RADIATION = 0.001
|
constants.HIGH_RADIATION = 0.001
|
||||||
constants.VERY_HIGH_RADIATION = 0.1
|
constants.VERY_HIGH_RADIATION = 0.1
|
||||||
constants.SEVERE_RADIATION = 8.0
|
constants.SEVERE_RADIATION = 8.0
|
||||||
constants.EXTREME_RADIATION = 100.0
|
constants.EXTREME_RADIATION = 100.0
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
|
--#region Mekanism Configuration Constants
|
||||||
|
|
||||||
|
---@class _mek_constants
|
||||||
|
local mek = {}
|
||||||
|
|
||||||
|
mek.BASE_BOIL_TEMP = 373.15 -- mekanism: HeatUtils.BASE_BOIL_TEMP
|
||||||
|
mek.JOULES_PER_MB = 1000000 -- mekanism: energyPerFissionFuel
|
||||||
|
mek.TURBINE_GAS_PER_TANK = 64000 -- mekanism: turbineGasPerTank
|
||||||
|
mek.TURBINE_DISPERSER_FLOW = 1280 -- mekanism: turbineDisperserGasFlow
|
||||||
|
mek.TURBINE_VENT_FLOW = 32000 -- mekanism: turbineVentGasFlow
|
||||||
|
|
||||||
|
constants.mek = mek
|
||||||
|
|
||||||
--#endregion
|
--#endregion
|
||||||
|
|
||||||
|
@ -24,6 +24,21 @@ function crash.set_env(application, version)
|
|||||||
ver = version
|
ver = version
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- log environment versions
|
||||||
|
---@param log_msg function log function to use
|
||||||
|
local function log_versions(log_msg)
|
||||||
|
log_msg(util.c("RUNTIME: ", _HOST))
|
||||||
|
log_msg(util.c("LUA VERSION: ", _VERSION))
|
||||||
|
log_msg(util.c("APPLICATION: ", app))
|
||||||
|
log_msg(util.c("FIRMWARE VERSION: ", ver))
|
||||||
|
log_msg(util.c("COMMS VERSION: ", comms.version))
|
||||||
|
if has_graphics then log_msg(util.c("GRAPHICS VERSION: ", core.version)) end
|
||||||
|
if has_lockbox then log_msg(util.c("LOCKBOX VERSION: ", lockbox.version)) end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- when running with debug logs, log the useful information that the crash handler knows
|
||||||
|
function crash.dbg_log_env() log_versions(log.debug) end
|
||||||
|
|
||||||
-- handle a crash error
|
-- handle a crash error
|
||||||
---@param error string error message
|
---@param error string error message
|
||||||
function crash.handler(error)
|
function crash.handler(error)
|
||||||
@ -31,13 +46,7 @@ function crash.handler(error)
|
|||||||
log.info("=====> FATAL SOFTWARE FAULT <=====")
|
log.info("=====> FATAL SOFTWARE FAULT <=====")
|
||||||
log.fatal(error)
|
log.fatal(error)
|
||||||
log.info("----------------------------------")
|
log.info("----------------------------------")
|
||||||
log.info(util.c("RUNTIME: ", _HOST))
|
log_versions(log.info)
|
||||||
log.info(util.c("LUA VERSION: ", _VERSION))
|
|
||||||
log.info(util.c("APPLICATION: ", app))
|
|
||||||
log.info(util.c("FIRMWARE VERSION: ", ver))
|
|
||||||
log.info(util.c("COMMS VERSION: ", comms.version))
|
|
||||||
if has_graphics then log.info(util.c("GRAPHICS VERSION: ", core.version)) end
|
|
||||||
if has_lockbox then log.info(util.c("LOCKBOX VERSION: ", lockbox.version)) end
|
|
||||||
log.info("----------------------------------")
|
log.info("----------------------------------")
|
||||||
log.info(debug.traceback("--- begin debug trace ---", 1))
|
log.info(debug.traceback("--- begin debug trace ---", 1))
|
||||||
log.info("--- end debug trace ---")
|
log.info("--- end debug trace ---")
|
||||||
|
@ -80,7 +80,7 @@ end
|
|||||||
---@param modem table modem to use
|
---@param modem table modem to use
|
||||||
function network.nic(modem)
|
function network.nic(modem)
|
||||||
local self = {
|
local self = {
|
||||||
connected = true, -- used to avoid costly MAC calculations if modem isn't even present
|
connected = true, -- used to avoid costly MAC calculations if modem isn't even present
|
||||||
channels = {}
|
channels = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,7 +114,7 @@ function network.nic(modem)
|
|||||||
modem.open(channel)
|
modem.open(channel)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- link all public functions except for transmit
|
-- link all public functions except for transmit, open, and close
|
||||||
for key, func in pairs(modem) do
|
for key, func in pairs(modem) do
|
||||||
if key ~= "transmit" and key ~= "open" and key ~= "close" and key ~= "closeAll" then public[key] = func end
|
if key ~= "transmit" and key ~= "open" and key ~= "close" and key ~= "closeAll" then public[key] = func end
|
||||||
end
|
end
|
||||||
@ -175,7 +175,7 @@ function network.nic(modem)
|
|||||||
---@param packet scada_packet packet
|
---@param packet scada_packet packet
|
||||||
function public.transmit(dest_channel, local_channel, packet)
|
function public.transmit(dest_channel, local_channel, packet)
|
||||||
if self.connected then
|
if self.connected then
|
||||||
local tx_packet = packet ---@type authd_packet|scada_packet
|
local tx_packet = packet ---@type authd_packet|scada_packet
|
||||||
|
|
||||||
if c_eng.hmac ~= nil then
|
if c_eng.hmac ~= nil then
|
||||||
-- local start = util.time_ms()
|
-- local start = util.time_ms()
|
||||||
@ -184,7 +184,7 @@ function network.nic(modem)
|
|||||||
---@cast tx_packet authd_packet
|
---@cast tx_packet authd_packet
|
||||||
tx_packet.make(packet, compute_hmac)
|
tx_packet.make(packet, compute_hmac)
|
||||||
|
|
||||||
-- log.debug("crypto.modem.transmit: data processing took " .. (util.time_ms() - start) .. "ms")
|
-- log.debug("network.modem.transmit: data processing took " .. (util.time_ms() - start) .. "ms")
|
||||||
end
|
end
|
||||||
|
|
||||||
modem.transmit(dest_channel, local_channel, tx_packet.raw_sendable())
|
modem.transmit(dest_channel, local_channel, tx_packet.raw_sendable())
|
||||||
@ -211,17 +211,18 @@ function network.nic(modem)
|
|||||||
a_packet.receive(side, sender, reply_to, message, distance)
|
a_packet.receive(side, sender, reply_to, message, distance)
|
||||||
|
|
||||||
if a_packet.is_valid() then
|
if a_packet.is_valid() then
|
||||||
-- local start = util.time_ms()
|
s_packet.receive(side, sender, reply_to, a_packet.data(), distance)
|
||||||
local packet_hmac = a_packet.mac()
|
|
||||||
local msg = a_packet.data()
|
|
||||||
local computed_hmac = compute_hmac(msg)
|
|
||||||
|
|
||||||
if packet_hmac == computed_hmac then
|
if s_packet.is_valid() then
|
||||||
-- log.debug("crypto.modem.receive: HMAC verified in " .. (util.time_ms() - start) .. "ms")
|
-- local start = util.time_ms()
|
||||||
s_packet.receive(side, sender, reply_to, textutils.unserialize(msg), distance)
|
local computed_hmac = compute_hmac(textutils.serialize(s_packet.raw_header(), { allow_repetitions = true, compact = true }))
|
||||||
s_packet.stamp_authenticated()
|
|
||||||
else
|
if a_packet.mac() == computed_hmac then
|
||||||
-- log.debug("crypto.modem.receive: HMAC failed verification in " .. (util.time_ms() - start) .. "ms")
|
-- log.debug("network.modem.receive: HMAC verified in " .. (util.time_ms() - start) .. "ms")
|
||||||
|
s_packet.stamp_authenticated()
|
||||||
|
else
|
||||||
|
-- log.debug("network.modem.receive: HMAC failed verification in " .. (util.time_ms() - start) .. "ms")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
|
@ -51,11 +51,13 @@ local function peri_init(iface)
|
|||||||
self.device = peripheral.wrap(iface)
|
self.device = peripheral.wrap(iface)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- initialization process (re-map)
|
-- create a protected version of a peripheral function call
|
||||||
|
---@nodiscard
|
||||||
for key, func in pairs(self.device) do
|
---@param key string function name
|
||||||
self.fault_counts[key] = 0
|
---@param func function function
|
||||||
self.device[key] = function (...)
|
---@return function method protected version of the function
|
||||||
|
local function protect_peri_function(key, func)
|
||||||
|
return function (...)
|
||||||
local return_table = table.pack(pcall(func, ...))
|
local return_table = table.pack(pcall(func, ...))
|
||||||
|
|
||||||
local status = return_table[1]
|
local status = return_table[1]
|
||||||
@ -85,20 +87,24 @@ local function peri_init(iface)
|
|||||||
count_str = " [" .. self.fault_counts[key] .. " total faults]"
|
count_str = " [" .. self.fault_counts[key] .. " total faults]"
|
||||||
end
|
end
|
||||||
|
|
||||||
log.error(util.c("PPM: protected ", key, "() -> ", result, count_str))
|
log.error(util.c("PPM: [@", iface, "] protected ", key, "() -> ", result, count_str))
|
||||||
end
|
end
|
||||||
|
|
||||||
self.fault_counts[key] = self.fault_counts[key] + 1
|
self.fault_counts[key] = self.fault_counts[key] + 1
|
||||||
|
|
||||||
if result == "Terminated" then
|
if result == "Terminated" then ppm_sys.terminate = true end
|
||||||
ppm_sys.terminate = true
|
|
||||||
end
|
|
||||||
|
|
||||||
return ACCESS_FAULT
|
return ACCESS_FAULT, result
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- initialization process (re-map)
|
||||||
|
for key, func in pairs(self.device) do
|
||||||
|
self.fault_counts[key] = 0
|
||||||
|
self.device[key] = protect_peri_function(key, func)
|
||||||
|
end
|
||||||
|
|
||||||
-- fault management & monitoring functions
|
-- fault management & monitoring functions
|
||||||
|
|
||||||
local function clear_fault() self.faulted = false end
|
local function clear_fault() self.faulted = false end
|
||||||
@ -131,12 +137,23 @@ local function peri_init(iface)
|
|||||||
|
|
||||||
local mt = {
|
local mt = {
|
||||||
__index = function (_, key)
|
__index = function (_, key)
|
||||||
|
-- try to find the function in case it was added (multiblock formed)
|
||||||
|
local funcs = peripheral.wrap(iface)
|
||||||
|
if (type(funcs) == "table") and (type(funcs[key]) == "function") then
|
||||||
|
-- add this function then return it
|
||||||
|
self.fault_counts[key] = 0
|
||||||
|
self.device[key] = protect_peri_function(key, funcs[key])
|
||||||
|
|
||||||
|
log.info(util.c("PPM: [@", iface, "] initialized previously undefined field ", key, "()"))
|
||||||
|
|
||||||
|
return self.device[key]
|
||||||
|
end
|
||||||
|
|
||||||
|
-- function still missing, return an undefined function handler
|
||||||
|
-- note: code should avoid storing functions for multiblocks and instead try to index them again
|
||||||
return (function ()
|
return (function ()
|
||||||
-- this will continuously be counting calls here as faults
|
-- this will continuously be counting calls here as faults
|
||||||
-- unlike other functions, faults here can't be cleared as it is just not defined
|
if self.fault_counts[key] == nil then self.fault_counts[key] = 0 end
|
||||||
if self.fault_counts[key] == nil then
|
|
||||||
self.fault_counts[key] = 0
|
|
||||||
end
|
|
||||||
|
|
||||||
-- function failed
|
-- function failed
|
||||||
self.faulted = true
|
self.faulted = true
|
||||||
@ -151,12 +168,12 @@ local function peri_init(iface)
|
|||||||
count_str = " [" .. self.fault_counts[key] .. " total calls]"
|
count_str = " [" .. self.fault_counts[key] .. " total calls]"
|
||||||
end
|
end
|
||||||
|
|
||||||
log.error(util.c("PPM: caught undefined function ", key, "()", count_str))
|
log.error(util.c("PPM: [@", iface, "] caught undefined function ", key, "()", count_str))
|
||||||
end
|
end
|
||||||
|
|
||||||
self.fault_counts[key] = self.fault_counts[key] + 1
|
self.fault_counts[key] = self.fault_counts[key] + 1
|
||||||
|
|
||||||
return UNDEFINED_FIELD
|
return ACCESS_FAULT, UNDEFINED_FIELD
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
|
@ -52,6 +52,8 @@ local IO_PORT = {
|
|||||||
-- facility
|
-- facility
|
||||||
F_ALARM = 7, -- active high, facility-wide alarm (any high priority unit alarm)
|
F_ALARM = 7, -- active high, facility-wide alarm (any high priority unit alarm)
|
||||||
F_ALARM_ANY = 8, -- active high, any alarm regardless of priority
|
F_ALARM_ANY = 8, -- active high, any alarm regardless of priority
|
||||||
|
F_MATRIX_LOW = 27, -- active high, induction matrix charge low
|
||||||
|
F_MATRIX_HIGH = 28, -- active high, induction matrix charge high
|
||||||
|
|
||||||
-- waste
|
-- waste
|
||||||
WASTE_PU = 9, -- active low, waste -> plutonium -> pellets route
|
WASTE_PU = 9, -- active low, waste -> plutonium -> pellets route
|
||||||
@ -75,17 +77,27 @@ 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
|
||||||
|
|
||||||
|
-- analog outputs --
|
||||||
|
|
||||||
|
-- facility
|
||||||
|
F_MATRIX_CHG = 29 -- analog charge level of the induction matrix
|
||||||
}
|
}
|
||||||
|
|
||||||
rsio.IO_LVL = IO_LVL
|
rsio.IO_LVL = IO_LVL
|
||||||
rsio.IO_DIR = IO_DIR
|
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 = IO_PORT.U_EMER_COOL
|
|
||||||
|
rsio.NUM_PORTS = 29
|
||||||
|
rsio.NUM_DIG_PORTS = 28
|
||||||
|
rsio.NUM_ANA_PORTS = 1
|
||||||
|
|
||||||
-- self checks
|
-- self checks
|
||||||
|
|
||||||
|
assert(rsio.NUM_PORTS == (rsio.NUM_DIG_PORTS + rsio.NUM_ANA_PORTS), "port counts inconsistent")
|
||||||
|
|
||||||
local dup_chk = {}
|
local dup_chk = {}
|
||||||
for _, v in pairs(IO_PORT) do
|
for _, v in pairs(IO_PORT) do
|
||||||
assert(dup_chk[v] ~= true, "duplicate in port list")
|
assert(dup_chk[v] ~= true, "duplicate in port list")
|
||||||
@ -96,64 +108,45 @@ assert(#dup_chk == rsio.NUM_PORTS, "port list malformed")
|
|||||||
|
|
||||||
--#endregion
|
--#endregion
|
||||||
|
|
||||||
--#region Utility Functions
|
--#region Utility Functions and Attribute Tables
|
||||||
|
|
||||||
local PORT_NAMES = {
|
local IO = IO_PORT
|
||||||
"F_SCRAM",
|
|
||||||
"F_ACK",
|
|
||||||
"R_SCRAM",
|
|
||||||
"R_RESET",
|
|
||||||
"R_ENABLE",
|
|
||||||
"U_ACK",
|
|
||||||
"F_ALARM",
|
|
||||||
"F_ALARM_ANY",
|
|
||||||
"WASTE_PU",
|
|
||||||
"WASTE_PO",
|
|
||||||
"WASTE_POPL",
|
|
||||||
"WASTE_AM",
|
|
||||||
"R_ACTIVE",
|
|
||||||
"R_AUTO_CTRL",
|
|
||||||
"R_SCRAMMED",
|
|
||||||
"R_AUTO_SCRAM",
|
|
||||||
"R_HIGH_DMG",
|
|
||||||
"R_HIGH_TEMP",
|
|
||||||
"R_LOW_COOLANT",
|
|
||||||
"R_EXCESS_HC",
|
|
||||||
"R_EXCESS_WS",
|
|
||||||
"R_INSUFF_FUEL",
|
|
||||||
"R_PLC_FAULT",
|
|
||||||
"R_PLC_TIMEOUT",
|
|
||||||
"U_ALARM",
|
|
||||||
"U_EMER_COOL"
|
|
||||||
}
|
|
||||||
|
|
||||||
|
-- list of all port names
|
||||||
|
local PORT_NAMES = {}
|
||||||
|
for k, v in pairs(IO) do PORT_NAMES[v] = k end
|
||||||
|
|
||||||
|
-- list of all port I/O modes
|
||||||
local MODES = {
|
local MODES = {
|
||||||
IO_MODE.DIGITAL_IN, -- F_SCRAM
|
[IO.F_SCRAM] = IO_MODE.DIGITAL_IN,
|
||||||
IO_MODE.DIGITAL_IN, -- F_ACK
|
[IO.F_ACK] = IO_MODE.DIGITAL_IN,
|
||||||
IO_MODE.DIGITAL_IN, -- R_SCRAM
|
[IO.R_SCRAM] = IO_MODE.DIGITAL_IN,
|
||||||
IO_MODE.DIGITAL_IN, -- R_RESET
|
[IO.R_RESET] = IO_MODE.DIGITAL_IN,
|
||||||
IO_MODE.DIGITAL_IN, -- R_ENABLE
|
[IO.R_ENABLE] = IO_MODE.DIGITAL_IN,
|
||||||
IO_MODE.DIGITAL_IN, -- U_ACK
|
[IO.U_ACK] = IO_MODE.DIGITAL_IN,
|
||||||
IO_MODE.DIGITAL_OUT, -- F_ALARM
|
[IO.F_ALARM] = IO_MODE.DIGITAL_OUT,
|
||||||
IO_MODE.DIGITAL_OUT, -- F_ALARM_ANY
|
[IO.F_ALARM_ANY] = IO_MODE.DIGITAL_OUT,
|
||||||
IO_MODE.DIGITAL_OUT, -- WASTE_PU
|
[IO.F_MATRIX_LOW] = IO_MODE.DIGITAL_OUT,
|
||||||
IO_MODE.DIGITAL_OUT, -- WASTE_PO
|
[IO.F_MATRIX_HIGH] = IO_MODE.DIGITAL_OUT,
|
||||||
IO_MODE.DIGITAL_OUT, -- WASTE_POPL
|
[IO.WASTE_PU] = IO_MODE.DIGITAL_OUT,
|
||||||
IO_MODE.DIGITAL_OUT, -- WASTE_AM
|
[IO.WASTE_PO] = IO_MODE.DIGITAL_OUT,
|
||||||
IO_MODE.DIGITAL_OUT, -- R_ACTIVE
|
[IO.WASTE_POPL] = IO_MODE.DIGITAL_OUT,
|
||||||
IO_MODE.DIGITAL_OUT, -- R_AUTO_CTRL
|
[IO.WASTE_AM] = IO_MODE.DIGITAL_OUT,
|
||||||
IO_MODE.DIGITAL_OUT, -- R_SCRAMMED
|
[IO.R_ACTIVE] = IO_MODE.DIGITAL_OUT,
|
||||||
IO_MODE.DIGITAL_OUT, -- R_AUTO_SCRAM
|
[IO.R_AUTO_CTRL] = IO_MODE.DIGITAL_OUT,
|
||||||
IO_MODE.DIGITAL_OUT, -- R_HIGH_DMG
|
[IO.R_SCRAMMED] = IO_MODE.DIGITAL_OUT,
|
||||||
IO_MODE.DIGITAL_OUT, -- R_HIGH_TEMP
|
[IO.R_AUTO_SCRAM] = IO_MODE.DIGITAL_OUT,
|
||||||
IO_MODE.DIGITAL_OUT, -- R_LOW_COOLANT
|
[IO.R_HIGH_DMG] = IO_MODE.DIGITAL_OUT,
|
||||||
IO_MODE.DIGITAL_OUT, -- R_EXCESS_HC
|
[IO.R_HIGH_TEMP] = IO_MODE.DIGITAL_OUT,
|
||||||
IO_MODE.DIGITAL_OUT, -- R_EXCESS_WS
|
[IO.R_LOW_COOLANT] = IO_MODE.DIGITAL_OUT,
|
||||||
IO_MODE.DIGITAL_OUT, -- R_INSUFF_FUEL
|
[IO.R_EXCESS_HC] = IO_MODE.DIGITAL_OUT,
|
||||||
IO_MODE.DIGITAL_OUT, -- R_PLC_FAULT
|
[IO.R_EXCESS_WS] = IO_MODE.DIGITAL_OUT,
|
||||||
IO_MODE.DIGITAL_OUT, -- R_PLC_TIMEOUT
|
[IO.R_INSUFF_FUEL] = IO_MODE.DIGITAL_OUT,
|
||||||
IO_MODE.DIGITAL_OUT, -- U_ALARM
|
[IO.R_PLC_FAULT] = IO_MODE.DIGITAL_OUT,
|
||||||
IO_MODE.DIGITAL_OUT -- U_EMER_COOL
|
[IO.R_PLC_TIMEOUT] = IO_MODE.DIGITAL_OUT,
|
||||||
|
[IO.U_ALARM] = IO_MODE.DIGITAL_OUT,
|
||||||
|
[IO.U_EMER_COOL] = IO_MODE.DIGITAL_OUT,
|
||||||
|
[IO.F_MATRIX_CHG] = IO_MODE.ANALOG_OUT
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(rsio.NUM_PORTS == #PORT_NAMES, "port names length incorrect")
|
assert(rsio.NUM_PORTS == #PORT_NAMES, "port names length incorrect")
|
||||||
@ -179,74 +172,51 @@ local function _O_ACTIVE_LOW(active) if active then return IO_LVL.LOW else retur
|
|||||||
|
|
||||||
-- I/O mappings to I/O function and I/O mode
|
-- I/O mappings to I/O function and I/O mode
|
||||||
local RS_DIO_MAP = {
|
local RS_DIO_MAP = {
|
||||||
-- F_SCRAM
|
[IO.F_SCRAM] = { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.IN },
|
||||||
{ _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.IN },
|
[IO.F_ACK] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.IN },
|
||||||
-- F_ACK
|
|
||||||
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.IN },
|
|
||||||
|
|
||||||
-- R_SCRAM
|
[IO.R_SCRAM] = { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.IN },
|
||||||
{ _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.IN },
|
[IO.R_RESET] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.IN },
|
||||||
-- R_RESET
|
[IO.R_ENABLE] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.IN },
|
||||||
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.IN },
|
|
||||||
-- R_ENABLE
|
|
||||||
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.IN },
|
|
||||||
|
|
||||||
-- U_ACK
|
[IO.U_ACK] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.IN },
|
||||||
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.IN },
|
|
||||||
|
|
||||||
-- F_ALARM
|
[IO.F_ALARM] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
||||||
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
[IO.F_ALARM_ANY] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
||||||
-- F_ALARM_ANY
|
[IO.F_MATRIX_LOW] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
||||||
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
[IO.F_MATRIX_HIGH] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
||||||
|
|
||||||
-- WASTE_PU
|
[IO.WASTE_PU] = { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT },
|
||||||
{ _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT },
|
[IO.WASTE_PO] = { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT },
|
||||||
-- WASTE_PO
|
[IO.WASTE_POPL] = { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT },
|
||||||
{ _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT },
|
[IO.WASTE_AM] = { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT },
|
||||||
-- WASTE_POPL
|
|
||||||
{ _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT },
|
|
||||||
-- WASTE_AM
|
|
||||||
{ _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT },
|
|
||||||
|
|
||||||
-- R_ACTIVE
|
[IO.R_ACTIVE] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
||||||
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
[IO.R_AUTO_CTRL] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
||||||
-- R_AUTO_CTRL
|
[IO.R_SCRAMMED] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
||||||
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
[IO.R_AUTO_SCRAM] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
||||||
-- R_SCRAMMED
|
[IO.R_HIGH_DMG] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
||||||
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
[IO.R_HIGH_TEMP] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
||||||
-- R_AUTO_SCRAM
|
[IO.R_LOW_COOLANT] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
||||||
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
[IO.R_EXCESS_HC] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
||||||
-- R_HIGH_DMG
|
[IO.R_EXCESS_WS] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
||||||
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
[IO.R_INSUFF_FUEL] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
||||||
-- R_HIGH_TEMP
|
[IO.R_PLC_FAULT] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
||||||
{ _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 },
|
||||||
-- R_LOW_COOLANT
|
|
||||||
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
|
||||||
-- R_EXCESS_HC
|
|
||||||
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
|
||||||
-- R_EXCESS_WS
|
|
||||||
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
|
||||||
-- R_INSUFF_FUEL
|
|
||||||
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
|
||||||
-- R_PLC_FAULT
|
|
||||||
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
|
||||||
-- R_PLC_TIMEOUT
|
|
||||||
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
|
||||||
|
|
||||||
-- U_ALARM
|
[IO.U_ALARM] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
||||||
{ _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 }
|
||||||
-- U_EMER_COOL
|
|
||||||
{ _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(rsio.NUM_PORTS == #RS_DIO_MAP, "RS_DIO_MAP length incorrect")
|
assert(rsio.NUM_DIG_PORTS == #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
|
||||||
---@param port IO_PORT
|
---@param port IO_PORT
|
||||||
---@return IO_DIR
|
---@return IO_DIR
|
||||||
function rsio.get_io_dir(port)
|
function rsio.get_io_dir(port)
|
||||||
if rsio.is_valid_port(port) then return RS_DIO_MAP[port].mode
|
if rsio.is_valid_port(port) then
|
||||||
|
return util.trinary(MODES[port] == IO_MODE.DIGITAL_OUT or MODES[port] == IO_MODE.ANALOG_OUT, IO_DIR.OUT, IO_DIR.IN)
|
||||||
else return IO_DIR.IN end
|
else return IO_DIR.IN end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -310,6 +280,13 @@ end
|
|||||||
|
|
||||||
--#region Digital I/O
|
--#region Digital I/O
|
||||||
|
|
||||||
|
-- check if a port is digital
|
||||||
|
---@nodiscard
|
||||||
|
---@param port IO_PORT
|
||||||
|
function rsio.is_digital(port)
|
||||||
|
return rsio.is_valid_port(port) and (MODES[port] == IO_MODE.DIGITAL_IN or MODES[port] == IO_MODE.DIGITAL_OUT)
|
||||||
|
end
|
||||||
|
|
||||||
-- get digital I/O level reading from a redstone boolean input value
|
-- get digital I/O level reading from a redstone boolean input value
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
---@param rs_value boolean raw value from redstone
|
---@param rs_value boolean raw value from redstone
|
||||||
@ -330,7 +307,7 @@ function rsio.digital_write(level) return level == IO_LVL.HIGH end
|
|||||||
---@param active boolean state to convert to logic level
|
---@param active boolean state to convert to logic level
|
||||||
---@return IO_LVL|false
|
---@return IO_LVL|false
|
||||||
function rsio.digital_write_active(port, active)
|
function rsio.digital_write_active(port, active)
|
||||||
if (not util.is_int(port)) or (port < IO_PORT.F_ALARM) or (port > IO_PORT.U_EMER_COOL) then
|
if not rsio.is_digital(port) then
|
||||||
return false
|
return false
|
||||||
else
|
else
|
||||||
return RS_DIO_MAP[port]._out(active)
|
return RS_DIO_MAP[port]._out(active)
|
||||||
@ -343,9 +320,7 @@ end
|
|||||||
---@param level IO_LVL logic level
|
---@param level IO_LVL logic level
|
||||||
---@return boolean|nil state true for active, false for inactive, or nil if invalid port or level provided
|
---@return boolean|nil state true for active, false for inactive, or nil if invalid port or level provided
|
||||||
function rsio.digital_is_active(port, level)
|
function rsio.digital_is_active(port, level)
|
||||||
if not util.is_int(port) then
|
if (not rsio.is_digital(port)) or level == IO_LVL.FLOATING or level == IO_LVL.DISCONNECT then
|
||||||
return nil
|
|
||||||
elseif level == IO_LVL.FLOATING or level == IO_LVL.DISCONNECT then
|
|
||||||
return nil
|
return nil
|
||||||
else
|
else
|
||||||
return RS_DIO_MAP[port]._in(level)
|
return RS_DIO_MAP[port]._in(level)
|
||||||
@ -356,6 +331,13 @@ end
|
|||||||
|
|
||||||
--#region Analog I/O
|
--#region Analog I/O
|
||||||
|
|
||||||
|
-- check if a port is analog
|
||||||
|
---@nodiscard
|
||||||
|
---@param port IO_PORT
|
||||||
|
function rsio.is_analog(port)
|
||||||
|
return rsio.is_valid_port(port) and (MODES[port] == IO_MODE.ANALOG_IN or MODES[port] == IO_MODE.ANALOG_OUT)
|
||||||
|
end
|
||||||
|
|
||||||
-- read an analog value scaled from min to max
|
-- read an analog value scaled from min to max
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
---@param rs_value number redstone reading (0 to 15)
|
---@param rs_value number redstone reading (0 to 15)
|
||||||
@ -372,7 +354,7 @@ end
|
|||||||
---@param value number value to write (from min to max range)
|
---@param value number value to write (from min to max range)
|
||||||
---@param min number minimum of range
|
---@param min number minimum of range
|
||||||
---@param max number maximum of range
|
---@param max number maximum of range
|
||||||
---@return number rs_value scaled redstone reading (0 to 15)
|
---@return integer rs_value scaled redstone reading (0 to 15)
|
||||||
function rsio.analog_write(value, min, max)
|
function rsio.analog_write(value, min, max)
|
||||||
local scaled_value = (value - min) / (max - min)
|
local scaled_value = (value - min) / (max - min)
|
||||||
return math.floor(scaled_value * 15)
|
return math.floor(scaled_value * 15)
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
---@class types
|
---@class types
|
||||||
local types = {}
|
local types = {}
|
||||||
|
|
||||||
-- CLASSES --
|
--#region CLASSES
|
||||||
|
|
||||||
---@class tank_fluid
|
---@class tank_fluid
|
||||||
---@field name fluid
|
---@field name fluid
|
||||||
@ -67,12 +67,54 @@ function types.new_zero_coordinate() return { x = 0, y = 0, z = 0 } end
|
|||||||
---@field reactor integer
|
---@field reactor integer
|
||||||
---@field rsio table|nil
|
---@field rsio table|nil
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
-- ALIASES --
|
-- ALIASES --
|
||||||
|
|
||||||
---@alias color integer
|
---@alias color integer
|
||||||
|
|
||||||
-- ENUMERATION TYPES --
|
--#region ENUMERATION TYPES
|
||||||
--#region
|
|
||||||
|
---@enum TEMP_SCALE
|
||||||
|
types.TEMP_SCALE = {
|
||||||
|
KELVIN = 1,
|
||||||
|
CELSIUS = 2,
|
||||||
|
FAHRENHEIT = 3,
|
||||||
|
RANKINE = 4
|
||||||
|
}
|
||||||
|
|
||||||
|
types.TEMP_SCALE_NAMES = {
|
||||||
|
"Kelvin",
|
||||||
|
"Celsius",
|
||||||
|
"Fahrenheit",
|
||||||
|
"Rankine"
|
||||||
|
}
|
||||||
|
|
||||||
|
types.TEMP_SCALE_UNITS = {
|
||||||
|
"K",
|
||||||
|
"\xb0C",
|
||||||
|
"\xb0F",
|
||||||
|
"\xb0R"
|
||||||
|
}
|
||||||
|
|
||||||
|
---@enum ENERGY_SCALE
|
||||||
|
types.ENERGY_SCALE = {
|
||||||
|
JOULES = 1,
|
||||||
|
FE = 2,
|
||||||
|
RF = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
types.ENERGY_SCALE_NAMES = {
|
||||||
|
"Joules (J)",
|
||||||
|
"Forge Energy (FE)",
|
||||||
|
"Redstone Flux (RF)"
|
||||||
|
}
|
||||||
|
|
||||||
|
types.ENERGY_SCALE_UNITS = {
|
||||||
|
"J",
|
||||||
|
"FE",
|
||||||
|
"RF"
|
||||||
|
}
|
||||||
|
|
||||||
---@enum PANEL_LINK_STATE
|
---@enum PANEL_LINK_STATE
|
||||||
types.PANEL_LINK_STATE = {
|
types.PANEL_LINK_STATE = {
|
||||||
@ -128,6 +170,15 @@ function types.rtu_type_to_string(utype)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@enum RTU_ID_FAIL
|
||||||
|
types.RTU_ID_FAIL = {
|
||||||
|
OK = 0,
|
||||||
|
OUT_OF_RANGE = 1,
|
||||||
|
DUPLICATE = 2,
|
||||||
|
MAX_DEVICES = 3,
|
||||||
|
MISSING = 4
|
||||||
|
}
|
||||||
|
|
||||||
---@enum TRI_FAIL
|
---@enum TRI_FAIL
|
||||||
types.TRI_FAIL = {
|
types.TRI_FAIL = {
|
||||||
OK = 1,
|
OK = 1,
|
||||||
@ -249,8 +300,7 @@ types.ALARM_STATE_NAMES = {
|
|||||||
|
|
||||||
--#endregion
|
--#endregion
|
||||||
|
|
||||||
-- STRING TYPES --
|
--#region STRING TYPES
|
||||||
--#region
|
|
||||||
|
|
||||||
---@alias side
|
---@alias side
|
||||||
---|"top"
|
---|"top"
|
||||||
@ -364,8 +414,7 @@ types.DUMPING_MODE = {
|
|||||||
|
|
||||||
--#endregion
|
--#endregion
|
||||||
|
|
||||||
-- MODBUS --
|
--#region MODBUS
|
||||||
--#region
|
|
||||||
|
|
||||||
-- MODBUS function codes
|
-- MODBUS function codes
|
||||||
---@enum MODBUS_FCODE
|
---@enum MODBUS_FCODE
|
||||||
|
@ -4,6 +4,8 @@
|
|||||||
|
|
||||||
local cc_strings = require("cc.strings")
|
local cc_strings = require("cc.strings")
|
||||||
|
|
||||||
|
local const = require("scada-common.constants")
|
||||||
|
|
||||||
local math = math
|
local math = math
|
||||||
local string = string
|
local string = string
|
||||||
local table = table
|
local table = table
|
||||||
@ -22,7 +24,7 @@ local t_pack = table.pack
|
|||||||
local util = {}
|
local util = {}
|
||||||
|
|
||||||
-- scada-common version
|
-- scada-common version
|
||||||
util.version = "1.1.19"
|
util.version = "1.4.3"
|
||||||
|
|
||||||
util.TICK_TIME_S = 0.05
|
util.TICK_TIME_S = 0.05
|
||||||
util.TICK_TIME_MS = 50
|
util.TICK_TIME_MS = 50
|
||||||
@ -108,12 +110,31 @@ function util.pad(str, n)
|
|||||||
return t_concat{util.spaces(lpad), str, util.spaces(rpad)}
|
return t_concat{util.spaces(lpad), str, util.spaces(rpad)}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- trim leading and trailing whitespace
|
||||||
|
---@nodiscard
|
||||||
|
---@param s string text
|
||||||
|
---@return string
|
||||||
|
function util.trim(s)
|
||||||
|
local str = s:gsub("^%s*(.-)%s*$", "%1")
|
||||||
|
return str
|
||||||
|
end
|
||||||
|
|
||||||
-- wrap a string into a table of lines
|
-- wrap a string into a table of lines
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
---@param str string
|
---@param str string
|
||||||
---@param limit integer line limit
|
---@param limit integer line limit, must be greater than 0
|
||||||
---@return table lines
|
---@return table lines
|
||||||
function util.strwrap(str, limit) return cc_strings.wrap(str, limit) end
|
function util.strwrap(str, limit)
|
||||||
|
assert(limit > 0, "util.strwrap() limit not greater than 0")
|
||||||
|
return cc_strings.wrap(str, limit)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- make sure a string is at least 'width' long
|
||||||
|
---@nodiscard
|
||||||
|
---@param str string
|
||||||
|
---@param width integer minimum width
|
||||||
|
---@return string string
|
||||||
|
function util.strminw(str, width) return cc_strings.ensure_width(str, width) end
|
||||||
|
|
||||||
-- concatenation with built-in to string
|
-- concatenation with built-in to string
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
@ -181,8 +202,7 @@ function util.round(x) return math.floor(x + 0.5) end
|
|||||||
-- get a new moving average object
|
-- get a new moving average object
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
---@param length integer history length
|
---@param length integer history length
|
||||||
---@param default number value to fill history with for first call to compute()
|
function util.mov_avg(length)
|
||||||
function util.mov_avg(length, default)
|
|
||||||
local data = {}
|
local data = {}
|
||||||
local index = 1
|
local index = 1
|
||||||
local last_t = 0 ---@type number|nil
|
local last_t = 0 ---@type number|nil
|
||||||
@ -190,11 +210,15 @@ function util.mov_avg(length, default)
|
|||||||
---@class moving_average
|
---@class moving_average
|
||||||
local public = {}
|
local public = {}
|
||||||
|
|
||||||
-- reset all to a given value
|
-- reset all to a given value, or clear all data if no value is given
|
||||||
---@param x number value
|
---@param x number? value
|
||||||
function public.reset(x)
|
function public.reset(x)
|
||||||
|
index = 1
|
||||||
data = {}
|
data = {}
|
||||||
for _ = 1, length do t_insert(data, x) end
|
|
||||||
|
if x then
|
||||||
|
for _ = 1, length do t_insert(data, x) end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- record a new value
|
-- record a new value
|
||||||
@ -214,12 +238,15 @@ function util.mov_avg(length, default)
|
|||||||
---@nodiscard
|
---@nodiscard
|
||||||
---@return number average
|
---@return number average
|
||||||
function public.compute()
|
function public.compute()
|
||||||
local sum = 0
|
if #data == 0 then return 0 end
|
||||||
for i = 1, length do sum = sum + data[i] end
|
|
||||||
return sum / length
|
|
||||||
end
|
|
||||||
|
|
||||||
public.reset(default)
|
local sum = 0
|
||||||
|
for i = 1, #data do
|
||||||
|
sum = sum + data[i]
|
||||||
|
end
|
||||||
|
|
||||||
|
return sum / #data
|
||||||
|
end
|
||||||
|
|
||||||
return public
|
return public
|
||||||
end
|
end
|
||||||
@ -362,72 +389,86 @@ end
|
|||||||
|
|
||||||
--#endregion
|
--#endregion
|
||||||
|
|
||||||
--#region MEKANISM POWER
|
--#region MEKANISM MATH
|
||||||
|
|
||||||
-- convert Joules to FE
|
-- convert Joules to FE (or RF)
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
---@param J number Joules
|
---@param J number Joules
|
||||||
---@return number FE Forge Energy
|
---@return number FE Forge Energy or Redstone Flux
|
||||||
function util.joules_to_fe(J) return (J * 0.4) end
|
function util.joules_to_fe_rf(J) return (J * 0.4) end
|
||||||
|
|
||||||
-- convert FE to Joules
|
-- convert FE (or RF) to Joules
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
---@param FE number Forge Energy
|
---@param FE number Forge Energy or Redstone Flux
|
||||||
---@return number J Joules
|
---@return number J Joules
|
||||||
function util.fe_to_joules(FE) return (FE * 2.5) end
|
function util.fe_rf_to_joules(FE) return (FE * 2.5) end
|
||||||
|
|
||||||
local function kFE(fe) return fe / 1000.0 end
|
-- format a power value into XXX.XX UNIT format<br>
|
||||||
local function MFE(fe) return fe / 1000000.0 end
|
-- example for FE: FE, kFE, MFE, GFE, TFE, PFE, EFE, ZFE
|
||||||
local function GFE(fe) return fe / 1000000000.0 end
|
|
||||||
local function TFE(fe) return fe / 1000000000000.0 end
|
|
||||||
local function PFE(fe) return fe / 1000000000000000.0 end
|
|
||||||
local function EFE(fe) return fe / 1000000000000000000.0 end -- if you accomplish this please touch grass
|
|
||||||
local function ZFE(fe) return fe / 1000000000000000000000.0 end -- how & why did you do this?
|
|
||||||
|
|
||||||
-- format a power value into XXX.XX UNIT format (FE, kFE, MFE, GFE, TFE, PFE, EFE, ZFE)
|
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
---@param fe number forge energy value
|
---@param e number energy value
|
||||||
|
---@param label string energy scale label
|
||||||
---@param combine_label? boolean if a label should be included in the string itself
|
---@param combine_label? boolean if a label should be included in the string itself
|
||||||
---@param format? string format override
|
---@param format? string format override
|
||||||
---@return string str, string? unit
|
---@return string str, string unit
|
||||||
function util.power_format(fe, combine_label, format)
|
function util.power_format(e, label, combine_label, format)
|
||||||
local unit, value
|
local unit, value
|
||||||
|
|
||||||
if type(format) ~= "string" then format = "%.2f" end
|
if type(format) ~= "string" then format = "%.2f" end
|
||||||
|
|
||||||
if fe < 1000.0 then
|
if e < 1000.0 then
|
||||||
unit = "FE"
|
unit = ""
|
||||||
value = fe
|
value = e
|
||||||
elseif fe < 1000000.0 then
|
elseif e < 1000000.0 then
|
||||||
unit = "kFE"
|
unit = "k"
|
||||||
value = kFE(fe)
|
value = e / 1000.0
|
||||||
elseif fe < 1000000000.0 then
|
elseif e < 1000000000.0 then
|
||||||
unit = "MFE"
|
unit = "M"
|
||||||
value = MFE(fe)
|
value = e / 1000000.0
|
||||||
elseif fe < 1000000000000.0 then
|
elseif e < 1000000000000.0 then
|
||||||
unit = "GFE"
|
unit = "G"
|
||||||
value = GFE(fe)
|
value = e / 1000000000.0
|
||||||
elseif fe < 1000000000000000.0 then
|
elseif e < 1000000000000000.0 then
|
||||||
unit = "TFE"
|
unit = "T"
|
||||||
value = TFE(fe)
|
value = e / 1000000000000.0
|
||||||
elseif fe < 1000000000000000000.0 then
|
elseif e < 1000000000000000000.0 then
|
||||||
unit = "PFE"
|
unit = "P"
|
||||||
value = PFE(fe)
|
value = e / 1000000000000000.0
|
||||||
elseif fe < 1000000000000000000000.0 then
|
elseif e < 1000000000000000000000.0 then
|
||||||
unit = "EFE"
|
-- if you accomplish this please touch grass
|
||||||
value = EFE(fe)
|
unit = "E"
|
||||||
|
value = e / 1000000000000000000.0
|
||||||
else
|
else
|
||||||
unit = "ZFE"
|
-- how & why did you do this?
|
||||||
value = ZFE(fe)
|
unit = "Z"
|
||||||
|
value = e / 1000000000000000000000.0
|
||||||
end
|
end
|
||||||
|
|
||||||
|
unit = unit .. label
|
||||||
|
|
||||||
if combine_label then
|
if combine_label then
|
||||||
return util.sprintf(util.c(format, " %s"), value, unit)
|
return util.sprintf(util.c(format, " %s"), value, unit), unit
|
||||||
else
|
else
|
||||||
return util.sprintf(format, value), unit
|
return util.sprintf(format, value), unit
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- compute Mekanism's rotation rate for a turbine
|
||||||
|
---@nodiscard
|
||||||
|
---@param turbine turbinev_session_db turbine data
|
||||||
|
function util.turbine_rotation(turbine)
|
||||||
|
local build = turbine.build
|
||||||
|
|
||||||
|
local inner_vol = build.steam_cap / const.mek.TURBINE_GAS_PER_TANK
|
||||||
|
local disp_rate = (build.dispersers * const.mek.TURBINE_DISPERSER_FLOW) * inner_vol
|
||||||
|
local vent_rate = build.vents * const.mek.TURBINE_VENT_FLOW
|
||||||
|
|
||||||
|
local max_rate = math.min(disp_rate, vent_rate)
|
||||||
|
local flow = math.min(max_rate, turbine.tanks.steam.amount)
|
||||||
|
|
||||||
|
return (flow * (turbine.tanks.steam.amount / build.steam_cap)) / max_rate
|
||||||
|
end
|
||||||
|
|
||||||
--#endregion
|
--#endregion
|
||||||
|
|
||||||
--#region UTILITY CLASSES
|
--#region UTILITY CLASSES
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user