Compare commits
62 Commits
v1.8.18-be
...
v1.8.20-be
| 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 |
2
.github/workflows/manifest.yml
vendored
2
.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
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ 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
|
## Contributing
|
||||||
|
|
||||||
|
|||||||
84
ccmsi.lua
84
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.16"
|
|
||||||
|
|
||||||
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"
|
||||||
@@ -219,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")
|
||||||
@@ -241,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" })
|
||||||
@@ -286,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
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ 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 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")
|
||||||
@@ -46,7 +46,8 @@ local RIGHT = core.ALIGN.RIGHT
|
|||||||
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.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
|
||||||
@@ -70,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
|
||||||
|
|
||||||
@@ -119,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
|
||||||
@@ -151,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 },
|
||||||
@@ -333,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
|
||||||
|
|
||||||
@@ -759,9 +762,13 @@ local function config_view(display)
|
|||||||
TextBox{parent=crd_c_1,x=1,y=8,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=types.TEMP_SCALE_NAMES,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
|
||||||
|
|
||||||
@@ -952,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)
|
||||||
@@ -969,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)
|
||||||
@@ -1122,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 })
|
||||||
@@ -1357,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
|
||||||
val = types.TEMP_SCALE_NAMES[raw]
|
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
|
||||||
|
|||||||
@@ -38,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")
|
||||||
@@ -67,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)
|
||||||
@@ -517,7 +520,7 @@ function coordinator.comms(version, nic, sv_watchdog)
|
|||||||
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() + 1
|
self.sv_r_seq_num = packet.scada_frame.seq_num() + 1
|
||||||
elseif self.sv_r_seq_num ~= 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?")
|
||||||
@@ -702,7 +705,7 @@ 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
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ 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_SCALE = types.TEMP_SCALE
|
||||||
local TEMP_UNITS = types.TEMP_SCALE_UNITS
|
local TEMP_UNITS = types.TEMP_SCALE_UNITS
|
||||||
|
|
||||||
@@ -50,8 +53,10 @@ end
|
|||||||
---@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 TEMP_SCALE temperature unit
|
---@param temp_scale TEMP_SCALE temperature unit
|
||||||
function iocontrol.init(conf, comms, temp_scale)
|
---@param energy_scale ENERGY_SCALE energy unit
|
||||||
io.temp_label = TEMP_UNITS[temp_scale]
|
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 == TEMP_SCALE.CELSIUS then
|
if temp_scale == TEMP_SCALE.CELSIUS then
|
||||||
@@ -65,6 +70,18 @@ function iocontrol.init(conf, comms, temp_scale)
|
|||||||
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 = {
|
||||||
@@ -72,6 +89,7 @@ function iocontrol.init(conf, comms, temp_scale)
|
|||||||
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,
|
||||||
|
|
||||||
@@ -126,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
|
||||||
@@ -692,7 +624,7 @@ function iocontrol.update_facility_status(status)
|
|||||||
ps.publish("is_discharging", out_f > in_f)
|
ps.publish("is_discharging", out_f > in_f)
|
||||||
|
|
||||||
if data and data.build then
|
if data and data.build then
|
||||||
local cap = util.joules_to_fe(data.build.transfer_cap)
|
local cap = util.joules_to_fe_rf(data.build.transfer_cap)
|
||||||
ps.publish("at_max_io", in_f >= cap or out_f >= cap)
|
ps.publish("at_max_io", in_f >= cap or out_f >= cap)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -70,8 +70,8 @@ function process.init(iocontrol, coord_comms)
|
|||||||
|
|
||||||
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)
|
self.io.facility.ps.publish("process_sps_low_power", ctl_proc.sps_low_power)
|
||||||
@@ -316,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
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout)
|
|||||||
local function _handle_packet(pkt)
|
local function _handle_packet(pkt)
|
||||||
-- check sequence number
|
-- check sequence number
|
||||||
if self.r_seq_num ~= pkt.scada_frame.seq_num() then
|
if self.r_seq_num ~= 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())
|
log.warning(log_header .. "sequence out-of-order: next = " .. self.r_seq_num .. ", new = " .. pkt.scada_frame.seq_num())
|
||||||
return
|
return
|
||||||
else
|
else
|
||||||
self.r_seq_num = pkt.scada_frame.seq_num() + 1
|
self.r_seq_num = pkt.scada_frame.seq_num() + 1
|
||||||
@@ -186,6 +186,10 @@ function pocket.new_session(id, s_addr, i_seq_num, 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
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ local renderer = require("coordinator.renderer")
|
|||||||
local sounder = require("coordinator.sounder")
|
local sounder = require("coordinator.sounder")
|
||||||
local threads = require("coordinator.threads")
|
local threads = require("coordinator.threads")
|
||||||
|
|
||||||
local COORDINATOR_VERSION = "v1.5.1"
|
local COORDINATOR_VERSION = "v1.5.6"
|
||||||
|
|
||||||
local CHUNK_LOAD_DELAY_S = 30.0
|
local CHUNK_LOAD_DELAY_S = 30.0
|
||||||
|
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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")
|
||||||
@@ -34,6 +36,8 @@ local function new_view(root, x, y, data, ps, id)
|
|||||||
local ind_yel = style.ind_yel
|
local ind_yel = style.ind_yel
|
||||||
local ind_wht = style.ind_wht
|
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
|
||||||
|
|
||||||
@@ -48,24 +52,24 @@ local function new_view(root, x, y, data, ps, id)
|
|||||||
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 capacity = PowerIndicator{parent=rect,x=7,y=3,lu_colors=lu_col,label="Capacity:",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 energy = PowerIndicator{parent=rect,x=7,y=4,lu_colors=lu_col,label="Energy: ",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 avg_chg = PowerIndicator{parent=rect,x=7,y=5,lu_colors=lu_col,label="\xb7Average:",format="%8.2f",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 input = PowerIndicator{parent=rect,x=7,y=6,lu_colors=lu_col,label="Input: ",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:",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 output = PowerIndicator{parent=rect,x=7,y=8,lu_colors=lu_col,label="Output: ",format="%8.2f",rate=true,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_out = PowerIndicator{parent=rect,x=7,y=9,lu_colors=lu_col,label="\xb7Average:",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 trans_cap = PowerIndicator{parent=rect,x=7,y=10,lu_colors=lu_col,label="Max I/O: ",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)
|
||||||
capacity.register(ps, "max_energy", function (val) capacity.update(util.joules_to_fe(val)) end)
|
capacity.register(ps, "max_energy", function (val) capacity.update(db.energy_convert(val)) end)
|
||||||
energy.register(ps, "energy", function (val) energy.update(util.joules_to_fe(val)) end)
|
energy.register(ps, "energy", function (val) energy.update(db.energy_convert(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(util.joules_to_fe(val)) end)
|
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(util.joules_to_fe(val)) end)
|
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(util.joules_to_fe(val)) end)
|
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: ",format="%7.2f",unit="%",value=0,width=20,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=13,lu_colors=lu_col,label="Cells: ",format="%7d",value=0,width=18,fg_bg=text_fg}
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|
||||||
@@ -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], "avg_charge", function (fe) cur_charge.update(fe / 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 --
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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,14 +24,16 @@ 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}
|
||||||
|
|||||||
@@ -132,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}
|
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}
|
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)
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ local flasher = require("graphics.flasher")
|
|||||||
|
|
||||||
local core = {}
|
local core = {}
|
||||||
|
|
||||||
core.version = "2.3.0"
|
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")
|
||||||
@@ -503,7 +504,10 @@ function element.new(args, constraint, child_offset_x, child_offset_y)
|
|||||||
|
|
||||||
if args.parent ~= nil then
|
if args.parent ~= nil then
|
||||||
-- remove self from parent
|
-- remove self from parent
|
||||||
|
-- log.debug("removing " .. self.id .. " from parent")
|
||||||
args.parent.__remove_child(self.id)
|
args.parent.__remove_child(self.id)
|
||||||
|
else
|
||||||
|
-- log.debug("no parent for " .. self.id .. " on delete attempt")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -57,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
|
||||||
|
|||||||
@@ -33,7 +33,8 @@ 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.9.2", { "Added temperature scale options" } },
|
||||||
|
{ "v0.11.3", { "Added energy scale options" } }
|
||||||
}
|
}
|
||||||
|
|
||||||
---@class pkt_configurator
|
---@class pkt_configurator
|
||||||
@@ -76,6 +77,7 @@ local tool_ctl = {
|
|||||||
---@class pkt_config
|
---@class pkt_config
|
||||||
local tmp_cfg = {
|
local tmp_cfg = {
|
||||||
TempScale = 1,
|
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
|
||||||
@@ -94,7 +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", 1 },
|
{ "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 },
|
||||||
@@ -175,13 +178,17 @@ local function config_view(display)
|
|||||||
|
|
||||||
TextBox{parent=ui_cfg,x=1,y=2,text=" Pocket UI",fg_bg=cpair(colors.black,colors.lime)}
|
TextBox{parent=ui_cfg,x=1,y=2,text=" Pocket UI",fg_bg=cpair(colors.black,colors.lime)}
|
||||||
|
|
||||||
TextBox{parent=ui_c_1,x=1,y=1,height=3,text="You may use the options below to customize formats."}
|
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=5,text="Temperature Scale"}
|
TextBox{parent=ui_c_1,x=1,y=4,text="Temperature Scale"}
|
||||||
local temp_scale = RadioButton{parent=ui_c_1,x=1,y=6,default=ini_cfg.TempScale,options=types.TEMP_SCALE_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime}
|
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()
|
local function submit_ui_opts()
|
||||||
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(3)
|
main_pane.set_value(3)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -372,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)
|
||||||
@@ -504,7 +516,9 @@ local function config_view(display)
|
|||||||
elseif f[1] == "LogMode" then
|
elseif f[1] == "LogMode" then
|
||||||
val = util.trinary(raw == log.MODE.APPEND, "append", "replace")
|
val = util.trinary(raw == log.MODE.APPEND, "append", "replace")
|
||||||
elseif f[1] == "TempScale" then
|
elseif f[1] == "TempScale" then
|
||||||
val = types.TEMP_SCALE_NAMES[raw]
|
val = util.strval(types.TEMP_SCALE_NAMES[raw])
|
||||||
|
elseif f[1] == "EnergyScale" then
|
||||||
|
val = util.strval(types.ENERGY_SCALE_NAMES[raw])
|
||||||
end
|
end
|
||||||
|
|
||||||
if val == "nil" then val = "<not set>" end
|
if val == "nil" then val = "<not set>" end
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ local util = require("scada-common.util")
|
|||||||
|
|
||||||
local ALARM = types.ALARM
|
local ALARM = types.ALARM
|
||||||
local ALARM_STATE = types.ALARM_STATE
|
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_SCALE = types.TEMP_SCALE
|
||||||
local TEMP_UNITS = types.TEMP_SCALE_UNITS
|
local TEMP_UNITS = types.TEMP_SCALE_UNITS
|
||||||
|
|
||||||
@@ -35,10 +38,15 @@ local io = {
|
|||||||
ps = psil.create()
|
ps = psil.create()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local config = nil ---@type pkt_config
|
||||||
|
|
||||||
-- initialize facility-independent components of pocket iocontrol
|
-- initialize facility-independent components of pocket iocontrol
|
||||||
---@param comms pocket_comms
|
---@param comms pocket_comms
|
||||||
---@param nav pocket_nav
|
---@param nav pocket_nav
|
||||||
function iocontrol.init_core(comms, nav)
|
---@param cfg pkt_config
|
||||||
|
function iocontrol.init_core(comms, nav, cfg)
|
||||||
|
config = cfg
|
||||||
|
|
||||||
io.nav = nav
|
io.nav = nav
|
||||||
|
|
||||||
---@class pocket_ioctl_diag
|
---@class pocket_ioctl_diag
|
||||||
@@ -86,10 +94,11 @@ function iocontrol.init_core(comms, nav)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- initialize facility-dependent components of pocket iocontrol
|
-- initialize facility-dependent components of pocket iocontrol
|
||||||
---@param conf facility_conf configuration
|
---@param conf facility_conf facility configuration
|
||||||
---@param temp_scale TEMP_SCALE temperature unit
|
function iocontrol.init_fac(conf)
|
||||||
function iocontrol.init_fac(conf, temp_scale)
|
local temp_scale, energy_scale = config.TempScale, config.EnergyScale
|
||||||
io.temp_label = TEMP_UNITS[temp_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 == TEMP_SCALE.CELSIUS then
|
if temp_scale == TEMP_SCALE.CELSIUS then
|
||||||
@@ -103,6 +112,18 @@ function iocontrol.init_fac(conf, temp_scale)
|
|||||||
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 pioctl_facility
|
---@class pioctl_facility
|
||||||
io.facility = {
|
io.facility = {
|
||||||
@@ -329,8 +350,8 @@ end
|
|||||||
|
|
||||||
-- set network link state
|
-- set network link state
|
||||||
---@param state POCKET_LINK_STATE
|
---@param state POCKET_LINK_STATE
|
||||||
---@param sv_addr integer? supervisor address if linked
|
---@param sv_addr integer|false|nil supervisor address if linked, nil if unchanged, false if unlinked
|
||||||
---@param api_addr integer? coordinator address if linked
|
---@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)
|
function iocontrol.report_link_state(state, sv_addr, api_addr)
|
||||||
io.ps.publish("link_state", state)
|
io.ps.publish("link_state", state)
|
||||||
|
|
||||||
@@ -342,10 +363,25 @@ function iocontrol.report_link_state(state, sv_addr, api_addr)
|
|||||||
io.ps.publish("crd_conn_quality", 0)
|
io.ps.publish("crd_conn_quality", 0)
|
||||||
end
|
end
|
||||||
|
|
||||||
if sv_addr then io.ps.publish("sv_addr", sv_addr) end
|
if sv_addr then
|
||||||
if api_addr then io.ps.publish("api_addr", api_addr) end
|
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
|
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)
|
-- determine supervisor connection quality (trip time)
|
||||||
---@param trip_time integer
|
---@param trip_time integer
|
||||||
function iocontrol.report_svr_tt(trip_time)
|
function iocontrol.report_svr_tt(trip_time)
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ 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.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")
|
||||||
@@ -52,6 +53,8 @@ function pocket.load_config()
|
|||||||
|
|
||||||
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_channel(config.SVR_Channel)
|
cfv.assert_channel(config.SVR_Channel)
|
||||||
cfv.assert_channel(config.CRD_Channel)
|
cfv.assert_channel(config.CRD_Channel)
|
||||||
@@ -490,7 +493,11 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav)
|
|||||||
-- 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()
|
||||||
@@ -499,7 +506,7 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav)
|
|||||||
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()
|
||||||
@@ -507,9 +514,6 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav)
|
|||||||
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, self.sv.addr, self.api.addr)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -606,7 +610,7 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav)
|
|||||||
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() + 1
|
self.api.r_seq_num = packet.scada_frame.seq_num() + 1
|
||||||
elseif self.api.r_seq_num ~= 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 ..
|
||||||
@@ -675,17 +679,19 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav)
|
|||||||
-- get configuration
|
-- get configuration
|
||||||
local conf = { num_units = fac_config[1], cooling = fac_config[2] }
|
local conf = { num_units = fac_config[1], cooling = fac_config[2] }
|
||||||
|
|
||||||
iocontrol.init_fac(conf, config.TempScale)
|
iocontrol.init_fac(conf)
|
||||||
|
|
||||||
log.info("coordinator connection established")
|
log.info("coordinator connection established")
|
||||||
self.establish_delay_counter = 0
|
self.establish_delay_counter = 0
|
||||||
self.api.linked = true
|
self.api.linked = true
|
||||||
self.api.addr = src_addr
|
self.api.addr = src_addr
|
||||||
|
|
||||||
|
iocontrol.report_crd_link_error("")
|
||||||
|
|
||||||
if self.sv.linked then
|
if self.sv.linked then
|
||||||
iocontrol.report_link_state(LINK_STATE.LINKED, self.sv.addr, self.api.addr)
|
iocontrol.report_link_state(LINK_STATE.LINKED, nil, self.api.addr)
|
||||||
else
|
else
|
||||||
iocontrol.report_link_state(LINK_STATE.API_LINK_ONLY)
|
iocontrol.report_link_state(LINK_STATE.API_LINK_ONLY, nil, self.api.addr)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
log.debug("invalid facility configuration table received from coordinator, establish failed")
|
log.debug("invalid facility configuration table received from coordinator, establish failed")
|
||||||
@@ -693,24 +699,29 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav)
|
|||||||
else
|
else
|
||||||
log.debug("received coordinator establish allow without facility configuration")
|
log.debug("received coordinator establish allow without facility configuration")
|
||||||
end
|
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
|
|
||||||
elseif est_ack == ESTABLISH_ACK.BAD_API_VERSION then
|
|
||||||
if self.api.last_est_ack ~= est_ack then
|
|
||||||
log.info("coordinator api version mismatch")
|
|
||||||
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
|
||||||
@@ -726,7 +737,7 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav)
|
|||||||
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() + 1
|
self.sv.r_seq_num = packet.scada_frame.seq_num() + 1
|
||||||
elseif self.sv.r_seq_num ~= 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 ..
|
||||||
@@ -822,25 +833,33 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav)
|
|||||||
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, self.sv.addr, self.api.addr)
|
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
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ local pocket = require("pocket.pocket")
|
|||||||
local renderer = require("pocket.renderer")
|
local renderer = require("pocket.renderer")
|
||||||
local threads = require("pocket.threads")
|
local threads = require("pocket.threads")
|
||||||
|
|
||||||
local POCKET_VERSION = "v0.11.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
|
||||||
@@ -152,7 +152,7 @@ local function main()
|
|||||||
log.debug("startup> comms init")
|
log.debug("startup> comms init")
|
||||||
|
|
||||||
-- init I/O control
|
-- init I/O control
|
||||||
iocontrol.init_core(smem_sys.pocket_comms, smem_sys.nav)
|
iocontrol.init_core(smem_sys.pocket_comms, smem_sys.nav, config)
|
||||||
|
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
-- start the UI
|
-- start the UI
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ local function new_view(root)
|
|||||||
for idx = 1, #s_results[tier] do
|
for idx = 1, #s_results[tier] do
|
||||||
local entry = s_results[tier][idx]
|
local entry = s_results[tier][idx]
|
||||||
TextBox{parent=search_results,text=entry[3].." >",fg_bg=cpair(colors.gray,colors.black)}
|
TextBox{parent=search_results,text=entry[3].." >",fg_bg=cpair(colors.gray,colors.black)}
|
||||||
PushButton{parent=search_results,text=entry[2],alignment=ALIGN.LEFT,fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=entry[4]}
|
PushButton{parent=search_results,text=entry[2],fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=entry[4]}
|
||||||
|
|
||||||
empty = false
|
empty = false
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -63,25 +63,25 @@ local function create_pages(root)
|
|||||||
|
|
||||||
PushButton{parent=nt_div,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=about_page.nav_to}
|
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",alignment=ALIGN.LEFT,fg_bg=label}
|
TextBox{parent=nt_div,x=2,y=3,text="Pocket Address",fg_bg=label}
|
||||||
---@diagnostic disable-next-line: undefined-field
|
---@diagnostic disable-next-line: undefined-field
|
||||||
TextBox{parent=nt_div,x=2,text=util.c(os.getComputerID(),":",config.PKT_Channel),alignment=ALIGN.LEFT}
|
TextBox{parent=nt_div,x=2,text=util.c(os.getComputerID(),":",config.PKT_Channel)}
|
||||||
|
|
||||||
nt_div.line_break()
|
nt_div.line_break()
|
||||||
TextBox{parent=nt_div,x=2,text="Supervisor Address",alignment=ALIGN.LEFT,fg_bg=label}
|
TextBox{parent=nt_div,x=2,text="Supervisor Address",fg_bg=label}
|
||||||
local sv = TextBox{parent=nt_div,x=2,text="",alignment=ALIGN.LEFT}
|
local sv = TextBox{parent=nt_div,x=2,text=""}
|
||||||
|
|
||||||
nt_div.line_break()
|
nt_div.line_break()
|
||||||
TextBox{parent=nt_div,x=2,text="Coordinator Address",alignment=ALIGN.LEFT,fg_bg=label}
|
TextBox{parent=nt_div,x=2,text="Coordinator Address",fg_bg=label}
|
||||||
local coord = TextBox{parent=nt_div,x=2,text="",alignment=ALIGN.LEFT}
|
local coord = TextBox{parent=nt_div,x=2,text=""}
|
||||||
|
|
||||||
sv.register(db.ps, "sv_addr", function (addr) sv.set_value(util.c(addr, ":", config.SVR_Channel)) end)
|
sv.register(db.ps, "sv_addr", sv.set_value)
|
||||||
coord.register(db.ps, "api_addr", function (addr) coord.set_value(util.c(addr, ":", config.CRD_Channel)) end)
|
coord.register(db.ps, "api_addr", coord.set_value)
|
||||||
|
|
||||||
nt_div.line_break()
|
nt_div.line_break()
|
||||||
TextBox{parent=nt_div,x=2,text="Message Authentication",alignment=ALIGN.LEFT,fg_bg=label}
|
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")
|
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,alignment=ALIGN.LEFT}
|
TextBox{parent=nt_div,x=2,text=auth}
|
||||||
|
|
||||||
--#endregion
|
--#endregion
|
||||||
|
|
||||||
@@ -96,28 +96,28 @@ local function create_pages(root)
|
|||||||
|
|
||||||
local fw_list = Div{parent=fw_list_box,x=1,y=2,height=18}
|
local fw_list = Div{parent=fw_list_box,x=1,y=2,height=18}
|
||||||
|
|
||||||
TextBox{parent=fw_list,x=2,text="Pocket Version",alignment=ALIGN.LEFT,fg_bg=label}
|
TextBox{parent=fw_list,x=2,text="Pocket Version",fg_bg=label}
|
||||||
TextBox{parent=fw_list,x=2,text=db.version,alignment=ALIGN.LEFT}
|
TextBox{parent=fw_list,x=2,text=db.version}
|
||||||
|
|
||||||
fw_list.line_break()
|
fw_list.line_break()
|
||||||
TextBox{parent=fw_list,x=2,text="Comms Version",alignment=ALIGN.LEFT,fg_bg=label}
|
TextBox{parent=fw_list,x=2,text="Comms Version",fg_bg=label}
|
||||||
TextBox{parent=fw_list,x=2,text=comms.version,alignment=ALIGN.LEFT}
|
TextBox{parent=fw_list,x=2,text=comms.version}
|
||||||
|
|
||||||
fw_list.line_break()
|
fw_list.line_break()
|
||||||
TextBox{parent=fw_list,x=2,text="API Version",alignment=ALIGN.LEFT,fg_bg=label}
|
TextBox{parent=fw_list,x=2,text="API Version",fg_bg=label}
|
||||||
TextBox{parent=fw_list,x=2,text=comms.api_version,alignment=ALIGN.LEFT}
|
TextBox{parent=fw_list,x=2,text=comms.api_version}
|
||||||
|
|
||||||
fw_list.line_break()
|
fw_list.line_break()
|
||||||
TextBox{parent=fw_list,x=2,text="Common Lib Version",alignment=ALIGN.LEFT,fg_bg=label}
|
TextBox{parent=fw_list,x=2,text="Common Lib Version",fg_bg=label}
|
||||||
TextBox{parent=fw_list,x=2,text=util.version,alignment=ALIGN.LEFT}
|
TextBox{parent=fw_list,x=2,text=util.version}
|
||||||
|
|
||||||
fw_list.line_break()
|
fw_list.line_break()
|
||||||
TextBox{parent=fw_list,x=2,text="Graphics Version",alignment=ALIGN.LEFT,fg_bg=label}
|
TextBox{parent=fw_list,x=2,text="Graphics Version",fg_bg=label}
|
||||||
TextBox{parent=fw_list,x=2,text=core.version,alignment=ALIGN.LEFT}
|
TextBox{parent=fw_list,x=2,text=core.version}
|
||||||
|
|
||||||
fw_list.line_break()
|
fw_list.line_break()
|
||||||
TextBox{parent=fw_list,x=2,text="Lockbox Version",alignment=ALIGN.LEFT,fg_bg=label}
|
TextBox{parent=fw_list,x=2,text="Lockbox Version",fg_bg=label}
|
||||||
TextBox{parent=fw_list,x=2,text=lockbox.version,alignment=ALIGN.LEFT}
|
TextBox{parent=fw_list,x=2,text=lockbox.version}
|
||||||
|
|
||||||
--#endregion
|
--#endregion
|
||||||
|
|
||||||
@@ -129,12 +129,12 @@ local function create_pages(root)
|
|||||||
PushButton{parent=hw_div,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=about_page.nav_to}
|
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()
|
hw_div.line_break()
|
||||||
TextBox{parent=hw_div,x=2,text="Lua Version",alignment=ALIGN.LEFT,fg_bg=label}
|
TextBox{parent=hw_div,x=2,text="Lua Version",fg_bg=label}
|
||||||
TextBox{parent=hw_div,x=2,text=_VERSION,alignment=ALIGN.LEFT}
|
TextBox{parent=hw_div,x=2,text=_VERSION}
|
||||||
|
|
||||||
hw_div.line_break()
|
hw_div.line_break()
|
||||||
TextBox{parent=hw_div,x=2,text="Environment",alignment=ALIGN.LEFT,fg_bg=label}
|
TextBox{parent=hw_div,x=2,text="Environment",fg_bg=label}
|
||||||
TextBox{parent=hw_div,x=2,text=_HOST,height=6,alignment=ALIGN.LEFT}
|
TextBox{parent=hw_div,x=2,text=_HOST,height=6}
|
||||||
|
|
||||||
--#endregion
|
--#endregion
|
||||||
|
|
||||||
|
|||||||
@@ -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=6}
|
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,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,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
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ local function init(main)
|
|||||||
local db = iocontrol.get_db()
|
local db = iocontrol.get_db()
|
||||||
|
|
||||||
-- window header message and connection status
|
-- window header message and connection status
|
||||||
TextBox{parent=main,y=1,text="EARLY ACCESS ALPHA S C ",alignment=ALIGN.LEFT,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 svr_conn = SignalBar{parent=main,y=1,x=22,compact=true,colors_low_med=cpair(colors.red,colors.yellow),disconnect_color=colors.lightGray,fg_bg=cpair(colors.green,colors.gray)}
|
||||||
local crd_conn = SignalBar{parent=main,y=1,x=26,compact=true,colors_low_med=cpair(colors.red,colors.yellow),disconnect_color=colors.lightGray,fg_bg=cpair(colors.green,colors.gray)}
|
local crd_conn = SignalBar{parent=main,y=1,x=26,compact=true,colors_low_med=cpair(colors.red,colors.yellow),disconnect_color=colors.lightGray,fg_bg=cpair(colors.green,colors.gray)}
|
||||||
|
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ return function (data, base_page, title, items, scroll_height)
|
|||||||
doc_map[item.key] = view
|
doc_map[item.key] = view
|
||||||
table.insert(search_db, { string.lower(item.name), item.name, title, view })
|
table.insert(search_db, { string.lower(item.name), item.name, title, view })
|
||||||
|
|
||||||
PushButton{parent=name_list,text=item.name,alignment=ALIGN.LEFT,fg_bg=cpair(colors.blue,colors.black),active_fg_bg=btn_active,callback=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
|
if i % 12 == 0 then util.nop() end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -59,13 +59,13 @@ return function (app, u_page, panes, tbn_pane, u_id, t_id, ps, update)
|
|||||||
ccool.register(ps, "energy_fill", ccool.update)
|
ccool.register(ps, "energy_fill", ccool.update)
|
||||||
|
|
||||||
TextBox{parent=tbn_div,text="Production",x=3,y=3,width=17,fg_bg=label}
|
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="",format="%11.2f",value=0,rate=true,width=17,fg_bg=text_fg}
|
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}
|
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}
|
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}
|
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}
|
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(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, "flow_rate", flow_rate.update)
|
flow_rate.register(ps, "flow_rate", flow_rate.update)
|
||||||
input_rate.register(ps, "steam_input_rate", input_rate.update)
|
input_rate.register(ps, "steam_input_rate", input_rate.update)
|
||||||
|
|
||||||
@@ -99,10 +99,10 @@ return function (app, u_page, panes, tbn_pane, u_id, t_id, ps, update)
|
|||||||
|
|
||||||
TextBox{parent=tbn_ext_div,text="Energy Fill",x=1,y=6,width=12,fg_bg=label}
|
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_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="",format="%17.4f",value=0,width=21,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_p.register(ps, "energy_fill", function (x) charge_p.update(x * 100) end)
|
||||||
charge_amnt.register(ps, "energy", charge_amnt.update)
|
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}
|
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}
|
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}
|
||||||
|
|||||||
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,38 +2,30 @@
|
|||||||
-- 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 = {
|
||||||
@@ -48,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
|
||||||
@@ -72,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
|
||||||
@@ -127,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
|
||||||
@@ -164,6 +127,11 @@ 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
|
||||||
@@ -179,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
|
||||||
|
|
||||||
@@ -206,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
|
||||||
@@ -216,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,dis_fg_bg=cpair(colors.lightGray,colors.white)}
|
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
|
||||||
@@ -224,439 +194,18 @@ 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,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,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,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}
|
|
||||||
|
|
||||||
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)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,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
|
|
||||||
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,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 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,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}
|
||||||
|
|
||||||
@@ -675,103 +224,13 @@ local function config_view(display)
|
|||||||
|
|
||||||
PushButton{parent=cl,x=1,y=14,text="\x1b Back",callback=function()main_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=cl,x=1,y=14,text="\x1b Back",callback=function()main_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
-- 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
|
|
||||||
|
|
||||||
if cfg.AuthKey then tool_ctl.show_key_btn.enable() else tool_ctl.show_key_btn.disable() end
|
|
||||||
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" 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 tool_ctl.auth_key_textbox = textbox end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- reset terminal screen
|
-- reset terminal screen
|
||||||
@@ -802,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
|
||||||
@@ -815,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
|
||||||
|
|||||||
@@ -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}
|
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}
|
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)
|
||||||
|
|||||||
@@ -57,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(len == 0 or len >= 8)
|
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, themes.COLOR_MODE.NUM_MODES)
|
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()
|
||||||
@@ -827,7 +833,7 @@ function plc.comms(version, nic, reactor, rps, conn_watchdog)
|
|||||||
if self.r_seq_num == nil then
|
if self.r_seq_num == nil then
|
||||||
self.r_seq_num = packet.scada_frame.seq_num() + 1
|
self.r_seq_num = packet.scada_frame.seq_num() + 1
|
||||||
elseif self.r_seq_num ~= 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 ..
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ local plc = require("reactor-plc.plc")
|
|||||||
local renderer = require("reactor-plc.renderer")
|
local renderer = require("reactor-plc.renderer")
|
||||||
local threads = require("reactor-plc.threads")
|
local threads = require("reactor-plc.threads")
|
||||||
|
|
||||||
local R_PLC_VERSION = "v1.8.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
|
||||||
|
|||||||
@@ -84,7 +84,8 @@ assert(#PORT_DSGN == rsio.NUM_PORTS)
|
|||||||
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.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
|
||||||
@@ -158,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
|
||||||
@@ -285,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."}
|
||||||
@@ -650,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
|
||||||
@@ -828,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()
|
||||||
@@ -885,22 +870,12 @@ 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 = tri(type == "inductionPort", "induction matrix", "SPS")
|
local dev = tri(type == "inductionPort", "induction matrix", "SPS")
|
||||||
@@ -908,7 +883,6 @@ local function config_view(display)
|
|||||||
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
|
||||||
@@ -965,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)
|
||||||
|
|||||||
@@ -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}
|
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}
|
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)
|
||||||
|
|||||||
@@ -444,7 +444,7 @@ function rtu.comms(version, nic, conn_watchdog)
|
|||||||
if self.r_seq_num == nil then
|
if self.r_seq_num == nil then
|
||||||
self.r_seq_num = packet.scada_frame.seq_num() + 1
|
self.r_seq_num = packet.scada_frame.seq_num() + 1
|
||||||
elseif self.r_seq_num ~= 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 ..
|
||||||
@@ -571,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.10.1"
|
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
|
||||||
|
|||||||
@@ -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,13 @@ 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
|
---@enum TEMP_SCALE
|
||||||
types.TEMP_SCALE = {
|
types.TEMP_SCALE = {
|
||||||
@@ -96,6 +97,25 @@ types.TEMP_SCALE_UNITS = {
|
|||||||
"\xb0R"
|
"\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 = {
|
||||||
LINKED = 1,
|
LINKED = 1,
|
||||||
@@ -150,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,
|
||||||
@@ -271,8 +300,7 @@ types.ALARM_STATE_NAMES = {
|
|||||||
|
|
||||||
--#endregion
|
--#endregion
|
||||||
|
|
||||||
-- STRING TYPES --
|
--#region STRING TYPES
|
||||||
--#region
|
|
||||||
|
|
||||||
---@alias side
|
---@alias side
|
||||||
---|"top"
|
---|"top"
|
||||||
@@ -386,8 +414,7 @@ types.DUMPING_MODE = {
|
|||||||
|
|
||||||
--#endregion
|
--#endregion
|
||||||
|
|
||||||
-- MODBUS --
|
--#region MODBUS
|
||||||
--#region
|
|
||||||
|
|
||||||
-- MODBUS function codes
|
-- MODBUS function codes
|
||||||
---@enum MODBUS_FCODE
|
---@enum MODBUS_FCODE
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ local t_pack = table.pack
|
|||||||
local util = {}
|
local util = {}
|
||||||
|
|
||||||
-- scada-common version
|
-- scada-common version
|
||||||
util.version = "1.4.0"
|
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
|
||||||
@@ -110,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
|
||||||
@@ -372,65 +391,63 @@ end
|
|||||||
|
|
||||||
--#region MEKANISM MATH
|
--#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
|
||||||
|
|||||||
@@ -181,7 +181,7 @@ local function config_view(display)
|
|||||||
TextBox{parent=main_page,x=2,y=2,height=2,text="Welcome to the Supervisor configurator! Please select one of the following options."}
|
TextBox{parent=main_page,x=2,y=2,height=2,text="Welcome to the Supervisor configurator! Please select one of the following options."}
|
||||||
|
|
||||||
if tool_ctl.ask_config then
|
if tool_ctl.ask_config then
|
||||||
TextBox{parent=main_page,x=2,y=y_start,height=4,width=49,text="Notice: This device 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
|
||||||
|
|
||||||
@@ -453,15 +453,16 @@ local function config_view(display)
|
|||||||
|
|
||||||
vis_unit_list.set_value(u_text)
|
vis_unit_list.set_value(u_text)
|
||||||
|
|
||||||
|
local vis_ftanks = tool_ctl.vis_ftanks
|
||||||
local next_idx = 1
|
local next_idx = 1
|
||||||
|
|
||||||
if is_ft(1) then
|
if is_ft(1) then
|
||||||
next_idx = 2
|
next_idx = 2
|
||||||
|
|
||||||
if (mode == 1 and (is_ft(2) or is_ft(3) or is_ft(4))) or (mode == 2 and (is_ft(2) or is_ft(3))) or ((mode == 3 or mode == 5) and is_ft(2)) then
|
if (mode == 1 and (is_ft(2) or is_ft(3) or is_ft(4))) or (mode == 2 and (is_ft(2) or is_ft(3))) or ((mode == 3 or mode == 5) and is_ft(2)) then
|
||||||
tool_ctl.vis_ftanks[1].pipe_direct.set_value("\x8c\x8c\x8c\x9c\x8c")
|
vis_ftanks[1].pipe_direct.set_value("\x8c\x8c\x8c\x9c\x8c")
|
||||||
else
|
else
|
||||||
tool_ctl.vis_ftanks[1].pipe_direct.set_value(string.rep("\x8c",5))
|
vis_ftanks[1].pipe_direct.set_value(string.rep("\x8c",5))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -469,108 +470,108 @@ local function config_view(display)
|
|||||||
local _2_46_need_chain = (mode == 4 and (is_ft(3) or is_ft(4))) or (mode == 6 and is_ft(3))
|
local _2_46_need_chain = (mode == 4 and (is_ft(3) or is_ft(4))) or (mode == 6 and is_ft(3))
|
||||||
|
|
||||||
if is_ft(2) then
|
if is_ft(2) then
|
||||||
tool_ctl.vis_ftanks[2].label.set_value("Tank F" .. next_idx)
|
vis_ftanks[2].label.set_value("Tank F" .. next_idx)
|
||||||
|
|
||||||
if (mode < 4 or mode == 5) and is_ft(1) then
|
if (mode < 4 or mode == 5) and is_ft(1) then
|
||||||
tool_ctl.vis_ftanks[2].label.hide(true)
|
vis_ftanks[2].label.hide(true)
|
||||||
tool_ctl.vis_ftanks[2].pipe_direct.hide(true)
|
vis_ftanks[2].pipe_direct.hide(true)
|
||||||
if _2_12_need_passt then
|
if _2_12_need_passt then
|
||||||
tool_ctl.vis_ftanks[2].pipe_chain.set_value("\x95\n\x9d")
|
vis_ftanks[2].pipe_chain.set_value("\x95\n\x9d")
|
||||||
else
|
else
|
||||||
tool_ctl.vis_ftanks[2].pipe_chain.set_value("\x95\n\x8d")
|
vis_ftanks[2].pipe_chain.set_value("\x95\n\x8d")
|
||||||
end
|
end
|
||||||
tool_ctl.vis_ftanks[2].pipe_chain.show()
|
vis_ftanks[2].pipe_chain.show()
|
||||||
else
|
else
|
||||||
tool_ctl.vis_ftanks[2].label.show()
|
vis_ftanks[2].label.show()
|
||||||
next_idx = next_idx + 1
|
next_idx = next_idx + 1
|
||||||
|
|
||||||
tool_ctl.vis_ftanks[2].pipe_chain.hide(true)
|
vis_ftanks[2].pipe_chain.hide(true)
|
||||||
if _2_12_need_passt or _2_46_need_chain then
|
if _2_12_need_passt or _2_46_need_chain then
|
||||||
tool_ctl.vis_ftanks[2].pipe_direct.set_value("\x8c\x8c\x8c\x9c")
|
vis_ftanks[2].pipe_direct.set_value("\x8c\x8c\x8c\x9c")
|
||||||
else
|
else
|
||||||
tool_ctl.vis_ftanks[2].pipe_direct.set_value("\x8c\x8c\x8c\x8c")
|
vis_ftanks[2].pipe_direct.set_value("\x8c\x8c\x8c\x8c")
|
||||||
end
|
end
|
||||||
tool_ctl.vis_ftanks[2].pipe_direct.show()
|
vis_ftanks[2].pipe_direct.show()
|
||||||
end
|
end
|
||||||
|
|
||||||
tool_ctl.vis_ftanks[2].line.show()
|
vis_ftanks[2].line.show()
|
||||||
elseif is_ft(1) and _2_12_need_passt then
|
elseif is_ft(1) and _2_12_need_passt then
|
||||||
tool_ctl.vis_ftanks[2].label.hide(true)
|
vis_ftanks[2].label.hide(true)
|
||||||
tool_ctl.vis_ftanks[2].pipe_direct.hide(true)
|
vis_ftanks[2].pipe_direct.hide(true)
|
||||||
tool_ctl.vis_ftanks[2].pipe_chain.set_value("\x95\n\x95")
|
vis_ftanks[2].pipe_chain.set_value("\x95\n\x95")
|
||||||
tool_ctl.vis_ftanks[2].pipe_chain.show()
|
vis_ftanks[2].pipe_chain.show()
|
||||||
tool_ctl.vis_ftanks[2].line.show()
|
vis_ftanks[2].line.show()
|
||||||
else
|
else
|
||||||
tool_ctl.vis_ftanks[2].line.hide(true)
|
vis_ftanks[2].line.hide(true)
|
||||||
end
|
end
|
||||||
|
|
||||||
if is_ft(3) then
|
if is_ft(3) then
|
||||||
tool_ctl.vis_ftanks[3].label.set_value("Tank F" .. next_idx)
|
vis_ftanks[3].label.set_value("Tank F" .. next_idx)
|
||||||
|
|
||||||
if (mode < 3 and (is_ft(1) or is_ft(2))) or ((mode == 4 or mode == 6) and is_ft(2)) then
|
if (mode < 3 and (is_ft(1) or is_ft(2))) or ((mode == 4 or mode == 6) and is_ft(2)) then
|
||||||
tool_ctl.vis_ftanks[3].label.hide(true)
|
vis_ftanks[3].label.hide(true)
|
||||||
tool_ctl.vis_ftanks[3].pipe_direct.hide(true)
|
vis_ftanks[3].pipe_direct.hide(true)
|
||||||
if (mode == 1 or mode == 4) and is_ft(4) then
|
if (mode == 1 or mode == 4) and is_ft(4) then
|
||||||
tool_ctl.vis_ftanks[3].pipe_chain.set_value("\x95\n\x9d")
|
vis_ftanks[3].pipe_chain.set_value("\x95\n\x9d")
|
||||||
else
|
else
|
||||||
tool_ctl.vis_ftanks[3].pipe_chain.set_value("\x95\n\x8d")
|
vis_ftanks[3].pipe_chain.set_value("\x95\n\x8d")
|
||||||
end
|
end
|
||||||
tool_ctl.vis_ftanks[3].pipe_chain.show()
|
vis_ftanks[3].pipe_chain.show()
|
||||||
else
|
else
|
||||||
tool_ctl.vis_ftanks[3].label.show()
|
vis_ftanks[3].label.show()
|
||||||
next_idx = next_idx + 1
|
next_idx = next_idx + 1
|
||||||
|
|
||||||
tool_ctl.vis_ftanks[3].pipe_chain.hide(true)
|
vis_ftanks[3].pipe_chain.hide(true)
|
||||||
if (mode == 1 or mode == 3 or mode == 4 or mode == 7) and is_ft(4) then
|
if (mode == 1 or mode == 3 or mode == 4 or mode == 7) and is_ft(4) then
|
||||||
tool_ctl.vis_ftanks[3].pipe_direct.set_value("\x8c\x8c\x8c\x9c")
|
vis_ftanks[3].pipe_direct.set_value("\x8c\x8c\x8c\x9c")
|
||||||
else
|
else
|
||||||
tool_ctl.vis_ftanks[3].pipe_direct.set_value("\x8c\x8c\x8c\x8c")
|
vis_ftanks[3].pipe_direct.set_value("\x8c\x8c\x8c\x8c")
|
||||||
end
|
end
|
||||||
tool_ctl.vis_ftanks[3].pipe_direct.show()
|
vis_ftanks[3].pipe_direct.show()
|
||||||
end
|
end
|
||||||
|
|
||||||
tool_ctl.vis_ftanks[3].line.show()
|
vis_ftanks[3].line.show()
|
||||||
elseif (mode == 1 and is_ft(4) and (is_ft(1) or is_ft(2))) or (mode == 4 and is_ft(2) and is_ft(4)) then
|
elseif (mode == 1 and is_ft(4) and (is_ft(1) or is_ft(2))) or (mode == 4 and is_ft(2) and is_ft(4)) then
|
||||||
tool_ctl.vis_ftanks[3].label.hide(true)
|
vis_ftanks[3].label.hide(true)
|
||||||
tool_ctl.vis_ftanks[3].pipe_direct.hide(true)
|
vis_ftanks[3].pipe_direct.hide(true)
|
||||||
tool_ctl.vis_ftanks[3].pipe_chain.set_value("\x95\n\x95")
|
vis_ftanks[3].pipe_chain.set_value("\x95\n\x95")
|
||||||
tool_ctl.vis_ftanks[3].pipe_chain.show()
|
vis_ftanks[3].pipe_chain.show()
|
||||||
tool_ctl.vis_ftanks[3].line.show()
|
vis_ftanks[3].line.show()
|
||||||
else
|
else
|
||||||
tool_ctl.vis_ftanks[3].line.hide(true)
|
vis_ftanks[3].line.hide(true)
|
||||||
end
|
end
|
||||||
|
|
||||||
if is_ft(4) then
|
if is_ft(4) then
|
||||||
tool_ctl.vis_ftanks[4].label.set_value("Tank F" .. next_idx)
|
vis_ftanks[4].label.set_value("Tank F" .. next_idx)
|
||||||
|
|
||||||
if (mode == 1 and (is_ft(1) or is_ft(2) or is_ft(3))) or ((mode == 3 or mode == 7) and is_ft(3)) or (mode == 4 and (is_ft(2) or is_ft(3))) then
|
if (mode == 1 and (is_ft(1) or is_ft(2) or is_ft(3))) or ((mode == 3 or mode == 7) and is_ft(3)) or (mode == 4 and (is_ft(2) or is_ft(3))) then
|
||||||
tool_ctl.vis_ftanks[4].label.hide(true)
|
vis_ftanks[4].label.hide(true)
|
||||||
tool_ctl.vis_ftanks[4].pipe_direct.hide(true)
|
vis_ftanks[4].pipe_direct.hide(true)
|
||||||
tool_ctl.vis_ftanks[4].pipe_chain.show()
|
vis_ftanks[4].pipe_chain.show()
|
||||||
else
|
else
|
||||||
tool_ctl.vis_ftanks[4].label.show()
|
vis_ftanks[4].label.show()
|
||||||
tool_ctl.vis_ftanks[4].pipe_chain.hide(true)
|
vis_ftanks[4].pipe_chain.hide(true)
|
||||||
tool_ctl.vis_ftanks[4].pipe_direct.show()
|
vis_ftanks[4].pipe_direct.show()
|
||||||
end
|
end
|
||||||
|
|
||||||
tool_ctl.vis_ftanks[4].line.show()
|
vis_ftanks[4].line.show()
|
||||||
else
|
else
|
||||||
tool_ctl.vis_ftanks[4].line.hide(true)
|
vis_ftanks[4].line.hide(true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function change_mode(mode)
|
||||||
|
tmp_cfg.FacilityTankMode = mode
|
||||||
|
tool_ctl.vis_draw(mode)
|
||||||
|
end
|
||||||
|
|
||||||
local tank_modes = { "Mode 1", "Mode 2", "Mode 3", "Mode 4", "Mode 5", "Mode 6", "Mode 7", "Mode 8" }
|
local tank_modes = { "Mode 1", "Mode 2", "Mode 3", "Mode 4", "Mode 5", "Mode 6", "Mode 7", "Mode 8" }
|
||||||
local tank_mode = RadioButton{parent=svr_c_5,x=1,y=4,callback=tool_ctl.vis_draw,default=math.max(1,ini_cfg.FacilityTankMode),options=tank_modes,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.yellow}
|
local tank_mode = RadioButton{parent=svr_c_5,x=1,y=4,callback=change_mode,default=math.max(1,ini_cfg.FacilityTankMode),options=tank_modes,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.yellow}
|
||||||
|
|
||||||
--#endregion
|
--#endregion
|
||||||
|
|
||||||
local function submit_mode()
|
|
||||||
tmp_cfg.FacilityTankMode = tank_mode.get_value()
|
|
||||||
svr_pane.set_value(7)
|
|
||||||
end
|
|
||||||
|
|
||||||
PushButton{parent=svr_c_5,x=1,y=14,text="\x1b Back",callback=function()svr_pane.set_value(4)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=svr_c_5,x=1,y=14,text="\x1b Back",callback=function()svr_pane.set_value(4)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
PushButton{parent=svr_c_5,x=44,y=14,text="Next \x1a",callback=submit_mode,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=svr_c_5,x=44,y=14,text="Next \x1a",callback=function()svr_pane.set_value(7)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
PushButton{parent=svr_c_5,x=8,y=14,min_width=7,text="About",callback=function()svr_pane.set_value(6)end,fg_bg=cpair(colors.black,colors.lightBlue),active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=svr_c_5,x=8,y=14,min_width=7,text="About",callback=function()svr_pane.set_value(6)end,fg_bg=cpair(colors.black,colors.lightBlue),active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
@@ -906,7 +907,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("/supervisor.settings") then
|
if settings.save("/supervisor.settings") then
|
||||||
load_settings(settings_cfg, true)
|
load_settings(settings_cfg, true)
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
832
supervisor/facility_update.lua
Normal file
832
supervisor/facility_update.lua
Normal file
@@ -0,0 +1,832 @@
|
|||||||
|
local audio = require("scada-common.audio")
|
||||||
|
local const = require("scada-common.constants")
|
||||||
|
local log = require("scada-common.log")
|
||||||
|
local rsio = require("scada-common.rsio")
|
||||||
|
local types = require("scada-common.types")
|
||||||
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
|
local qtypes = require("supervisor.session.rtu.qtypes")
|
||||||
|
|
||||||
|
local TONE = audio.TONE
|
||||||
|
|
||||||
|
local ALARM = types.ALARM
|
||||||
|
local PRIO = types.ALARM_PRIORITY
|
||||||
|
local ALARM_STATE = types.ALARM_STATE
|
||||||
|
local CONTAINER_MODE = types.CONTAINER_MODE
|
||||||
|
local PROCESS = types.PROCESS
|
||||||
|
local PROCESS_NAMES = types.PROCESS_NAMES
|
||||||
|
local WASTE_MODE = types.WASTE_MODE
|
||||||
|
local WASTE = types.WASTE_PRODUCT
|
||||||
|
|
||||||
|
local IO = rsio.IO
|
||||||
|
|
||||||
|
local ALARM_LIMS = const.ALARM_LIMITS
|
||||||
|
|
||||||
|
local DTV_RTU_S_DATA = qtypes.DTV_RTU_S_DATA
|
||||||
|
|
||||||
|
-- 7.14 kJ per blade for 1 mB of fissile fuel<br>
|
||||||
|
-- 2856 FE per blade per 1 mB, 285.6 FE per blade per 0.1 mB (minimum)
|
||||||
|
local POWER_PER_BLADE = util.joules_to_fe_rf(7140)
|
||||||
|
|
||||||
|
local FLOW_STABILITY_DELAY_S = const.FLOW_STABILITY_DELAY_MS / 1000
|
||||||
|
|
||||||
|
local CHARGE_Kp = 0.15
|
||||||
|
local CHARGE_Ki = 0.0
|
||||||
|
local CHARGE_Kd = 0.6
|
||||||
|
|
||||||
|
local RATE_Kp = 2.45
|
||||||
|
local RATE_Ki = 0.4825
|
||||||
|
local RATE_Kd = -1.0
|
||||||
|
|
||||||
|
local self = nil ---@type _facility_self
|
||||||
|
local next_mode = 0
|
||||||
|
local charge_update = 0
|
||||||
|
local rate_update = 0
|
||||||
|
|
||||||
|
---@class facility_update_extension
|
||||||
|
local update = {}
|
||||||
|
|
||||||
|
--#region PRIVATE FUNCTIONS
|
||||||
|
|
||||||
|
-- check if all auto-controlled units completed ramping
|
||||||
|
---@nodiscard
|
||||||
|
local function all_units_ramped()
|
||||||
|
local all_ramped = true
|
||||||
|
|
||||||
|
for i = 1, #self.prio_defs do
|
||||||
|
local units = self.prio_defs[i]
|
||||||
|
for u = 1, #units do
|
||||||
|
all_ramped = all_ramped and units[u].auto_ramp_complete()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return all_ramped
|
||||||
|
end
|
||||||
|
|
||||||
|
-- split a burn rate among the reactors
|
||||||
|
---@param burn_rate number burn rate assignment
|
||||||
|
---@param ramp boolean true to ramp, false to set right away
|
||||||
|
---@param abort_on_fault boolean? true to exit if one device has an effective burn rate different than its limit
|
||||||
|
---@return integer unallocated_br100, boolean? aborted
|
||||||
|
local function allocate_burn_rate(burn_rate, ramp, abort_on_fault)
|
||||||
|
local unallocated = math.floor(burn_rate * 100)
|
||||||
|
|
||||||
|
-- go through all priority groups
|
||||||
|
for i = 1, #self.prio_defs do
|
||||||
|
local units = self.prio_defs[i]
|
||||||
|
|
||||||
|
if #units > 0 then
|
||||||
|
local split = math.floor(unallocated / #units)
|
||||||
|
|
||||||
|
local splits = {}
|
||||||
|
for u = 1, #units do splits[u] = split end
|
||||||
|
splits[#units] = splits[#units] + (unallocated % #units)
|
||||||
|
|
||||||
|
-- go through all reactor units in this group
|
||||||
|
for id = 1, #units do
|
||||||
|
local u = units[id] ---@type reactor_unit
|
||||||
|
|
||||||
|
local ctl = u.get_control_inf()
|
||||||
|
local lim_br100 = u.auto_get_effective_limit()
|
||||||
|
|
||||||
|
if abort_on_fault and (lim_br100 ~= ctl.lim_br100) then
|
||||||
|
-- effective limit differs from set limit, unit is degraded
|
||||||
|
return unallocated, true
|
||||||
|
end
|
||||||
|
|
||||||
|
local last = ctl.br100
|
||||||
|
|
||||||
|
if splits[id] <= lim_br100 then
|
||||||
|
ctl.br100 = splits[id]
|
||||||
|
else
|
||||||
|
ctl.br100 = lim_br100
|
||||||
|
|
||||||
|
if id < #units then
|
||||||
|
local remaining = #units - id
|
||||||
|
split = math.floor(unallocated / remaining)
|
||||||
|
for x = (id + 1), #units do splits[x] = split end
|
||||||
|
splits[#units] = splits[#units] + (unallocated % remaining)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
unallocated = math.max(0, unallocated - ctl.br100)
|
||||||
|
|
||||||
|
if last ~= ctl.br100 then u.auto_commit_br100(ramp) end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return unallocated, false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- set idle state of all assigned reactors
|
||||||
|
---@param idle boolean idle state
|
||||||
|
local function set_idling(idle)
|
||||||
|
for i = 1, #self.prio_defs do
|
||||||
|
for _, u in pairs(self.prio_defs[i]) do u.auto_set_idle(idle) end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
|
--#region PUBLIC FUNCTIONS
|
||||||
|
|
||||||
|
-- automatic control pre-update logic
|
||||||
|
function update.pre_auto()
|
||||||
|
-- unlink RTU sessions if they are closed
|
||||||
|
for _, v in pairs(self.rtu_list) do util.filter_table(v, function (u) return u.is_connected() end) end
|
||||||
|
|
||||||
|
-- check if test routines are allowed right now
|
||||||
|
self.allow_testing = true
|
||||||
|
for i = 1, #self.units do
|
||||||
|
local u = self.units[i] ---@type reactor_unit
|
||||||
|
self.allow_testing = self.allow_testing and u.is_safe_idle()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- current state for process control
|
||||||
|
charge_update = 0
|
||||||
|
rate_update = 0
|
||||||
|
|
||||||
|
-- calculate moving averages for induction matrix
|
||||||
|
if self.induction[1] ~= nil then
|
||||||
|
local matrix = self.induction[1] ---@type unit_session
|
||||||
|
local db = matrix.get_db() ---@type imatrix_session_db
|
||||||
|
|
||||||
|
local build_update = db.build.last_update
|
||||||
|
rate_update = db.state.last_update
|
||||||
|
charge_update = db.tanks.last_update
|
||||||
|
|
||||||
|
local has_data = build_update > 0 and rate_update > 0 and charge_update > 0
|
||||||
|
|
||||||
|
if matrix.is_faulted() then
|
||||||
|
-- a fault occured, cannot reliably update stats
|
||||||
|
has_data = false
|
||||||
|
self.im_stat_init = false
|
||||||
|
self.imtx_faulted_times = { build_update, rate_update, charge_update }
|
||||||
|
elseif not self.im_stat_init then
|
||||||
|
-- prevent operation with partially invalid data
|
||||||
|
-- all fields must have updated since the last fault
|
||||||
|
has_data = self.imtx_faulted_times[1] < build_update and
|
||||||
|
self.imtx_faulted_times[2] < rate_update and
|
||||||
|
self.imtx_faulted_times[3] < charge_update
|
||||||
|
end
|
||||||
|
|
||||||
|
if has_data then
|
||||||
|
local energy = util.joules_to_fe_rf(db.tanks.energy)
|
||||||
|
local input = util.joules_to_fe_rf(db.state.last_input)
|
||||||
|
local output = util.joules_to_fe_rf(db.state.last_output)
|
||||||
|
|
||||||
|
if self.im_stat_init then
|
||||||
|
self.avg_charge.record(energy, charge_update)
|
||||||
|
self.avg_inflow.record(input, rate_update)
|
||||||
|
self.avg_outflow.record(output, rate_update)
|
||||||
|
|
||||||
|
if charge_update ~= self.imtx_last_charge_t then
|
||||||
|
local delta = (energy - self.imtx_last_charge) / (charge_update - self.imtx_last_charge_t)
|
||||||
|
|
||||||
|
self.imtx_last_charge = energy
|
||||||
|
self.imtx_last_charge_t = charge_update
|
||||||
|
|
||||||
|
-- if the capacity changed, toss out existing data
|
||||||
|
if db.build.max_energy ~= self.imtx_last_capacity then
|
||||||
|
self.imtx_last_capacity = db.build.max_energy
|
||||||
|
self.avg_net.reset()
|
||||||
|
else
|
||||||
|
self.avg_net.record(delta, charge_update)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
self.im_stat_init = true
|
||||||
|
|
||||||
|
self.avg_charge.reset(energy)
|
||||||
|
self.avg_inflow.reset(input)
|
||||||
|
self.avg_outflow.reset(output)
|
||||||
|
self.avg_net.reset()
|
||||||
|
|
||||||
|
self.imtx_last_capacity = db.build.max_energy
|
||||||
|
self.imtx_last_charge = energy
|
||||||
|
self.imtx_last_charge_t = charge_update
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- prevent use by control systems
|
||||||
|
rate_update = 0
|
||||||
|
charge_update = 0
|
||||||
|
end
|
||||||
|
else
|
||||||
|
self.im_stat_init = false
|
||||||
|
end
|
||||||
|
|
||||||
|
self.all_sys_ok = true
|
||||||
|
for i = 1, #self.units do
|
||||||
|
self.all_sys_ok = self.all_sys_ok and not self.units[i].get_control_inf().degraded
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- run auto control
|
||||||
|
---@param ExtChargeIdling boolean ExtChargeIdling config field
|
||||||
|
function update.auto_control(ExtChargeIdling)
|
||||||
|
local AUTO_SCRAM = self.types.AUTO_SCRAM
|
||||||
|
local START_STATUS = self.types.START_STATUS
|
||||||
|
|
||||||
|
local avg_charge = self.avg_charge.compute()
|
||||||
|
local avg_inflow = self.avg_inflow.compute()
|
||||||
|
local avg_outflow = self.avg_outflow.compute()
|
||||||
|
|
||||||
|
local now = os.clock()
|
||||||
|
|
||||||
|
local state_changed = self.mode ~= self.last_mode
|
||||||
|
next_mode = self.mode
|
||||||
|
|
||||||
|
-- once auto control is started, sort the priority sublists by limits
|
||||||
|
if state_changed then
|
||||||
|
self.saturated = false
|
||||||
|
|
||||||
|
log.debug(util.c("FAC: state changed from ", PROCESS_NAMES[self.last_mode + 1], " to ", PROCESS_NAMES[self.mode + 1]))
|
||||||
|
|
||||||
|
if (self.last_mode == PROCESS.INACTIVE) or (self.last_mode == PROCESS.GEN_RATE_FAULT_IDLE) then
|
||||||
|
self.start_fail = START_STATUS.OK
|
||||||
|
|
||||||
|
if (self.mode ~= PROCESS.MATRIX_FAULT_IDLE) and (self.mode ~= PROCESS.SYSTEM_ALARM_IDLE) then
|
||||||
|
-- auto clear ASCRAM
|
||||||
|
self.ascram = false
|
||||||
|
self.ascram_reason = AUTO_SCRAM.NONE
|
||||||
|
end
|
||||||
|
|
||||||
|
local blade_count = nil
|
||||||
|
self.max_burn_combined = 0.0
|
||||||
|
|
||||||
|
for i = 1, #self.prio_defs do
|
||||||
|
table.sort(self.prio_defs[i],
|
||||||
|
---@param a reactor_unit
|
||||||
|
---@param b reactor_unit
|
||||||
|
function (a, b) return a.get_control_inf().lim_br100 < b.get_control_inf().lim_br100 end
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, u in pairs(self.prio_defs[i]) do
|
||||||
|
local u_blade_count = u.get_control_inf().blade_count
|
||||||
|
|
||||||
|
if blade_count == nil then
|
||||||
|
blade_count = u_blade_count
|
||||||
|
elseif (u_blade_count ~= blade_count) and (self.mode == PROCESS.GEN_RATE) then
|
||||||
|
log.warning("FAC: cannot start GEN_RATE process with inconsistent unit blade counts")
|
||||||
|
next_mode = PROCESS.INACTIVE
|
||||||
|
self.start_fail = START_STATUS.BLADE_MISMATCH
|
||||||
|
end
|
||||||
|
|
||||||
|
if self.start_fail == START_STATUS.OK then u.auto_engage() end
|
||||||
|
|
||||||
|
self.max_burn_combined = self.max_burn_combined + (u.get_control_inf().lim_br100 / 100.0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
log.debug(util.c("FAC: computed a max combined burn rate of ", self.max_burn_combined, "mB/t"))
|
||||||
|
|
||||||
|
if blade_count == nil then
|
||||||
|
-- no units
|
||||||
|
log.warning("FAC: cannot start process control with 0 units assigned")
|
||||||
|
next_mode = PROCESS.INACTIVE
|
||||||
|
self.start_fail = START_STATUS.NO_UNITS
|
||||||
|
else
|
||||||
|
self.charge_conversion = blade_count * POWER_PER_BLADE
|
||||||
|
end
|
||||||
|
elseif self.mode == PROCESS.INACTIVE then
|
||||||
|
for i = 1, #self.prio_defs do
|
||||||
|
-- disable reactors and disengage auto control
|
||||||
|
for _, u in pairs(self.prio_defs[i]) do
|
||||||
|
u.disable()
|
||||||
|
u.auto_set_idle(false)
|
||||||
|
u.auto_disengage()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
log.info("FAC: disengaging auto control (now inactive)")
|
||||||
|
end
|
||||||
|
|
||||||
|
self.initial_ramp = true
|
||||||
|
self.waiting_on_ramp = false
|
||||||
|
self.waiting_on_stable = false
|
||||||
|
else
|
||||||
|
self.initial_ramp = false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- update unit ready state
|
||||||
|
local assign_count = 0
|
||||||
|
self.units_ready = true
|
||||||
|
for i = 1, #self.prio_defs do
|
||||||
|
for _, u in pairs(self.prio_defs[i]) do
|
||||||
|
assign_count = assign_count + 1
|
||||||
|
self.units_ready = self.units_ready and u.get_control_inf().ready
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- perform mode-specific operations
|
||||||
|
if self.mode == PROCESS.INACTIVE then
|
||||||
|
if not self.units_ready then
|
||||||
|
self.status_text = { "NOT READY", "assigned units not ready" }
|
||||||
|
else
|
||||||
|
-- clear ASCRAM once ready
|
||||||
|
self.ascram = false
|
||||||
|
self.ascram_reason = AUTO_SCRAM.NONE
|
||||||
|
|
||||||
|
if self.start_fail == START_STATUS.NO_UNITS and assign_count == 0 then
|
||||||
|
self.status_text = { "START FAILED", "no units were assigned" }
|
||||||
|
elseif self.start_fail == START_STATUS.BLADE_MISMATCH then
|
||||||
|
self.status_text = { "START FAILED", "turbine blade count mismatch" }
|
||||||
|
else
|
||||||
|
self.status_text = { "IDLE", "control disengaged" }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif self.mode == PROCESS.MAX_BURN then
|
||||||
|
-- run units at their limits
|
||||||
|
if state_changed then
|
||||||
|
self.time_start = now
|
||||||
|
self.saturated = true
|
||||||
|
|
||||||
|
self.status_text = { "MONITORED MODE", "running reactors at limit" }
|
||||||
|
log.info("FAC: MAX_BURN process mode started")
|
||||||
|
end
|
||||||
|
|
||||||
|
allocate_burn_rate(self.max_burn_combined, true)
|
||||||
|
elseif self.mode == PROCESS.BURN_RATE then
|
||||||
|
-- a total aggregate burn rate
|
||||||
|
if state_changed then
|
||||||
|
self.time_start = now
|
||||||
|
self.status_text = { "BURN RATE MODE", "running" }
|
||||||
|
log.info("FAC: BURN_RATE process mode started")
|
||||||
|
end
|
||||||
|
|
||||||
|
local unallocated = allocate_burn_rate(self.burn_target, true)
|
||||||
|
self.saturated = self.burn_target == self.max_burn_combined or unallocated > 0
|
||||||
|
elseif self.mode == PROCESS.CHARGE then
|
||||||
|
-- target a level of charge
|
||||||
|
if state_changed then
|
||||||
|
self.time_start = now
|
||||||
|
self.last_time = now
|
||||||
|
self.last_error = 0
|
||||||
|
self.accumulator = 0
|
||||||
|
|
||||||
|
-- enabling idling on all assigned units
|
||||||
|
set_idling(true)
|
||||||
|
|
||||||
|
self.status_text = { "CHARGE MODE", "running control loop" }
|
||||||
|
log.info("FAC: CHARGE mode starting PID control")
|
||||||
|
elseif self.last_update < charge_update then
|
||||||
|
-- convert to kFE to make constants not microscopic
|
||||||
|
local error = util.round((self.charge_setpoint - avg_charge) / 1000) / 1000
|
||||||
|
|
||||||
|
-- stop accumulator when saturated to avoid windup
|
||||||
|
if not self.saturated then
|
||||||
|
self.accumulator = self.accumulator + (error * (now - self.last_time))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- local runtime = now - self.time_start
|
||||||
|
local integral = self.accumulator
|
||||||
|
local derivative = (error - self.last_error) / (now - self.last_time)
|
||||||
|
|
||||||
|
local P = CHARGE_Kp * error
|
||||||
|
local I = CHARGE_Ki * integral
|
||||||
|
local D = CHARGE_Kd * derivative
|
||||||
|
|
||||||
|
local output = P + I + D
|
||||||
|
|
||||||
|
-- clamp at range -> output clamped (out_c)
|
||||||
|
local out_c = math.max(0, math.min(output, self.max_burn_combined))
|
||||||
|
|
||||||
|
self.saturated = output ~= out_c
|
||||||
|
|
||||||
|
if not ExtChargeIdling then
|
||||||
|
-- stop idling early if the output is zero, we are at or above the setpoint, and are not losing charge
|
||||||
|
set_idling(not ((out_c == 0) and (error <= 0) and (avg_outflow <= 0)))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- log.debug(util.sprintf("CHARGE[%f] { CHRG[%f] ERR[%f] INT[%f] => OUT[%f] OUT_C[%f] <= P[%f] I[%f] D[%f] }",
|
||||||
|
-- runtime, avg_charge, error, integral, output, out_c, P, I, D))
|
||||||
|
|
||||||
|
allocate_burn_rate(out_c, true)
|
||||||
|
|
||||||
|
self.last_time = now
|
||||||
|
self.last_error = error
|
||||||
|
end
|
||||||
|
|
||||||
|
self.last_update = charge_update
|
||||||
|
elseif self.mode == PROCESS.GEN_RATE then
|
||||||
|
-- target a rate of generation
|
||||||
|
if state_changed then
|
||||||
|
-- estimate an initial output
|
||||||
|
local output = self.gen_rate_setpoint / self.charge_conversion
|
||||||
|
|
||||||
|
local unallocated = allocate_burn_rate(output, true)
|
||||||
|
|
||||||
|
self.saturated = output >= self.max_burn_combined or unallocated > 0
|
||||||
|
self.waiting_on_ramp = true
|
||||||
|
|
||||||
|
self.status_text = { "GENERATION MODE", "starting up" }
|
||||||
|
log.info(util.c("FAC: GEN_RATE process mode initial ramp started (initial target is ", output, " mB/t)"))
|
||||||
|
elseif self.waiting_on_ramp then
|
||||||
|
if all_units_ramped() then
|
||||||
|
self.waiting_on_ramp = false
|
||||||
|
self.waiting_on_stable = true
|
||||||
|
|
||||||
|
self.time_start = now
|
||||||
|
|
||||||
|
self.status_text = { "GENERATION MODE", "holding ramped rate" }
|
||||||
|
log.info("FAC: GEN_RATE process mode initial ramp completed, holding for stablization time")
|
||||||
|
end
|
||||||
|
elseif self.waiting_on_stable then
|
||||||
|
if (now - self.time_start) > FLOW_STABILITY_DELAY_S then
|
||||||
|
self.waiting_on_stable = false
|
||||||
|
|
||||||
|
self.time_start = now
|
||||||
|
self.last_time = now
|
||||||
|
self.last_error = 0
|
||||||
|
self.accumulator = 0
|
||||||
|
|
||||||
|
self.status_text = { "GENERATION MODE", "running control loop" }
|
||||||
|
log.info("FAC: GEN_RATE process mode initial hold completed, starting PID control")
|
||||||
|
end
|
||||||
|
elseif self.last_update < rate_update then
|
||||||
|
-- convert to MFE (in rounded kFE) to make constants not microscopic
|
||||||
|
local error = util.round((self.gen_rate_setpoint - avg_inflow) / 1000) / 1000
|
||||||
|
|
||||||
|
-- stop accumulator when saturated to avoid windup
|
||||||
|
if not self.saturated then
|
||||||
|
self.accumulator = self.accumulator + (error * (now - self.last_time))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- local runtime = now - self.time_start
|
||||||
|
local integral = self.accumulator
|
||||||
|
local derivative = (error - self.last_error) / (now - self.last_time)
|
||||||
|
|
||||||
|
local P = RATE_Kp * error
|
||||||
|
local I = RATE_Ki * integral
|
||||||
|
local D = RATE_Kd * derivative
|
||||||
|
|
||||||
|
-- velocity (rate) (derivative of charge level => rate) feed forward
|
||||||
|
local FF = self.gen_rate_setpoint / self.charge_conversion
|
||||||
|
|
||||||
|
local output = P + I + D + FF
|
||||||
|
|
||||||
|
-- clamp at range -> output clamped (sp_c)
|
||||||
|
local out_c = math.max(0, math.min(output, self.max_burn_combined))
|
||||||
|
|
||||||
|
self.saturated = output ~= out_c
|
||||||
|
|
||||||
|
-- log.debug(util.sprintf("GEN_RATE[%f] { RATE[%f] ERR[%f] INT[%f] => OUT[%f] OUT_C[%f] <= P[%f] I[%f] D[%f] }",
|
||||||
|
-- runtime, avg_inflow, error, integral, output, out_c, P, I, D))
|
||||||
|
|
||||||
|
allocate_burn_rate(out_c, false)
|
||||||
|
|
||||||
|
self.last_time = now
|
||||||
|
self.last_error = error
|
||||||
|
end
|
||||||
|
|
||||||
|
self.last_update = rate_update
|
||||||
|
elseif self.mode == PROCESS.MATRIX_FAULT_IDLE then
|
||||||
|
-- exceeded charge, wait until condition clears
|
||||||
|
if self.ascram_reason == AUTO_SCRAM.NONE then
|
||||||
|
next_mode = self.return_mode
|
||||||
|
log.info("FAC: exiting matrix fault idle state due to fault resolution")
|
||||||
|
elseif self.ascram_reason == AUTO_SCRAM.CRIT_ALARM then
|
||||||
|
next_mode = PROCESS.SYSTEM_ALARM_IDLE
|
||||||
|
log.info("FAC: exiting matrix fault idle state due to critical unit alarm")
|
||||||
|
end
|
||||||
|
elseif self.mode == PROCESS.SYSTEM_ALARM_IDLE then
|
||||||
|
-- do nothing, wait for user to confirm (stop and reset)
|
||||||
|
elseif self.mode == PROCESS.GEN_RATE_FAULT_IDLE then
|
||||||
|
-- system faulted (degraded/not ready) while running generation rate mode
|
||||||
|
-- mode will need to be fully restarted once everything is OK to re-ramp to feed-forward
|
||||||
|
if self.units_ready then
|
||||||
|
log.info("FAC: system ready after faulting out of GEN_RATE process mode, switching back...")
|
||||||
|
next_mode = PROCESS.GEN_RATE
|
||||||
|
end
|
||||||
|
elseif self.mode ~= PROCESS.INACTIVE then
|
||||||
|
log.error(util.c("FAC: unsupported process mode ", self.mode, ", switching to inactive"))
|
||||||
|
next_mode = PROCESS.INACTIVE
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- update automatic safety logic
|
||||||
|
function update.auto_safety()
|
||||||
|
local AUTO_SCRAM = self.types.AUTO_SCRAM
|
||||||
|
|
||||||
|
local astatus = self.ascram_status
|
||||||
|
|
||||||
|
if self.induction[1] ~= nil then
|
||||||
|
local db = self.induction[1].get_db() ---@type imatrix_session_db
|
||||||
|
|
||||||
|
-- clear matrix disconnected
|
||||||
|
if astatus.matrix_dc then
|
||||||
|
astatus.matrix_dc = false
|
||||||
|
log.info("FAC: induction matrix reconnected, clearing ASCRAM condition")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- check matrix fill too high
|
||||||
|
local was_fill = astatus.matrix_fill
|
||||||
|
astatus.matrix_fill = (db.tanks.energy_fill >= ALARM_LIMS.CHARGE_HIGH) or (astatus.matrix_fill and db.tanks.energy_fill > ALARM_LIMS.CHARGE_RE_ENABLE)
|
||||||
|
|
||||||
|
if was_fill and not astatus.matrix_fill then
|
||||||
|
log.info(util.c("FAC: charge state of induction matrix entered acceptable range <= ", ALARM_LIMS.CHARGE_RE_ENABLE * 100, "%"))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- check for critical unit alarms
|
||||||
|
astatus.crit_alarm = false
|
||||||
|
for i = 1, #self.units do
|
||||||
|
local u = self.units[i] ---@type reactor_unit
|
||||||
|
|
||||||
|
if u.has_alarm_min_prio(PRIO.CRITICAL) then
|
||||||
|
astatus.crit_alarm = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- check for facility radiation
|
||||||
|
if #self.envd > 0 then
|
||||||
|
local max_rad = 0
|
||||||
|
|
||||||
|
for i = 1, #self.envd do
|
||||||
|
local envd = self.envd[i] ---@type unit_session
|
||||||
|
local e_db = envd.get_db() ---@type envd_session_db
|
||||||
|
if e_db.radiation_raw > max_rad then max_rad = e_db.radiation_raw end
|
||||||
|
end
|
||||||
|
|
||||||
|
astatus.radiation = max_rad >= ALARM_LIMS.FAC_HIGH_RAD
|
||||||
|
else
|
||||||
|
-- don't clear, if it is true then we lost it with high radiation, so just keep alarming
|
||||||
|
-- operator can restart the system or hit the stop/reset button
|
||||||
|
end
|
||||||
|
|
||||||
|
-- system not ready, will need to restart GEN_RATE mode
|
||||||
|
-- clears when we enter the fault waiting state
|
||||||
|
astatus.gen_fault = self.mode == PROCESS.GEN_RATE and not self.units_ready
|
||||||
|
else
|
||||||
|
astatus.matrix_dc = true
|
||||||
|
end
|
||||||
|
|
||||||
|
if (self.mode ~= PROCESS.INACTIVE) and (self.mode ~= PROCESS.SYSTEM_ALARM_IDLE) then
|
||||||
|
local scram = astatus.matrix_dc or astatus.matrix_fill or astatus.crit_alarm or astatus.gen_fault
|
||||||
|
|
||||||
|
if scram and not self.ascram then
|
||||||
|
-- SCRAM all units
|
||||||
|
for i = 1, #self.prio_defs do
|
||||||
|
for _, u in pairs(self.prio_defs[i]) do
|
||||||
|
u.auto_scram()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if astatus.crit_alarm then
|
||||||
|
-- highest priority alarm
|
||||||
|
next_mode = PROCESS.SYSTEM_ALARM_IDLE
|
||||||
|
self.ascram_reason = AUTO_SCRAM.CRIT_ALARM
|
||||||
|
self.status_text = { "AUTOMATIC SCRAM", "critical unit alarm tripped" }
|
||||||
|
|
||||||
|
log.info("FAC: automatic SCRAM due to critical unit alarm")
|
||||||
|
log.warning("FAC: emergency exit of process control due to critical unit alarm")
|
||||||
|
elseif astatus.radiation then
|
||||||
|
next_mode = PROCESS.SYSTEM_ALARM_IDLE
|
||||||
|
self.ascram_reason = AUTO_SCRAM.RADIATION
|
||||||
|
self.status_text = { "AUTOMATIC SCRAM", "facility radiation high" }
|
||||||
|
|
||||||
|
log.info("FAC: automatic SCRAM due to high facility radiation")
|
||||||
|
elseif astatus.matrix_dc then
|
||||||
|
next_mode = PROCESS.MATRIX_FAULT_IDLE
|
||||||
|
self.ascram_reason = AUTO_SCRAM.MATRIX_DC
|
||||||
|
self.status_text = { "AUTOMATIC SCRAM", "induction matrix disconnected" }
|
||||||
|
|
||||||
|
if self.mode ~= PROCESS.MATRIX_FAULT_IDLE then self.return_mode = self.mode end
|
||||||
|
|
||||||
|
log.info("FAC: automatic SCRAM due to induction matrix disconnection")
|
||||||
|
elseif astatus.matrix_fill then
|
||||||
|
next_mode = PROCESS.MATRIX_FAULT_IDLE
|
||||||
|
self.ascram_reason = AUTO_SCRAM.MATRIX_FILL
|
||||||
|
self.status_text = { "AUTOMATIC SCRAM", "induction matrix fill high" }
|
||||||
|
|
||||||
|
if self.mode ~= PROCESS.MATRIX_FAULT_IDLE then self.return_mode = self.mode end
|
||||||
|
|
||||||
|
log.info("FAC: automatic SCRAM due to induction matrix high charge")
|
||||||
|
elseif astatus.gen_fault then
|
||||||
|
-- lowest priority alarm
|
||||||
|
next_mode = PROCESS.GEN_RATE_FAULT_IDLE
|
||||||
|
self.ascram_reason = AUTO_SCRAM.GEN_FAULT
|
||||||
|
self.status_text = { "GENERATION MODE IDLE", "paused: system not ready" }
|
||||||
|
|
||||||
|
log.info("FAC: automatic SCRAM due to unit problem while in GEN_RATE mode, will resume once all units are ready")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
self.ascram = scram
|
||||||
|
|
||||||
|
if not self.ascram then
|
||||||
|
self.ascram_reason = AUTO_SCRAM.NONE
|
||||||
|
|
||||||
|
-- reset PLC RPS trips if we should
|
||||||
|
for i = 1, #self.units do
|
||||||
|
local u = self.units[i] ---@type reactor_unit
|
||||||
|
u.auto_cond_rps_reset()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- update last mode and set next mode
|
||||||
|
function update.post_auto()
|
||||||
|
self.last_mode = self.mode
|
||||||
|
self.mode = next_mode
|
||||||
|
end
|
||||||
|
|
||||||
|
-- update alarm audio control
|
||||||
|
function update.alarm_audio()
|
||||||
|
local allow_test = self.allow_testing and self.test_tone_set
|
||||||
|
|
||||||
|
local alarms = { false, false, false, false, false, false, false, false, false, false, false, false }
|
||||||
|
|
||||||
|
-- reset tone states before re-evaluting
|
||||||
|
for i = 1, #self.tone_states do self.tone_states[i] = false end
|
||||||
|
|
||||||
|
if allow_test then
|
||||||
|
alarms = self.test_alarm_states
|
||||||
|
else
|
||||||
|
-- check all alarms for all units
|
||||||
|
for i = 1, #self.units do
|
||||||
|
local u = self.units[i] ---@type reactor_unit
|
||||||
|
for id, alarm in pairs(u.get_alarms()) do
|
||||||
|
alarms[id] = alarms[id] or (alarm == ALARM_STATE.TRIPPED)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not self.test_tone_reset then
|
||||||
|
-- clear testing alarms if we aren't using them
|
||||||
|
for i = 1, #self.test_alarm_states do self.test_alarm_states[i] = false end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Evaluate Alarms --
|
||||||
|
|
||||||
|
-- containment breach is worst case CRITICAL alarm, this takes priority
|
||||||
|
if alarms[ALARM.ContainmentBreach] then
|
||||||
|
self.tone_states[TONE.T_1800Hz_Int_4Hz] = true
|
||||||
|
else
|
||||||
|
-- critical damage is highest priority CRITICAL level alarm
|
||||||
|
if alarms[ALARM.CriticalDamage] then
|
||||||
|
self.tone_states[TONE.T_660Hz_Int_125ms] = true
|
||||||
|
else
|
||||||
|
-- EMERGENCY level alarms + URGENT over temp
|
||||||
|
if alarms[ALARM.ReactorDamage] or alarms[ALARM.ReactorOverTemp] or alarms[ALARM.ReactorWasteLeak] then
|
||||||
|
self.tone_states[TONE.T_544Hz_440Hz_Alt] = true
|
||||||
|
-- URGENT level turbine trip
|
||||||
|
elseif alarms[ALARM.TurbineTrip] then
|
||||||
|
self.tone_states[TONE.T_745Hz_Int_1Hz] = true
|
||||||
|
-- URGENT level reactor lost
|
||||||
|
elseif alarms[ALARM.ReactorLost] then
|
||||||
|
self.tone_states[TONE.T_340Hz_Int_2Hz] = true
|
||||||
|
-- TIMELY level alarms
|
||||||
|
elseif alarms[ALARM.ReactorHighTemp] or alarms[ALARM.ReactorHighWaste] or alarms[ALARM.RCSTransient] then
|
||||||
|
self.tone_states[TONE.T_800Hz_Int] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- check RPS transient URGENT level alarm
|
||||||
|
if alarms[ALARM.RPSTransient] then
|
||||||
|
self.tone_states[TONE.T_1000Hz_Int] = true
|
||||||
|
-- disable really painful audio combination
|
||||||
|
self.tone_states[TONE.T_340Hz_Int_2Hz] = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- radiation is a big concern, always play this CRITICAL level alarm if active
|
||||||
|
if alarms[ALARM.ContainmentRadiation] then
|
||||||
|
self.tone_states[TONE.T_800Hz_1000Hz_Alt] = true
|
||||||
|
-- we are going to disable the RPS trip alarm audio due to conflict, and if it was enabled
|
||||||
|
-- then we can re-enable the reactor lost alarm audio since it doesn't painfully combine with this one
|
||||||
|
if self.tone_states[TONE.T_1000Hz_Int] and alarms[ALARM.ReactorLost] then self.tone_states[TONE.T_340Hz_Int_2Hz] = true end
|
||||||
|
-- it sounds *really* bad if this is in conjunction with these other tones, so disable them
|
||||||
|
self.tone_states[TONE.T_745Hz_Int_1Hz] = false
|
||||||
|
self.tone_states[TONE.T_800Hz_Int] = false
|
||||||
|
self.tone_states[TONE.T_1000Hz_Int] = false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- add to tone states if testing is active
|
||||||
|
if allow_test then
|
||||||
|
for i = 1, #self.tone_states do
|
||||||
|
self.tone_states[i] = self.tone_states[i] or self.test_tone_states[i]
|
||||||
|
end
|
||||||
|
|
||||||
|
self.test_tone_reset = false
|
||||||
|
else
|
||||||
|
if not self.test_tone_reset then
|
||||||
|
-- clear testing tones if we aren't using them
|
||||||
|
for i = 1, #self.test_tone_states do self.test_tone_states[i] = false end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- flag that tones were reset
|
||||||
|
self.test_tone_set = false
|
||||||
|
self.test_tone_reset = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- update facility redstone
|
||||||
|
---@param ack_all function acknowledge all alarms
|
||||||
|
function update.redstone(ack_all)
|
||||||
|
if #self.redstone > 0 then
|
||||||
|
-- handle facility SCRAM
|
||||||
|
if self.io_ctl.digital_read(IO.F_SCRAM) then
|
||||||
|
for i = 1, #self.units do
|
||||||
|
local u = self.units[i] ---@type reactor_unit
|
||||||
|
u.cond_scram()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- handle facility ack
|
||||||
|
if self.io_ctl.digital_read(IO.F_ACK) then ack_all() end
|
||||||
|
|
||||||
|
-- update facility alarm outputs
|
||||||
|
local has_prio_alarm, has_any_alarm = false, false
|
||||||
|
for i = 1, #self.units do
|
||||||
|
local u = self.units[i] ---@type reactor_unit
|
||||||
|
|
||||||
|
if u.has_alarm_min_prio(PRIO.EMERGENCY) then
|
||||||
|
has_prio_alarm, has_any_alarm = true, true
|
||||||
|
break
|
||||||
|
elseif u.has_alarm_min_prio(PRIO.TIMELY) then
|
||||||
|
has_any_alarm = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
self.io_ctl.digital_write(IO.F_ALARM, has_prio_alarm)
|
||||||
|
self.io_ctl.digital_write(IO.F_ALARM_ANY, has_any_alarm)
|
||||||
|
|
||||||
|
-- update induction matrix related outputs
|
||||||
|
if self.induction[1] ~= nil then
|
||||||
|
local db = self.induction[1].get_db() ---@type imatrix_session_db
|
||||||
|
|
||||||
|
self.io_ctl.digital_write(IO.F_MATRIX_LOW, db.tanks.energy_fill < const.RS_THRESHOLDS.IMATRIX_CHARGE_LOW)
|
||||||
|
self.io_ctl.digital_write(IO.F_MATRIX_HIGH, db.tanks.energy_fill > const.RS_THRESHOLDS.IMATRIX_CHARGE_HIGH)
|
||||||
|
self.io_ctl.analog_write(IO.F_MATRIX_CHG, db.tanks.energy_fill, 0, 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- update unit tasks
|
||||||
|
function update.unit_mgmt()
|
||||||
|
local insufficent_po_rate = false
|
||||||
|
local need_emcool = false
|
||||||
|
|
||||||
|
for i = 1, #self.units do
|
||||||
|
local u = self.units[i] ---@type reactor_unit
|
||||||
|
|
||||||
|
-- update auto waste processing
|
||||||
|
if u.get_control_inf().waste_mode == WASTE_MODE.AUTO then
|
||||||
|
if (u.get_sna_rate() * 10.0) < u.get_burn_rate() then
|
||||||
|
insufficent_po_rate = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- check if unit activated emergency coolant & uses facility tanks
|
||||||
|
if (self.cooling_conf.fac_tank_mode > 0) and u.is_emer_cool_tripped() and (self.cooling_conf.fac_tank_defs[i] == 2) then
|
||||||
|
need_emcool = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- update waste product
|
||||||
|
|
||||||
|
self.current_waste_product = self.waste_product
|
||||||
|
|
||||||
|
if (not self.sps_low_power) and (self.waste_product == WASTE.ANTI_MATTER) and (self.induction[1] ~= nil) then
|
||||||
|
local db = self.induction[1].get_db() ---@type imatrix_session_db
|
||||||
|
|
||||||
|
if db.tanks.energy_fill >= 0.15 then
|
||||||
|
self.disabled_sps = false
|
||||||
|
elseif self.disabled_sps or ((db.tanks.last_update > 0) and (db.tanks.energy_fill < 0.1)) then
|
||||||
|
self.disabled_sps = true
|
||||||
|
self.current_waste_product = WASTE.POLONIUM
|
||||||
|
end
|
||||||
|
else
|
||||||
|
self.disabled_sps = false
|
||||||
|
end
|
||||||
|
|
||||||
|
if self.pu_fallback and insufficent_po_rate then
|
||||||
|
self.current_waste_product = WASTE.PLUTONIUM
|
||||||
|
end
|
||||||
|
|
||||||
|
-- make sure dynamic tanks are allowing outflow if required
|
||||||
|
-- set all, rather than trying to determine which is for which (simpler & safer)
|
||||||
|
-- there should be no need for any to be in fill only mode
|
||||||
|
if need_emcool then
|
||||||
|
for i = 1, #self.tanks do
|
||||||
|
local session = self.tanks[i] ---@type unit_session
|
||||||
|
local tank = session.get_db() ---@type dynamicv_session_db
|
||||||
|
|
||||||
|
if tank.state.container_mode == CONTAINER_MODE.FILL then
|
||||||
|
session.get_cmd_queue().push_data(DTV_RTU_S_DATA.SET_CONT_MODE, CONTAINER_MODE.BOTH)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
|
-- link the self instance and return the update interface
|
||||||
|
---@param fac_self _facility_self
|
||||||
|
return function (fac_self)
|
||||||
|
self = fac_self
|
||||||
|
return update
|
||||||
|
end
|
||||||
47
supervisor/panel/components/chk_entry.lua
Normal file
47
supervisor/panel/components/chk_entry.lua
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
--
|
||||||
|
-- RTU ID Check Failure Entry
|
||||||
|
--
|
||||||
|
|
||||||
|
local types = require("scada-common.types")
|
||||||
|
|
||||||
|
local style = require("supervisor.panel.style")
|
||||||
|
|
||||||
|
local core = require("graphics.core")
|
||||||
|
|
||||||
|
local Div = require("graphics.elements.div")
|
||||||
|
local TextBox = require("graphics.elements.textbox")
|
||||||
|
|
||||||
|
local ALIGN = core.ALIGN
|
||||||
|
|
||||||
|
local cpair = core.cpair
|
||||||
|
|
||||||
|
-- create an ID check list entry
|
||||||
|
---@param parent graphics_element parent
|
||||||
|
---@param msg string message
|
||||||
|
---@param fail_code integer failure code
|
||||||
|
local function init(parent, msg, fail_code)
|
||||||
|
-- root div
|
||||||
|
local root = Div{parent=parent,x=2,y=2,height=4,width=parent.get_width()-2,hidden=true}
|
||||||
|
local entry = Div{parent=root,x=2,y=1,height=3,fg_bg=style.theme.highlight_box_bright}
|
||||||
|
|
||||||
|
local fg_bg = cpair(colors.black,colors.yellow)
|
||||||
|
local tag = "MISSING"
|
||||||
|
|
||||||
|
if fail_code == types.RTU_ID_FAIL.OUT_OF_RANGE then
|
||||||
|
fg_bg = cpair(colors.black,colors.orange)
|
||||||
|
tag = "BAD INDEX"
|
||||||
|
elseif fail_code == types.RTU_ID_FAIL.DUPLICATE then
|
||||||
|
fg_bg = cpair(colors.black,colors.red)
|
||||||
|
tag = "DUPLICATE"
|
||||||
|
end
|
||||||
|
|
||||||
|
TextBox{parent=entry,y=1,text="",width=11,fg_bg=fg_bg}
|
||||||
|
TextBox{parent=entry,text=tag,alignment=ALIGN.CENTER,width=11,fg_bg=fg_bg}
|
||||||
|
TextBox{parent=entry,text="",width=11,fg_bg=fg_bg}
|
||||||
|
|
||||||
|
TextBox{parent=entry,x=13,y=2,text=msg}
|
||||||
|
|
||||||
|
return root
|
||||||
|
end
|
||||||
|
|
||||||
|
return init
|
||||||
@@ -10,6 +10,7 @@ local supervisor = require("supervisor.supervisor")
|
|||||||
local pgi = require("supervisor.panel.pgi")
|
local pgi = require("supervisor.panel.pgi")
|
||||||
local style = require("supervisor.panel.style")
|
local style = require("supervisor.panel.style")
|
||||||
|
|
||||||
|
local chk_entry = require("supervisor.panel.components.chk_entry")
|
||||||
local pdg_entry = require("supervisor.panel.components.pdg_entry")
|
local pdg_entry = require("supervisor.panel.components.pdg_entry")
|
||||||
local rtu_entry = require("supervisor.panel.components.rtu_entry")
|
local rtu_entry = require("supervisor.panel.components.rtu_entry")
|
||||||
|
|
||||||
@@ -73,8 +74,8 @@ local function init(panel)
|
|||||||
--
|
--
|
||||||
|
|
||||||
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}
|
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}
|
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)
|
||||||
@@ -83,7 +84,7 @@ local function init(panel)
|
|||||||
-- page handling
|
-- page handling
|
||||||
--
|
--
|
||||||
|
|
||||||
-- plc page
|
-- plc sessions page
|
||||||
|
|
||||||
local plc_page = Div{parent=page_div,x=1,y=1,hidden=true}
|
local plc_page = Div{parent=page_div,x=1,y=1,hidden=true}
|
||||||
local plc_list = Div{parent=plc_page,x=2,y=2,width=49}
|
local plc_list = Div{parent=plc_page,x=2,y=2,width=49}
|
||||||
@@ -115,13 +116,13 @@ local function init(panel)
|
|||||||
plc_list.line_break()
|
plc_list.line_break()
|
||||||
end
|
end
|
||||||
|
|
||||||
-- rtu page
|
-- rtu sessions page
|
||||||
|
|
||||||
local rtu_page = Div{parent=page_div,x=1,y=1,hidden=true}
|
local rtu_page = Div{parent=page_div,x=1,y=1,hidden=true}
|
||||||
local rtu_list = ListBox{parent=rtu_page,x=1,y=1,height=17,width=51,scroll_height=1000,fg_bg=cpair(colors.black,colors.ivory),nav_fg_bg=cpair(colors.gray,colors.lightGray),nav_active=cpair(colors.black,colors.gray)}
|
local rtu_list = ListBox{parent=rtu_page,x=1,y=1,height=17,width=51,scroll_height=1000,fg_bg=cpair(colors.black,colors.ivory),nav_fg_bg=cpair(colors.gray,colors.lightGray),nav_active=cpair(colors.black,colors.gray)}
|
||||||
local _ = Div{parent=rtu_list,height=1,hidden=true} -- padding
|
local _ = Div{parent=rtu_list,height=1,hidden=true} -- padding
|
||||||
|
|
||||||
-- coordinator page
|
-- coordinator session page
|
||||||
|
|
||||||
local crd_page = Div{parent=page_div,x=1,y=1,hidden=true}
|
local crd_page = Div{parent=page_div,x=1,y=1,hidden=true}
|
||||||
local crd_box = Div{parent=crd_page,x=2,y=2,width=49,height=4,fg_bg=s_hi_bright}
|
local crd_box = Div{parent=crd_page,x=2,y=2,width=49,height=4,fg_bg=s_hi_bright}
|
||||||
@@ -143,15 +144,37 @@ local function init(panel)
|
|||||||
crd_rtt.register(databus.ps, "crd_rtt", crd_rtt.update)
|
crd_rtt.register(databus.ps, "crd_rtt", crd_rtt.update)
|
||||||
crd_rtt.register(databus.ps, "crd_rtt_color", crd_rtt.recolor)
|
crd_rtt.register(databus.ps, "crd_rtt_color", crd_rtt.recolor)
|
||||||
|
|
||||||
-- pocket diagnostics page
|
-- pocket sessions page
|
||||||
|
|
||||||
local pkt_page = Div{parent=page_div,x=1,y=1,hidden=true}
|
local pkt_page = Div{parent=page_div,x=1,y=1,hidden=true}
|
||||||
local pdg_list = ListBox{parent=pkt_page,x=1,y=1,height=17,width=51,scroll_height=1000,fg_bg=style.fp.text_fg,nav_fg_bg=cpair(colors.gray,colors.lightGray),nav_active=cpair(colors.black,colors.gray)}
|
local pdg_list = ListBox{parent=pkt_page,x=1,y=1,height=17,width=51,scroll_height=1000,fg_bg=style.fp.text_fg,nav_fg_bg=cpair(colors.gray,colors.lightGray),nav_active=cpair(colors.black,colors.gray)}
|
||||||
local _ = Div{parent=pdg_list,height=1,hidden=true} -- padding
|
local _ = Div{parent=pdg_list,height=1,hidden=true} -- padding
|
||||||
|
|
||||||
|
-- RTU device ID check/diagnostics page
|
||||||
|
|
||||||
|
local chk_page = Div{parent=page_div,x=1,y=1,hidden=true}
|
||||||
|
local chk_list = ListBox{parent=chk_page,x=1,y=1,height=17,width=51,scroll_height=1000,fg_bg=style.fp.text_fg,nav_fg_bg=cpair(colors.gray,colors.lightGray),nav_active=cpair(colors.black,colors.gray)}
|
||||||
|
local _ = Div{parent=chk_list,height=1,hidden=true} -- padding
|
||||||
|
|
||||||
|
-- info page
|
||||||
|
|
||||||
|
local info_page = Div{parent=page_div,x=1,y=1,hidden=true}
|
||||||
|
local info = Div{parent=info_page,height=6,x=2,y=2}
|
||||||
|
|
||||||
|
TextBox{parent=info,text="SVR \x1a Supervisor Status"}
|
||||||
|
TextBox{parent=info,text="PLC \x1a Reactor PLC Connections"}
|
||||||
|
TextBox{parent=info,text="RTU \x1a RTU Gateway Connections"}
|
||||||
|
TextBox{parent=info,text="CRD \x1a Coordinator Connection"}
|
||||||
|
TextBox{parent=info,text="PKT \x1a Pocket Connections"}
|
||||||
|
TextBox{parent=info,text="DEV \x1a RTU Device/Configuration Alerts"}
|
||||||
|
|
||||||
|
local notes = Div{parent=info_page,width=49,height=8,x=2,y=9,fg_bg=style.fp.disabled_fg}
|
||||||
|
|
||||||
|
TextBox{parent=notes,text="The DEV tab will show missing devices and devices that connected with incorrect information. Missing entries will indicate how the configuration should be, duplicate entries will indicate what is a duplicate, and out-of-range entries will indicate the invalid entry. An out-of-range example is a #2 turbine when you should only have 1 turbine for that unit."}
|
||||||
|
|
||||||
-- assemble page panes
|
-- assemble page panes
|
||||||
|
|
||||||
local panes = { main_page, plc_page, rtu_page, crd_page, pkt_page }
|
local panes = { main_page, plc_page, rtu_page, crd_page, pkt_page, chk_page, info_page }
|
||||||
|
|
||||||
local page_pane = MultiPane{parent=page_div,x=1,y=1,panes=panes}
|
local page_pane = MultiPane{parent=page_div,x=1,y=1,panes=panes}
|
||||||
|
|
||||||
@@ -161,12 +184,14 @@ local function init(panel)
|
|||||||
{ name = "RTU", color = style.fp.text },
|
{ name = "RTU", color = style.fp.text },
|
||||||
{ name = "CRD", color = style.fp.text },
|
{ name = "CRD", color = style.fp.text },
|
||||||
{ name = "PKT", color = style.fp.text },
|
{ name = "PKT", color = style.fp.text },
|
||||||
|
{ name = "DEV", color = style.fp.text },
|
||||||
|
{ name = "INF", color = style.fp.text }
|
||||||
}
|
}
|
||||||
|
|
||||||
TabBar{parent=panel,y=2,tabs=tabs,min_width=9,callback=page_pane.set_value,fg_bg=style.theme.highlight_box_bright}
|
TabBar{parent=panel,y=2,tabs=tabs,min_width=7,callback=page_pane.set_value,fg_bg=style.theme.highlight_box_bright}
|
||||||
|
|
||||||
-- link RTU/PDG list management to PGI
|
-- link RTU/PDG/CHK list management to PGI
|
||||||
pgi.link_elements(rtu_list, rtu_entry, pdg_list, pdg_entry)
|
pgi.link_elements(rtu_list, rtu_entry, pdg_list, pdg_entry, chk_list, chk_entry)
|
||||||
end
|
end
|
||||||
|
|
||||||
return init
|
return init
|
||||||
|
|||||||
@@ -10,10 +10,12 @@ local pgi = {}
|
|||||||
local data = {
|
local data = {
|
||||||
rtu_list = nil, ---@type nil|graphics_element
|
rtu_list = nil, ---@type nil|graphics_element
|
||||||
pdg_list = nil, ---@type nil|graphics_element
|
pdg_list = nil, ---@type nil|graphics_element
|
||||||
|
chk_list = nil, ---@type nil|graphics_element
|
||||||
rtu_entry = nil, ---@type function
|
rtu_entry = nil, ---@type function
|
||||||
pdg_entry = nil, ---@type function
|
pdg_entry = nil, ---@type function
|
||||||
-- session entries
|
chk_entry = nil, ---@type function
|
||||||
s_entries = { rtu = {}, pdg = {} }
|
-- list entries
|
||||||
|
entries = { rtu = {}, pdg = {}, chk = {}, missing = {} }
|
||||||
}
|
}
|
||||||
|
|
||||||
-- link list boxes
|
-- link list boxes
|
||||||
@@ -21,19 +23,25 @@ local data = {
|
|||||||
---@param rtu_entry function RTU entry constructor
|
---@param rtu_entry function RTU entry constructor
|
||||||
---@param pdg_list graphics_element pocket diagnostics list element
|
---@param pdg_list graphics_element pocket diagnostics list element
|
||||||
---@param pdg_entry function pocket diagnostics entry constructor
|
---@param pdg_entry function pocket diagnostics entry constructor
|
||||||
function pgi.link_elements(rtu_list, rtu_entry, pdg_list, pdg_entry)
|
---@param chk_list graphics_element CHK list element
|
||||||
|
---@param chk_entry function CHK entry constructor
|
||||||
|
function pgi.link_elements(rtu_list, rtu_entry, pdg_list, pdg_entry, chk_list, chk_entry)
|
||||||
data.rtu_list = rtu_list
|
data.rtu_list = rtu_list
|
||||||
data.pdg_list = pdg_list
|
data.pdg_list = pdg_list
|
||||||
|
data.chk_list = chk_list
|
||||||
data.rtu_entry = rtu_entry
|
data.rtu_entry = rtu_entry
|
||||||
data.pdg_entry = pdg_entry
|
data.pdg_entry = pdg_entry
|
||||||
|
data.chk_entry = chk_entry
|
||||||
end
|
end
|
||||||
|
|
||||||
-- unlink all fields, disabling the PGI
|
-- unlink all fields, disabling the PGI
|
||||||
function pgi.unlink()
|
function pgi.unlink()
|
||||||
data.rtu_list = nil
|
data.rtu_list = nil
|
||||||
data.pdg_list = nil
|
data.pdg_list = nil
|
||||||
|
data.chk_list = nil
|
||||||
data.rtu_entry = nil
|
data.rtu_entry = nil
|
||||||
data.pdg_entry = nil
|
data.pdg_entry = nil
|
||||||
|
data.chk_entry = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
-- add an RTU entry to the RTU list
|
-- add an RTU entry to the RTU list
|
||||||
@@ -43,7 +51,8 @@ function pgi.create_rtu_entry(session_id)
|
|||||||
local success, result = pcall(data.rtu_entry, data.rtu_list, session_id)
|
local success, result = pcall(data.rtu_entry, data.rtu_list, session_id)
|
||||||
|
|
||||||
if success then
|
if success then
|
||||||
data.s_entries.rtu[session_id] = result
|
data.entries.rtu[session_id] = result
|
||||||
|
log.debug(util.c("PGI: created RTU entry (", session_id, ")"))
|
||||||
else
|
else
|
||||||
log.error(util.c("PGI: failed to create RTU entry (", result, ")"), true)
|
log.error(util.c("PGI: failed to create RTU entry (", result, ")"), true)
|
||||||
end
|
end
|
||||||
@@ -53,15 +62,17 @@ end
|
|||||||
-- delete an RTU entry from the RTU list
|
-- delete an RTU entry from the RTU list
|
||||||
---@param session_id integer RTU session
|
---@param session_id integer RTU session
|
||||||
function pgi.delete_rtu_entry(session_id)
|
function pgi.delete_rtu_entry(session_id)
|
||||||
if data.s_entries.rtu[session_id] ~= nil then
|
if data.entries.rtu[session_id] ~= nil then
|
||||||
local success, result = pcall(data.s_entries.rtu[session_id].delete)
|
local success, result = pcall(data.entries.rtu[session_id].delete)
|
||||||
data.s_entries.rtu[session_id] = nil
|
data.entries.rtu[session_id] = nil
|
||||||
|
|
||||||
if not success then
|
if success then
|
||||||
|
log.debug(util.c("PGI: deleted RTU entry (", session_id, ")"))
|
||||||
|
else
|
||||||
log.error(util.c("PGI: failed to delete RTU entry (", result, ")"), true)
|
log.error(util.c("PGI: failed to delete RTU entry (", result, ")"), true)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
log.debug(util.c("PGI: tried to delete unknown RTU entry ", session_id))
|
log.warning(util.c("PGI: tried to delete unknown RTU entry ", session_id))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -72,7 +83,8 @@ function pgi.create_pdg_entry(session_id)
|
|||||||
local success, result = pcall(data.pdg_entry, data.pdg_list, session_id)
|
local success, result = pcall(data.pdg_entry, data.pdg_list, session_id)
|
||||||
|
|
||||||
if success then
|
if success then
|
||||||
data.s_entries.pdg[session_id] = result
|
data.entries.pdg[session_id] = result
|
||||||
|
log.debug(util.c("PGI: created PDG entry (", session_id, ")"))
|
||||||
else
|
else
|
||||||
log.error(util.c("PGI: failed to create PDG entry (", result, ")"), true)
|
log.error(util.c("PGI: failed to create PDG entry (", result, ")"), true)
|
||||||
end
|
end
|
||||||
@@ -82,15 +94,92 @@ end
|
|||||||
-- delete a PDG entry from the PDG list
|
-- delete a PDG entry from the PDG list
|
||||||
---@param session_id integer pocket diagnostics session
|
---@param session_id integer pocket diagnostics session
|
||||||
function pgi.delete_pdg_entry(session_id)
|
function pgi.delete_pdg_entry(session_id)
|
||||||
if data.s_entries.pdg[session_id] ~= nil then
|
if data.entries.pdg[session_id] ~= nil then
|
||||||
local success, result = pcall(data.s_entries.pdg[session_id].delete)
|
local success, result = pcall(data.entries.pdg[session_id].delete)
|
||||||
data.s_entries.pdg[session_id] = nil
|
data.entries.pdg[session_id] = nil
|
||||||
|
|
||||||
if not success then
|
if success then
|
||||||
|
log.debug(util.c("PGI: deleted PDG entry (", session_id, ")"))
|
||||||
|
else
|
||||||
log.error(util.c("PGI: failed to delete PDG entry (", result, ")"), true)
|
log.error(util.c("PGI: failed to delete PDG entry (", result, ")"), true)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
log.debug(util.c("PGI: tried to delete unknown PDG entry ", session_id))
|
log.warning(util.c("PGI: tried to delete unknown PDG entry ", session_id))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- add a device ID check failure entry to the CHK list
|
||||||
|
---@note this assumes only one type of failure can occur per each RTU gateway session's RTU, which is the case
|
||||||
|
---@param unit unit_session RTU session
|
||||||
|
---@param fail_code integer failure code
|
||||||
|
---@param msg string description to show the user
|
||||||
|
function pgi.create_chk_entry(unit, fail_code, msg)
|
||||||
|
local gw_session = unit.get_session_id()
|
||||||
|
|
||||||
|
if data.chk_list ~= nil and data.chk_entry ~= nil then
|
||||||
|
if not data.entries.chk[gw_session] then data.entries.chk[gw_session] = {} end
|
||||||
|
|
||||||
|
local success, result = pcall(data.chk_entry, data.chk_list, msg, fail_code)
|
||||||
|
|
||||||
|
if success then
|
||||||
|
data.entries.chk[gw_session][unit.get_unit_id()] = result
|
||||||
|
log.debug(util.c("PGI: created CHK entry (", gw_session, ":", unit.get_unit_id(), ")"))
|
||||||
|
else
|
||||||
|
log.error(util.c("PGI: failed to create CHK entry (", result, ")"), true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- delete a device ID check failure entry from the CHK list
|
||||||
|
---@note this assumes only one type of failure can occur per each RTU gateway session's RTU, which is the case
|
||||||
|
---@param unit unit_session RTU session
|
||||||
|
function pgi.delete_chk_entry(unit)
|
||||||
|
local gw_session = unit.get_session_id()
|
||||||
|
local ent_chk = data.entries.chk
|
||||||
|
|
||||||
|
if ent_chk[gw_session] ~= nil and ent_chk[gw_session][unit.get_unit_id()] ~= nil then
|
||||||
|
local success, result = pcall(ent_chk[gw_session][unit.get_unit_id()].delete)
|
||||||
|
ent_chk[gw_session][unit.get_unit_id()] = nil
|
||||||
|
|
||||||
|
if success then
|
||||||
|
log.debug(util.c("PGI: deleted CHK entry ", gw_session, ":", unit.get_unit_id()))
|
||||||
|
else
|
||||||
|
log.error(util.c("PGI: failed to delete CHK entry (", result, ")"), true)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
log.warning(util.c("PGI: tried to delete unknown CHK entry with session of ", gw_session, " and unit ID of ", unit.get_unit_id()))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- add a device ID missing entry to the CHK list
|
||||||
|
---@param message string missing device message
|
||||||
|
function pgi.create_missing_entry(message)
|
||||||
|
if data.chk_list ~= nil and data.chk_entry ~= nil then
|
||||||
|
local success, result = pcall(data.chk_entry, data.chk_list, message, 4)
|
||||||
|
|
||||||
|
if success then
|
||||||
|
data.entries.missing[message] = result
|
||||||
|
log.debug(util.c("PGI: created missing CHK entry (", message, ")"))
|
||||||
|
else
|
||||||
|
log.error(util.c("PGI: failed to create missing CHK entry (", result, ")"), true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- delete a device ID missing entry from the CHK list
|
||||||
|
---@param message string missing device message
|
||||||
|
function pgi.delete_missing_entry(message)
|
||||||
|
if data.entries.missing[message] ~= nil then
|
||||||
|
local success, result = pcall(data.entries.missing[message].delete)
|
||||||
|
data.entries.missing[message] = nil
|
||||||
|
|
||||||
|
if success then
|
||||||
|
log.debug(util.c("PGI: deleted missing CHK entry \"", message, "\""))
|
||||||
|
else
|
||||||
|
log.error(util.c("PGI: failed to delete missing CHK entry (", result, ")"), true)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
log.warning(util.c("PGI: tried to delete unknown missing CHK entry \"", message, "\""))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ function coordinator.new_session(id, s_addr, i_seq_num, in_queue, out_queue, tim
|
|||||||
-- print a log message to the terminal as long as the UI isn't running
|
-- print a log message to the terminal as long as the UI isn't running
|
||||||
local function println(message) if not fp_ok then util.println_ts(message) end end
|
local function println(message) if not fp_ok then util.println_ts(message) end end
|
||||||
|
|
||||||
local log_header = "crdn_session(" .. id .. "): "
|
local log_tag = "crdn_session(" .. id .. "): "
|
||||||
|
|
||||||
local self = {
|
local self = {
|
||||||
units = facility.get_units(),
|
units = facility.get_units(),
|
||||||
@@ -184,7 +184,7 @@ function coordinator.new_session(id, s_addr, i_seq_num, in_queue, out_queue, tim
|
|||||||
local function _handle_packet(pkt)
|
local function _handle_packet(pkt)
|
||||||
-- check sequence number
|
-- check sequence number
|
||||||
if self.r_seq_num ~= pkt.scada_frame.seq_num() then
|
if self.r_seq_num ~= 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())
|
log.warning(log_tag .. "sequence out-of-order: next = " .. self.r_seq_num .. ", new = " .. pkt.scada_frame.seq_num())
|
||||||
return
|
return
|
||||||
else
|
else
|
||||||
self.r_seq_num = pkt.scada_frame.seq_num() + 1
|
self.r_seq_num = pkt.scada_frame.seq_num() + 1
|
||||||
@@ -205,7 +205,7 @@ function coordinator.new_session(id, s_addr, i_seq_num, in_queue, out_queue, tim
|
|||||||
self.last_rtt = srv_now - srv_start
|
self.last_rtt = srv_now - srv_start
|
||||||
|
|
||||||
if self.last_rtt > 750 then
|
if self.last_rtt > 750 then
|
||||||
log.warning(log_header .. "COORD KEEP_ALIVE round trip time > 750ms (" .. self.last_rtt .. "ms)")
|
log.warning(log_tag .. "COORD KEEP_ALIVE round trip time > 750ms (" .. self.last_rtt .. "ms)")
|
||||||
end
|
end
|
||||||
|
|
||||||
-- log.debug(log_header .. "COORD RTT = " .. self.last_rtt .. "ms")
|
-- log.debug(log_header .. "COORD RTT = " .. self.last_rtt .. "ms")
|
||||||
@@ -213,13 +213,17 @@ function coordinator.new_session(id, s_addr, i_seq_num, in_queue, out_queue, tim
|
|||||||
|
|
||||||
databus.tx_crd_rtt(self.last_rtt)
|
databus.tx_crd_rtt(self.last_rtt)
|
||||||
else
|
else
|
||||||
log.debug(log_header .. "SCADA keep alive packet length mismatch")
|
log.debug(log_tag .. "SCADA keep alive packet length mismatch")
|
||||||
end
|
end
|
||||||
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_tag .. "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_tag .. "handler received unsupported SCADA_MGMT packet type " .. pkt.type)
|
||||||
end
|
end
|
||||||
elseif pkt.scada_frame.protocol() == PROTOCOL.SCADA_CRDN then
|
elseif pkt.scada_frame.protocol() == PROTOCOL.SCADA_CRDN then
|
||||||
---@cast pkt crdn_frame
|
---@cast pkt crdn_frame
|
||||||
@@ -252,7 +256,7 @@ function coordinator.new_session(id, s_addr, i_seq_num, in_queue, out_queue, tim
|
|||||||
|
|
||||||
_send(CRDN_TYPE.FAC_CMD, { cmd, table.unpack(facility.auto_start(config)) })
|
_send(CRDN_TYPE.FAC_CMD, { cmd, table.unpack(facility.auto_start(config)) })
|
||||||
else
|
else
|
||||||
log.debug(log_header .. "CRDN auto start (with configuration) packet length mismatch")
|
log.debug(log_tag .. "CRDN auto start (with configuration) packet length mismatch")
|
||||||
end
|
end
|
||||||
elseif cmd == FAC_COMMAND.ACK_ALL_ALARMS then
|
elseif cmd == FAC_COMMAND.ACK_ALL_ALARMS then
|
||||||
facility.ack_all()
|
facility.ack_all()
|
||||||
@@ -261,25 +265,25 @@ function coordinator.new_session(id, s_addr, i_seq_num, in_queue, out_queue, tim
|
|||||||
if pkt.length == 2 then
|
if pkt.length == 2 then
|
||||||
_send(CRDN_TYPE.FAC_CMD, { cmd, facility.set_waste_product(pkt.data[2]) })
|
_send(CRDN_TYPE.FAC_CMD, { cmd, facility.set_waste_product(pkt.data[2]) })
|
||||||
else
|
else
|
||||||
log.debug(log_header .. "CRDN set waste mode packet length mismatch")
|
log.debug(log_tag .. "CRDN set waste mode packet length mismatch")
|
||||||
end
|
end
|
||||||
elseif cmd == FAC_COMMAND.SET_PU_FB then
|
elseif cmd == FAC_COMMAND.SET_PU_FB then
|
||||||
if pkt.length == 2 then
|
if pkt.length == 2 then
|
||||||
_send(CRDN_TYPE.FAC_CMD, { cmd, facility.set_pu_fallback(pkt.data[2]) })
|
_send(CRDN_TYPE.FAC_CMD, { cmd, facility.set_pu_fallback(pkt.data[2]) })
|
||||||
else
|
else
|
||||||
log.debug(log_header .. "CRDN set pu fallback packet length mismatch")
|
log.debug(log_tag .. "CRDN set pu fallback packet length mismatch")
|
||||||
end
|
end
|
||||||
elseif cmd == FAC_COMMAND.SET_SPS_LP then
|
elseif cmd == FAC_COMMAND.SET_SPS_LP then
|
||||||
if pkt.length == 2 then
|
if pkt.length == 2 then
|
||||||
_send(CRDN_TYPE.FAC_CMD, { cmd, facility.set_sps_low_power(pkt.data[2]) })
|
_send(CRDN_TYPE.FAC_CMD, { cmd, facility.set_sps_low_power(pkt.data[2]) })
|
||||||
else
|
else
|
||||||
log.debug(log_header .. "CRDN set sps low power packet length mismatch")
|
log.debug(log_tag .. "CRDN set sps low power packet length mismatch")
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
log.debug(log_header .. "CRDN facility command unknown")
|
log.debug(log_tag .. "CRDN facility command unknown")
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
log.debug(log_header .. "CRDN facility command packet length mismatch")
|
log.debug(log_tag .. "CRDN facility command packet length mismatch")
|
||||||
end
|
end
|
||||||
elseif pkt.type == CRDN_TYPE.UNIT_BUILDS then
|
elseif pkt.type == CRDN_TYPE.UNIT_BUILDS then
|
||||||
-- acknowledgement to coordinator receiving builds
|
-- acknowledgement to coordinator receiving builds
|
||||||
@@ -307,13 +311,13 @@ function coordinator.new_session(id, s_addr, i_seq_num, in_queue, out_queue, tim
|
|||||||
if pkt.length == 3 then
|
if pkt.length == 3 then
|
||||||
out_queue.push_data(SV_Q_DATA.SET_BURN, data)
|
out_queue.push_data(SV_Q_DATA.SET_BURN, data)
|
||||||
else
|
else
|
||||||
log.debug(log_header .. "CRDN unit command burn rate missing option")
|
log.debug(log_tag .. "CRDN unit command burn rate missing option")
|
||||||
end
|
end
|
||||||
elseif cmd == UNIT_COMMAND.SET_WASTE then
|
elseif cmd == UNIT_COMMAND.SET_WASTE then
|
||||||
if (pkt.length == 3) and (type(pkt.data[3]) == "number") and (pkt.data[3] > 0) and (pkt.data[3] <= 4) then
|
if (pkt.length == 3) and (type(pkt.data[3]) == "number") and (pkt.data[3] > 0) and (pkt.data[3] <= 4) then
|
||||||
unit.set_waste_mode(pkt.data[3])
|
unit.set_waste_mode(pkt.data[3])
|
||||||
else
|
else
|
||||||
log.debug(log_header .. "CRDN unit command set waste missing/invalid option")
|
log.debug(log_tag .. "CRDN unit command set waste missing/invalid option")
|
||||||
end
|
end
|
||||||
elseif cmd == UNIT_COMMAND.ACK_ALL_ALARMS then
|
elseif cmd == UNIT_COMMAND.ACK_ALL_ALARMS then
|
||||||
unit.ack_all()
|
unit.ack_all()
|
||||||
@@ -322,32 +326,32 @@ function coordinator.new_session(id, s_addr, i_seq_num, in_queue, out_queue, tim
|
|||||||
if pkt.length == 3 then
|
if pkt.length == 3 then
|
||||||
unit.ack_alarm(pkt.data[3])
|
unit.ack_alarm(pkt.data[3])
|
||||||
else
|
else
|
||||||
log.debug(log_header .. "CRDN unit command ack alarm missing alarm id")
|
log.debug(log_tag .. "CRDN unit command ack alarm missing alarm id")
|
||||||
end
|
end
|
||||||
elseif cmd == UNIT_COMMAND.RESET_ALARM then
|
elseif cmd == UNIT_COMMAND.RESET_ALARM then
|
||||||
if pkt.length == 3 then
|
if pkt.length == 3 then
|
||||||
unit.reset_alarm(pkt.data[3])
|
unit.reset_alarm(pkt.data[3])
|
||||||
else
|
else
|
||||||
log.debug(log_header .. "CRDN unit command reset alarm missing alarm id")
|
log.debug(log_tag .. "CRDN unit command reset alarm missing alarm id")
|
||||||
end
|
end
|
||||||
elseif cmd == UNIT_COMMAND.SET_GROUP then
|
elseif cmd == UNIT_COMMAND.SET_GROUP then
|
||||||
if (pkt.length == 3) and (type(pkt.data[3]) == "number") and (pkt.data[3] >= 0) and (pkt.data[3] <= 4) then
|
if (pkt.length == 3) and (type(pkt.data[3]) == "number") and (pkt.data[3] >= 0) and (pkt.data[3] <= 4) then
|
||||||
facility.set_group(unit.get_id(), pkt.data[3])
|
facility.set_group(unit.get_id(), pkt.data[3])
|
||||||
_send(CRDN_TYPE.UNIT_CMD, { cmd, uid, pkt.data[3] })
|
_send(CRDN_TYPE.UNIT_CMD, { cmd, uid, pkt.data[3] })
|
||||||
else
|
else
|
||||||
log.debug(log_header .. "CRDN unit command set group missing group id")
|
log.debug(log_tag .. "CRDN unit command set group missing group id")
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
log.debug(log_header .. "CRDN unit command unknown")
|
log.debug(log_tag .. "CRDN unit command unknown")
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
log.debug(log_header .. "CRDN unit command invalid")
|
log.debug(log_tag .. "CRDN unit command invalid")
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
log.debug(log_header .. "CRDN unit command packet length mismatch")
|
log.debug(log_tag .. "CRDN unit command packet length mismatch")
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
log.debug(log_header .. "handler received unexpected SCADA_CRDN packet type " .. pkt.type)
|
log.debug(log_tag .. "handler received unexpected SCADA_CRDN packet type " .. pkt.type)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -370,7 +374,7 @@ function coordinator.new_session(id, s_addr, i_seq_num, in_queue, out_queue, tim
|
|||||||
_close()
|
_close()
|
||||||
_send_mgmt(MGMT_TYPE.CLOSE, {})
|
_send_mgmt(MGMT_TYPE.CLOSE, {})
|
||||||
println("connection to coordinator " .. id .. " closed by server")
|
println("connection to coordinator " .. id .. " closed by server")
|
||||||
log.info(log_header .. "session closed by server")
|
log.info(log_tag .. "session closed by server")
|
||||||
end
|
end
|
||||||
|
|
||||||
-- iterate the session
|
-- iterate the session
|
||||||
@@ -437,14 +441,14 @@ function coordinator.new_session(id, s_addr, i_seq_num, in_queue, out_queue, tim
|
|||||||
_send(CRDN_TYPE.FAC_BUILDS, { facility.get_build(cmd.val.type) })
|
_send(CRDN_TYPE.FAC_BUILDS, { facility.get_build(cmd.val.type) })
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
log.error(log_header .. "unsupported data command received in in_queue (this is a bug)", true)
|
log.error(log_tag .. "unsupported data command received in in_queue (this is a bug)", true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- max 100ms spent processing queue
|
-- max 100ms spent processing queue
|
||||||
if util.time() - handle_start > 100 then
|
if util.time() - handle_start > 100 then
|
||||||
log.warning(log_header .. "exceeded 100ms queue process limit")
|
log.warning(log_tag .. "exceeded 100ms queue process limit")
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -452,7 +456,7 @@ function coordinator.new_session(id, s_addr, i_seq_num, in_queue, out_queue, tim
|
|||||||
-- exit if connection was closed
|
-- exit if connection was closed
|
||||||
if not self.connected then
|
if not self.connected then
|
||||||
println("connection to coordinator closed by remote host")
|
println("connection to coordinator closed by remote host")
|
||||||
log.info(log_header .. "session closed by remote host")
|
log.info(log_tag .. "session closed by remote host")
|
||||||
return self.connected
|
return self.connected
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ function plc.new_session(id, s_addr, i_seq_num, reactor_id, in_queue, out_queue,
|
|||||||
-- print a log message to the terminal as long as the UI isn't running
|
-- print a log message to the terminal as long as the UI isn't running
|
||||||
local function println(message) if not fp_ok then util.println_ts(message) end end
|
local function println(message) if not fp_ok then util.println_ts(message) end end
|
||||||
|
|
||||||
local log_header = "plc_session(" .. id .. "): "
|
local log_tag = "plc_session(" .. id .. "): "
|
||||||
|
|
||||||
local self = {
|
local self = {
|
||||||
commanded_state = false,
|
commanded_state = false,
|
||||||
@@ -184,7 +184,7 @@ function plc.new_session(id, s_addr, i_seq_num, reactor_id, in_queue, out_queue,
|
|||||||
self.sDB.max_op_temp_H2O = max_burn * 2 * (JOULES_PER_MB * heat_cap ^ -1) + BASE_BOIL_TEMP
|
self.sDB.max_op_temp_H2O = max_burn * 2 * (JOULES_PER_MB * heat_cap ^ -1) + BASE_BOIL_TEMP
|
||||||
self.sDB.max_op_temp_Na = max_burn * (JOULES_PER_MB * heat_cap ^ -1) + BASE_BOIL_TEMP
|
self.sDB.max_op_temp_Na = max_burn * (JOULES_PER_MB * heat_cap ^ -1) + BASE_BOIL_TEMP
|
||||||
|
|
||||||
log.info(util.sprintf(log_header .. "computed maximum operational temperatures %.3fK (H2O) and %.3fK (Na)",
|
log.info(util.sprintf(log_tag .. "computed maximum operational temperatures %.3fK (H2O) and %.3fK (Na)",
|
||||||
self.sDB.max_op_temp_H2O, self.sDB.max_op_temp_Na))
|
self.sDB.max_op_temp_H2O, self.sDB.max_op_temp_Na))
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -289,12 +289,12 @@ function plc.new_session(id, s_addr, i_seq_num, reactor_id, in_queue, out_queue,
|
|||||||
_copy_status(pkt.data[7])
|
_copy_status(pkt.data[7])
|
||||||
self.received_status_cache = true
|
self.received_status_cache = true
|
||||||
else
|
else
|
||||||
log.error(log_header .. "RPLC status packet reactor data length mismatch")
|
log.error(log_tag .. "RPLC status packet reactor data length mismatch")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
log.debug(log_header .. "RPLC status packet invalid")
|
log.debug(log_tag .. "RPLC status packet invalid")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -341,7 +341,7 @@ function plc.new_session(id, s_addr, i_seq_num, reactor_id, in_queue, out_queue,
|
|||||||
if pkt.length == 1 then
|
if pkt.length == 1 then
|
||||||
return pkt.data[1]
|
return pkt.data[1]
|
||||||
else
|
else
|
||||||
log.debug(log_header .. "RPLC ACK length mismatch")
|
log.debug(log_tag .. "RPLC ACK length mismatch")
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -351,7 +351,7 @@ function plc.new_session(id, s_addr, i_seq_num, reactor_id, in_queue, out_queue,
|
|||||||
local function _handle_packet(pkt)
|
local function _handle_packet(pkt)
|
||||||
-- check sequence number
|
-- check sequence number
|
||||||
if self.r_seq_num ~= pkt.scada_frame.seq_num() then
|
if self.r_seq_num ~= 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())
|
log.warning(log_tag .. "sequence out-of-order: next = " .. self.r_seq_num .. ", new = " .. pkt.scada_frame.seq_num())
|
||||||
return
|
return
|
||||||
else
|
else
|
||||||
self.r_seq_num = pkt.scada_frame.seq_num() + 1
|
self.r_seq_num = pkt.scada_frame.seq_num() + 1
|
||||||
@@ -362,7 +362,7 @@ function plc.new_session(id, s_addr, i_seq_num, reactor_id, in_queue, out_queue,
|
|||||||
---@cast pkt rplc_frame
|
---@cast pkt rplc_frame
|
||||||
-- check reactor ID
|
-- check reactor ID
|
||||||
if pkt.id ~= reactor_id then
|
if pkt.id ~= reactor_id then
|
||||||
log.warning(log_header .. "discarding RPLC packet with ID not matching reactor ID: reactor " .. reactor_id .. " != " .. pkt.id)
|
log.warning(log_tag .. "discarding RPLC packet with ID not matching reactor ID: reactor " .. reactor_id .. " != " .. pkt.id)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -375,7 +375,7 @@ function plc.new_session(id, s_addr, i_seq_num, reactor_id, in_queue, out_queue,
|
|||||||
if pkt.length >= 5 then
|
if pkt.length >= 5 then
|
||||||
_handle_status(pkt)
|
_handle_status(pkt)
|
||||||
else
|
else
|
||||||
log.debug(log_header .. "RPLC status packet length mismatch")
|
log.debug(log_tag .. "RPLC status packet length mismatch")
|
||||||
end
|
end
|
||||||
elseif pkt.type == RPLC_TYPE.MEK_STRUCT then
|
elseif pkt.type == RPLC_TYPE.MEK_STRUCT then
|
||||||
-- received reactor structure, record it
|
-- received reactor structure, record it
|
||||||
@@ -385,7 +385,7 @@ function plc.new_session(id, s_addr, i_seq_num, reactor_id, in_queue, out_queue,
|
|||||||
self.received_struct = true
|
self.received_struct = true
|
||||||
out_queue.push_data(svqtypes.SV_Q_DATA.PLC_BUILD_CHANGED, reactor_id)
|
out_queue.push_data(svqtypes.SV_Q_DATA.PLC_BUILD_CHANGED, reactor_id)
|
||||||
else
|
else
|
||||||
log.debug(log_header .. "RPLC struct packet length mismatch")
|
log.debug(log_tag .. "RPLC struct packet length mismatch")
|
||||||
end
|
end
|
||||||
elseif pkt.type == RPLC_TYPE.MEK_BURN_RATE then
|
elseif pkt.type == RPLC_TYPE.MEK_BURN_RATE then
|
||||||
-- burn rate acknowledgement
|
-- burn rate acknowledgement
|
||||||
@@ -393,7 +393,7 @@ function plc.new_session(id, s_addr, i_seq_num, reactor_id, in_queue, out_queue,
|
|||||||
if ack then
|
if ack then
|
||||||
self.acks.burn_rate = true
|
self.acks.burn_rate = true
|
||||||
elseif ack == false then
|
elseif ack == false then
|
||||||
log.debug(log_header .. "burn rate update failed!")
|
log.debug(log_tag .. "burn rate update failed!")
|
||||||
end
|
end
|
||||||
|
|
||||||
-- send acknowledgement to coordinator
|
-- send acknowledgement to coordinator
|
||||||
@@ -408,7 +408,7 @@ function plc.new_session(id, s_addr, i_seq_num, reactor_id, in_queue, out_queue,
|
|||||||
if ack then
|
if ack then
|
||||||
self.sDB.control_state = true
|
self.sDB.control_state = true
|
||||||
elseif ack == false then
|
elseif ack == false then
|
||||||
log.debug(log_header .. "enable failed!")
|
log.debug(log_tag .. "enable failed!")
|
||||||
end
|
end
|
||||||
|
|
||||||
-- send acknowledgement to coordinator
|
-- send acknowledgement to coordinator
|
||||||
@@ -424,7 +424,7 @@ function plc.new_session(id, s_addr, i_seq_num, reactor_id, in_queue, out_queue,
|
|||||||
self.acks.disable = true
|
self.acks.disable = true
|
||||||
self.sDB.control_state = false
|
self.sDB.control_state = false
|
||||||
elseif ack == false then
|
elseif ack == false then
|
||||||
log.debug(log_header .. "disable failed!")
|
log.debug(log_tag .. "disable failed!")
|
||||||
end
|
end
|
||||||
elseif pkt.type == RPLC_TYPE.RPS_SCRAM then
|
elseif pkt.type == RPLC_TYPE.RPS_SCRAM then
|
||||||
-- manual SCRAM acknowledgement
|
-- manual SCRAM acknowledgement
|
||||||
@@ -433,7 +433,7 @@ function plc.new_session(id, s_addr, i_seq_num, reactor_id, in_queue, out_queue,
|
|||||||
self.acks.scram = true
|
self.acks.scram = true
|
||||||
self.sDB.control_state = false
|
self.sDB.control_state = false
|
||||||
elseif ack == false then
|
elseif ack == false then
|
||||||
log.debug(log_header .. "manual SCRAM failed!")
|
log.debug(log_tag .. "manual SCRAM failed!")
|
||||||
end
|
end
|
||||||
|
|
||||||
-- send acknowledgement to coordinator
|
-- send acknowledgement to coordinator
|
||||||
@@ -449,7 +449,7 @@ function plc.new_session(id, s_addr, i_seq_num, reactor_id, in_queue, out_queue,
|
|||||||
self.acks.ascram = true
|
self.acks.ascram = true
|
||||||
self.sDB.control_state = false
|
self.sDB.control_state = false
|
||||||
elseif ack == false then
|
elseif ack == false then
|
||||||
log.debug(log_header .. " automatic SCRAM failed!")
|
log.debug(log_tag .. " automatic SCRAM failed!")
|
||||||
end
|
end
|
||||||
elseif pkt.type == RPLC_TYPE.RPS_STATUS then
|
elseif pkt.type == RPLC_TYPE.RPS_STATUS then
|
||||||
-- RPS status packet received, copy data
|
-- RPS status packet received, copy data
|
||||||
@@ -459,10 +459,10 @@ function plc.new_session(id, s_addr, i_seq_num, reactor_id, in_queue, out_queue,
|
|||||||
-- copied in RPS status data OK
|
-- copied in RPS status data OK
|
||||||
else
|
else
|
||||||
-- error copying RPS status data
|
-- error copying RPS status data
|
||||||
log.error(log_header .. "failed to parse RPS status packet data")
|
log.error(log_tag .. "failed to parse RPS status packet data")
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
log.debug(log_header .. "RPLC RPS status packet length mismatch")
|
log.debug(log_tag .. "RPLC RPS status packet length mismatch")
|
||||||
end
|
end
|
||||||
elseif pkt.type == RPLC_TYPE.RPS_ALARM then
|
elseif pkt.type == RPLC_TYPE.RPS_ALARM then
|
||||||
-- RPS alarm
|
-- RPS alarm
|
||||||
@@ -472,10 +472,10 @@ function plc.new_session(id, s_addr, i_seq_num, reactor_id, in_queue, out_queue,
|
|||||||
-- copied in RPS status data OK
|
-- copied in RPS status data OK
|
||||||
else
|
else
|
||||||
-- error copying RPS status data
|
-- error copying RPS status data
|
||||||
log.error(log_header .. "failed to parse RPS alarm status data")
|
log.error(log_tag .. "failed to parse RPS alarm status data")
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
log.debug(log_header .. "RPLC RPS alarm packet length mismatch")
|
log.debug(log_tag .. "RPLC RPS alarm packet length mismatch")
|
||||||
end
|
end
|
||||||
elseif pkt.type == RPLC_TYPE.RPS_RESET then
|
elseif pkt.type == RPLC_TYPE.RPS_RESET then
|
||||||
-- RPS reset acknowledgement
|
-- RPS reset acknowledgement
|
||||||
@@ -485,7 +485,7 @@ function plc.new_session(id, s_addr, i_seq_num, reactor_id, in_queue, out_queue,
|
|||||||
self.sDB.rps_tripped = false
|
self.sDB.rps_tripped = false
|
||||||
self.sDB.rps_trip_cause = "ok"
|
self.sDB.rps_trip_cause = "ok"
|
||||||
elseif ack == false then
|
elseif ack == false then
|
||||||
log.debug(log_header .. "RPS reset failed")
|
log.debug(log_tag .. "RPS reset failed")
|
||||||
end
|
end
|
||||||
|
|
||||||
-- send acknowledgement to coordinator
|
-- send acknowledgement to coordinator
|
||||||
@@ -498,7 +498,7 @@ function plc.new_session(id, s_addr, i_seq_num, reactor_id, in_queue, out_queue,
|
|||||||
-- RPS auto control reset acknowledgement
|
-- RPS auto control reset acknowledgement
|
||||||
local ack = _get_ack(pkt)
|
local ack = _get_ack(pkt)
|
||||||
if not ack then
|
if not ack then
|
||||||
log.debug(log_header .. "RPS auto reset failed")
|
log.debug(log_tag .. "RPS auto reset failed")
|
||||||
end
|
end
|
||||||
elseif pkt.type == RPLC_TYPE.AUTO_BURN_RATE then
|
elseif pkt.type == RPLC_TYPE.AUTO_BURN_RATE then
|
||||||
if pkt.length == 1 then
|
if pkt.length == 1 then
|
||||||
@@ -506,18 +506,18 @@ function plc.new_session(id, s_addr, i_seq_num, reactor_id, in_queue, out_queue,
|
|||||||
|
|
||||||
if ack == PLC_AUTO_ACK.FAIL then
|
if ack == PLC_AUTO_ACK.FAIL then
|
||||||
self.acks.burn_rate = false
|
self.acks.burn_rate = false
|
||||||
log.debug(log_header .. "RPLC automatic burn rate set fail")
|
log.debug(log_tag .. "RPLC automatic burn rate set fail")
|
||||||
elseif ack == PLC_AUTO_ACK.DIRECT_SET_OK or ack == PLC_AUTO_ACK.RAMP_SET_OK or ack == PLC_AUTO_ACK.ZERO_DIS_OK then
|
elseif ack == PLC_AUTO_ACK.DIRECT_SET_OK or ack == PLC_AUTO_ACK.RAMP_SET_OK or ack == PLC_AUTO_ACK.ZERO_DIS_OK then
|
||||||
self.acks.burn_rate = true
|
self.acks.burn_rate = true
|
||||||
else
|
else
|
||||||
self.acks.burn_rate = false
|
self.acks.burn_rate = false
|
||||||
log.debug(log_header .. "RPLC automatic burn rate ack unknown")
|
log.debug(log_tag .. "RPLC automatic burn rate ack unknown")
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
log.debug(log_header .. "RPLC automatic burn rate ack packet length mismatch")
|
log.debug(log_tag .. "RPLC automatic burn rate ack packet length mismatch")
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
log.debug(log_header .. "handler received unsupported RPLC packet type " .. pkt.type)
|
log.debug(log_tag .. "handler received unsupported RPLC packet type " .. pkt.type)
|
||||||
end
|
end
|
||||||
elseif pkt.scada_frame.protocol() == PROTOCOL.SCADA_MGMT then
|
elseif pkt.scada_frame.protocol() == PROTOCOL.SCADA_MGMT then
|
||||||
---@cast pkt mgmt_frame
|
---@cast pkt mgmt_frame
|
||||||
@@ -530,7 +530,7 @@ function plc.new_session(id, s_addr, i_seq_num, reactor_id, in_queue, out_queue,
|
|||||||
self.last_rtt = srv_now - srv_start
|
self.last_rtt = srv_now - srv_start
|
||||||
|
|
||||||
if self.last_rtt > 750 then
|
if self.last_rtt > 750 then
|
||||||
log.warning(log_header .. "PLC KEEP_ALIVE round trip time > 750ms (" .. self.last_rtt .. "ms)")
|
log.warning(log_tag .. "PLC KEEP_ALIVE round trip time > 750ms (" .. self.last_rtt .. "ms)")
|
||||||
end
|
end
|
||||||
|
|
||||||
-- log.debug(log_header .. "PLC RTT = " .. self.last_rtt .. "ms")
|
-- log.debug(log_header .. "PLC RTT = " .. self.last_rtt .. "ms")
|
||||||
@@ -538,13 +538,17 @@ function plc.new_session(id, s_addr, i_seq_num, reactor_id, in_queue, out_queue,
|
|||||||
|
|
||||||
databus.tx_plc_rtt(reactor_id, self.last_rtt)
|
databus.tx_plc_rtt(reactor_id, self.last_rtt)
|
||||||
else
|
else
|
||||||
log.debug(log_header .. "SCADA keep alive packet length mismatch")
|
log.debug(log_tag .. "SCADA keep alive packet length mismatch")
|
||||||
end
|
end
|
||||||
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_tag .. "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_tag .. "handler received unsupported SCADA_MGMT packet type " .. pkt.type)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -639,7 +643,7 @@ function plc.new_session(id, s_addr, i_seq_num, reactor_id, in_queue, out_queue,
|
|||||||
_close()
|
_close()
|
||||||
_send_mgmt(MGMT_TYPE.CLOSE, {})
|
_send_mgmt(MGMT_TYPE.CLOSE, {})
|
||||||
println("connection to reactor " .. reactor_id .. " PLC closed by server")
|
println("connection to reactor " .. reactor_id .. " PLC closed by server")
|
||||||
log.info(log_header .. "session closed by server")
|
log.info(log_tag .. "session closed by server")
|
||||||
end
|
end
|
||||||
|
|
||||||
-- iterate the session
|
-- iterate the session
|
||||||
@@ -696,7 +700,7 @@ function plc.new_session(id, s_addr, i_seq_num, reactor_id, in_queue, out_queue,
|
|||||||
_send(RPLC_TYPE.RPS_AUTO_RESET, {})
|
_send(RPLC_TYPE.RPS_AUTO_RESET, {})
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
log.error(log_header .. "unsupported command received in in_queue (this is a bug)", true)
|
log.error(log_tag .. "unsupported command received in in_queue (this is a bug)", true)
|
||||||
end
|
end
|
||||||
elseif message.qtype == mqueue.TYPE.DATA then
|
elseif message.qtype == mqueue.TYPE.DATA then
|
||||||
-- instruction with body
|
-- instruction with body
|
||||||
@@ -745,14 +749,14 @@ function plc.new_session(id, s_addr, i_seq_num, reactor_id, in_queue, out_queue,
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
log.error(log_header .. "unsupported data command received in in_queue (this is a bug)", true)
|
log.error(log_tag .. "unsupported data command received in in_queue (this is a bug)", true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- max 100ms spent processing queue
|
-- max 100ms spent processing queue
|
||||||
if util.time() - handle_start > 100 then
|
if util.time() - handle_start > 100 then
|
||||||
log.warning(log_header .. "exceeded 100ms queue process limit")
|
log.warning(log_tag .. "exceeded 100ms queue process limit")
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -760,7 +764,7 @@ function plc.new_session(id, s_addr, i_seq_num, reactor_id, in_queue, out_queue,
|
|||||||
-- exit if connection was closed
|
-- exit if connection was closed
|
||||||
if not self.connected then
|
if not self.connected then
|
||||||
println("connection to reactor " .. reactor_id .. " PLC closed by remote host")
|
println("connection to reactor " .. reactor_id .. " PLC closed by remote host")
|
||||||
log.info(log_header .. "session closed by remote host")
|
log.info(log_tag .. "session closed by remote host")
|
||||||
return self.connected
|
return self.connected
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -802,7 +806,7 @@ function plc.new_session(id, s_addr, i_seq_num, reactor_id, in_queue, out_queue,
|
|||||||
|
|
||||||
if not self.received_status_cache then
|
if not self.received_status_cache then
|
||||||
if rtimes.status_req - util.time() <= 0 then
|
if rtimes.status_req - util.time() <= 0 then
|
||||||
_send(RPLC_TYPE.MEK_STATUS, {})
|
_send(RPLC_TYPE.STATUS, {})
|
||||||
rtimes.status_req = util.time() + RETRY_PERIOD
|
rtimes.status_req = util.time() + RETRY_PERIOD
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout,
|
|||||||
-- print a log message to the terminal as long as the UI isn't running
|
-- print a log message to the terminal as long as the UI isn't running
|
||||||
local function println(message) if not fp_ok then util.println_ts(message) end end
|
local function println(message) if not fp_ok then util.println_ts(message) end end
|
||||||
|
|
||||||
local log_header = "pdg_session(" .. id .. "): "
|
local log_tag = "pdg_session(" .. id .. "): "
|
||||||
|
|
||||||
local self = {
|
local self = {
|
||||||
-- connection properties
|
-- connection properties
|
||||||
@@ -95,7 +95,7 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout,
|
|||||||
local function _handle_packet(pkt)
|
local function _handle_packet(pkt)
|
||||||
-- check sequence number
|
-- check sequence number
|
||||||
if self.r_seq_num ~= pkt.scada_frame.seq_num() then
|
if self.r_seq_num ~= 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())
|
log.warning(log_tag .. "sequence out-of-order: next = " .. self.r_seq_num .. ", new = " .. pkt.scada_frame.seq_num())
|
||||||
return
|
return
|
||||||
else
|
else
|
||||||
self.r_seq_num = pkt.scada_frame.seq_num() + 1
|
self.r_seq_num = pkt.scada_frame.seq_num() + 1
|
||||||
@@ -116,7 +116,7 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout,
|
|||||||
self.last_rtt = srv_now - srv_start
|
self.last_rtt = srv_now - srv_start
|
||||||
|
|
||||||
if self.last_rtt > 750 then
|
if self.last_rtt > 750 then
|
||||||
log.warning(log_header .. "PDG KEEP_ALIVE round trip time > 750ms (" .. self.last_rtt .. "ms)")
|
log.warning(log_tag .. "PDG KEEP_ALIVE round trip time > 750ms (" .. self.last_rtt .. "ms)")
|
||||||
end
|
end
|
||||||
|
|
||||||
-- log.debug(log_header .. "PDG RTT = " .. self.last_rtt .. "ms")
|
-- log.debug(log_header .. "PDG RTT = " .. self.last_rtt .. "ms")
|
||||||
@@ -124,11 +124,15 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout,
|
|||||||
|
|
||||||
databus.tx_pdg_rtt(id, self.last_rtt)
|
databus.tx_pdg_rtt(id, self.last_rtt)
|
||||||
else
|
else
|
||||||
log.debug(log_header .. "SCADA keep alive packet length mismatch")
|
log.debug(log_tag .. "SCADA keep alive packet length mismatch")
|
||||||
end
|
end
|
||||||
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_tag .. "terminated session due to an unexpected ESTABLISH packet")
|
||||||
elseif pkt.type == MGMT_TYPE.DIAG_TONE_GET then
|
elseif pkt.type == MGMT_TYPE.DIAG_TONE_GET then
|
||||||
-- get the state of alarm tones
|
-- get the state of alarm tones
|
||||||
_send_mgmt(MGMT_TYPE.DIAG_TONE_GET, facility.get_alarm_tones())
|
_send_mgmt(MGMT_TYPE.DIAG_TONE_GET, facility.get_alarm_tones())
|
||||||
@@ -145,13 +149,13 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout,
|
|||||||
local allow_testing, test_tone_states = facility.diag_set_test_tone(pkt.data[1], pkt.data[2])
|
local allow_testing, test_tone_states = facility.diag_set_test_tone(pkt.data[1], pkt.data[2])
|
||||||
_send_mgmt(MGMT_TYPE.DIAG_TONE_SET, { allow_testing, test_tone_states })
|
_send_mgmt(MGMT_TYPE.DIAG_TONE_SET, { allow_testing, test_tone_states })
|
||||||
else
|
else
|
||||||
log.debug(log_header .. "SCADA diag tone set packet data type mismatch")
|
log.debug(log_tag .. "SCADA diag tone set packet data type mismatch")
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
log.debug(log_header .. "SCADA diag tone set packet length mismatch")
|
log.debug(log_tag .. "SCADA diag tone set packet length mismatch")
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
log.debug(log_header .. "DIAG_TONE_SET is blocked without HMAC for security")
|
log.debug(log_tag .. "DIAG_TONE_SET is blocked without HMAC for security")
|
||||||
end
|
end
|
||||||
|
|
||||||
if not valid then _send_mgmt(MGMT_TYPE.DIAG_TONE_SET, { false }) end
|
if not valid then _send_mgmt(MGMT_TYPE.DIAG_TONE_SET, { false }) end
|
||||||
@@ -168,18 +172,18 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout,
|
|||||||
local allow_testing, test_alarm_states = facility.diag_set_test_alarm(pkt.data[1], pkt.data[2])
|
local allow_testing, test_alarm_states = facility.diag_set_test_alarm(pkt.data[1], pkt.data[2])
|
||||||
_send_mgmt(MGMT_TYPE.DIAG_ALARM_SET, { allow_testing, test_alarm_states })
|
_send_mgmt(MGMT_TYPE.DIAG_ALARM_SET, { allow_testing, test_alarm_states })
|
||||||
else
|
else
|
||||||
log.debug(log_header .. "SCADA diag alarm set packet data type mismatch")
|
log.debug(log_tag .. "SCADA diag alarm set packet data type mismatch")
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
log.debug(log_header .. "SCADA diag alarm set packet length mismatch")
|
log.debug(log_tag .. "SCADA diag alarm set packet length mismatch")
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
log.debug(log_header .. "DIAG_ALARM_SET is blocked without HMAC for security")
|
log.debug(log_tag .. "DIAG_ALARM_SET is blocked without HMAC for security")
|
||||||
end
|
end
|
||||||
|
|
||||||
if not valid then _send_mgmt(MGMT_TYPE.DIAG_ALARM_SET, { false }) end
|
if not valid then _send_mgmt(MGMT_TYPE.DIAG_ALARM_SET, { false }) end
|
||||||
else
|
else
|
||||||
log.debug(log_header .. "handler received unsupported SCADA_MGMT packet type " .. pkt.type)
|
log.debug(log_tag .. "handler received unsupported SCADA_MGMT packet type " .. pkt.type)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -205,7 +209,7 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout,
|
|||||||
_close()
|
_close()
|
||||||
_send_mgmt(MGMT_TYPE.CLOSE, {})
|
_send_mgmt(MGMT_TYPE.CLOSE, {})
|
||||||
println("connection to pocket diag session " .. id .. " closed by server")
|
println("connection to pocket diag session " .. id .. " closed by server")
|
||||||
log.info(log_header .. "session closed by server")
|
log.info(log_tag .. "session closed by server")
|
||||||
end
|
end
|
||||||
|
|
||||||
-- iterate the session
|
-- iterate the session
|
||||||
@@ -236,7 +240,7 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout,
|
|||||||
|
|
||||||
-- max 100ms spent processing queue
|
-- max 100ms spent processing queue
|
||||||
if util.time() - handle_start > 100 then
|
if util.time() - handle_start > 100 then
|
||||||
log.warning(log_header .. "exceeded 100ms queue process limit")
|
log.warning(log_tag .. "exceeded 100ms queue process limit")
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -244,7 +248,7 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout,
|
|||||||
-- exit if connection was closed
|
-- exit if connection was closed
|
||||||
if not self.connected then
|
if not self.connected then
|
||||||
println("connection to pocket diag session " .. id .. " closed by remote host")
|
println("connection to pocket diag session " .. id .. " closed by remote host")
|
||||||
log.info(log_header .. "session closed by remote host")
|
log.info(log_tag .. "session closed by remote host")
|
||||||
return self.connected
|
return self.connected
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ local PERIODICS = {
|
|||||||
ALARM_TONES = 500
|
ALARM_TONES = 500
|
||||||
}
|
}
|
||||||
|
|
||||||
-- create a new RTU session
|
-- create a new RTU gateway session
|
||||||
---@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
|
||||||
@@ -38,14 +38,14 @@ local PERIODICS = {
|
|||||||
---@param in_queue mqueue in message queue
|
---@param in_queue mqueue in message queue
|
||||||
---@param out_queue mqueue out message queue
|
---@param out_queue mqueue out message queue
|
||||||
---@param timeout number communications timeout
|
---@param timeout number communications timeout
|
||||||
---@param advertisement table RTU device advertisement
|
---@param advertisement table RTU gateway device advertisement
|
||||||
---@param facility facility facility data table
|
---@param facility facility facility data table
|
||||||
---@param fp_ok boolean if the front panel UI is running
|
---@param fp_ok boolean if the front panel UI is running
|
||||||
function rtu.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout, advertisement, facility, fp_ok)
|
function rtu.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout, advertisement, facility, fp_ok)
|
||||||
-- print a log message to the terminal as long as the UI isn't running
|
-- print a log message to the terminal as long as the UI isn't running
|
||||||
local function println(message) if not fp_ok then util.println_ts(message) end end
|
local function println(message) if not fp_ok then util.println_ts(message) end end
|
||||||
|
|
||||||
local log_header = "rtu_session(" .. id .. "): "
|
local log_tag = "rtu_gw_session(" .. id .. "): "
|
||||||
|
|
||||||
local self = {
|
local self = {
|
||||||
modbus_q = mqueue.new(),
|
modbus_q = mqueue.new(),
|
||||||
@@ -124,7 +124,7 @@ function rtu.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout, ad
|
|||||||
|
|
||||||
if u_type == false then
|
if u_type == false then
|
||||||
-- validation fail
|
-- validation fail
|
||||||
log.debug(log_header .. "_handle_advertisement(): advertisement unit validation failure")
|
log.debug(log_tag .. "_handle_advertisement(): advertisement unit validation failure")
|
||||||
else
|
else
|
||||||
if unit_advert.reactor > 0 then
|
if unit_advert.reactor > 0 then
|
||||||
local target_unit = self.fac_units[unit_advert.reactor] ---@type reactor_unit
|
local target_unit = self.fac_units[unit_advert.reactor] ---@type reactor_unit
|
||||||
@@ -156,9 +156,9 @@ function rtu.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout, ad
|
|||||||
if type(unit) ~= "nil" then target_unit.add_envd(unit) end
|
if type(unit) ~= "nil" then target_unit.add_envd(unit) end
|
||||||
elseif u_type == RTU_UNIT_TYPE.VIRTUAL then
|
elseif u_type == RTU_UNIT_TYPE.VIRTUAL then
|
||||||
-- skip virtual units
|
-- skip virtual units
|
||||||
log.debug(util.c(log_header, "skipping virtual RTU unit #", i))
|
log.debug(util.c(log_tag, "skipping virtual RTU #", i))
|
||||||
else
|
else
|
||||||
log.warning(util.c(log_header, "_handle_advertisement(): encountered unsupported reactor-specific RTU type ", type_string))
|
log.warning(util.c(log_tag, "_handle_advertisement(): encountered unsupported reactor-specific RTU type ", type_string))
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
-- facility RTUs
|
-- facility RTUs
|
||||||
@@ -184,9 +184,9 @@ function rtu.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout, ad
|
|||||||
if type(unit) ~= "nil" then facility.add_envd(unit) end
|
if type(unit) ~= "nil" then facility.add_envd(unit) end
|
||||||
elseif u_type == RTU_UNIT_TYPE.VIRTUAL then
|
elseif u_type == RTU_UNIT_TYPE.VIRTUAL then
|
||||||
-- skip virtual units
|
-- skip virtual units
|
||||||
log.debug(util.c(log_header, "skipping virtual RTU unit #", i))
|
log.debug(util.c(log_tag, "skipping virtual RTU #", i))
|
||||||
else
|
else
|
||||||
log.warning(util.c(log_header, "_handle_advertisement(): encountered unsupported facility RTU type ", type_string))
|
log.warning(util.c(log_tag, "_handle_advertisement(): encountered unsupported facility RTU type ", type_string))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -195,20 +195,20 @@ function rtu.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout, ad
|
|||||||
self.units[i] = unit
|
self.units[i] = unit
|
||||||
unit_count = unit_count + 1
|
unit_count = unit_count + 1
|
||||||
elseif u_type ~= RTU_UNIT_TYPE.VIRTUAL then
|
elseif u_type ~= RTU_UNIT_TYPE.VIRTUAL then
|
||||||
log.warning(util.c(log_header, "_handle_advertisement(): problem occured while creating a unit (type is ", type_string, ")"))
|
log.warning(util.c(log_tag, "_handle_advertisement(): problem occured while creating a unit (type is ", type_string, ")"))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
databus.tx_rtu_units(id, unit_count)
|
databus.tx_rtu_units(id, unit_count)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- mark this RTU session as closed, stop watchdog
|
-- mark this RTU gateway session as closed, stop watchdog
|
||||||
local function _close()
|
local function _close()
|
||||||
self.conn_watchdog.cancel()
|
self.conn_watchdog.cancel()
|
||||||
self.connected = false
|
self.connected = false
|
||||||
databus.tx_rtu_disconnected(id)
|
databus.tx_rtu_disconnected(id)
|
||||||
|
|
||||||
-- mark all RTU unit sessions as closed so the reactor unit knows
|
-- mark all RTU sessions as closed so the reactor unit knows
|
||||||
for _, unit in pairs(self.units) do unit.close() end
|
for _, unit in pairs(self.units) do unit.close() end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -242,7 +242,7 @@ function rtu.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout, ad
|
|||||||
local function _handle_packet(pkt)
|
local function _handle_packet(pkt)
|
||||||
-- check sequence number
|
-- check sequence number
|
||||||
if self.r_seq_num ~= pkt.scada_frame.seq_num() then
|
if self.r_seq_num ~= 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())
|
log.warning(log_tag .. "sequence out-of-order: next = " .. self.r_seq_num .. ", new = " .. pkt.scada_frame.seq_num())
|
||||||
return
|
return
|
||||||
else
|
else
|
||||||
self.r_seq_num = pkt.scada_frame.seq_num() + 1
|
self.r_seq_num = pkt.scada_frame.seq_num() + 1
|
||||||
@@ -265,27 +265,31 @@ function rtu.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout, ad
|
|||||||
-- keep alive reply
|
-- keep alive reply
|
||||||
if pkt.length == 2 then
|
if pkt.length == 2 then
|
||||||
local srv_start = pkt.data[1]
|
local srv_start = pkt.data[1]
|
||||||
-- local rtu_send = pkt.data[2]
|
-- local rtu_gw_send = pkt.data[2]
|
||||||
local srv_now = util.time()
|
local srv_now = util.time()
|
||||||
self.last_rtt = srv_now - srv_start
|
self.last_rtt = srv_now - srv_start
|
||||||
|
|
||||||
if self.last_rtt > 750 then
|
if self.last_rtt > 750 then
|
||||||
log.warning(log_header .. "RTU KEEP_ALIVE round trip time > 750ms (" .. self.last_rtt .. "ms)")
|
log.warning(log_tag .. "RTU GW KEEP_ALIVE round trip time > 750ms (" .. self.last_rtt .. "ms)")
|
||||||
end
|
end
|
||||||
|
|
||||||
-- log.debug(log_header .. "RTU RTT = " .. self.last_rtt .. "ms")
|
-- log.debug(log_tag .. "RTU GW RTT = " .. self.last_rtt .. "ms")
|
||||||
-- log.debug(log_header .. "RTU TT = " .. (srv_now - rtu_send) .. "ms")
|
-- log.debug(log_tag .. "RTU GW TT = " .. (srv_now - rtu_gw_send) .. "ms")
|
||||||
|
|
||||||
databus.tx_rtu_rtt(id, self.last_rtt)
|
databus.tx_rtu_rtt(id, self.last_rtt)
|
||||||
else
|
else
|
||||||
log.debug(log_header .. "SCADA keep alive packet length mismatch")
|
log.debug(log_tag .. "SCADA keep alive packet length mismatch")
|
||||||
end
|
end
|
||||||
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_tag .. "terminated session due to an unexpected ESTABLISH packet")
|
||||||
elseif pkt.type == MGMT_TYPE.RTU_ADVERT then
|
elseif pkt.type == MGMT_TYPE.RTU_ADVERT then
|
||||||
-- RTU unit advertisement
|
-- RTU advertisement
|
||||||
log.debug(log_header .. "received updated advertisement")
|
log.debug(log_tag .. "received updated advertisement")
|
||||||
self.advert = pkt.data
|
self.advert = pkt.data
|
||||||
|
|
||||||
-- handle advertisement; this will re-create all unit sub-sessions
|
-- handle advertisement; this will re-create all unit sub-sessions
|
||||||
@@ -298,17 +302,17 @@ function rtu.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout, ad
|
|||||||
unit.invalidate_cache()
|
unit.invalidate_cache()
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
log.debug(log_header .. "SCADA RTU device re-mount packet length mismatch")
|
log.debug(log_tag .. "SCADA RTU GW device re-mount packet length mismatch")
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
log.debug(log_header .. "handler received unsupported SCADA_MGMT packet type " .. pkt.type)
|
log.debug(log_tag .. "handler received unsupported SCADA_MGMT packet type " .. pkt.type)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- PUBLIC FUNCTIONS --
|
-- PUBLIC FUNCTIONS --
|
||||||
|
|
||||||
-- get the session ID
|
-- get the gateway session ID
|
||||||
function public.get_id() return id end
|
function public.get_id() return id end
|
||||||
|
|
||||||
-- check if a timer matches this session's watchdog
|
-- check if a timer matches this session's watchdog
|
||||||
@@ -322,8 +326,8 @@ function rtu.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout, ad
|
|||||||
function public.close()
|
function public.close()
|
||||||
_close()
|
_close()
|
||||||
_send_mgmt(MGMT_TYPE.CLOSE, {})
|
_send_mgmt(MGMT_TYPE.CLOSE, {})
|
||||||
println(log_header .. "connection to RTU closed by server")
|
println(log_tag .. "connection to RTU GW closed by server")
|
||||||
log.info(log_header .. "session closed by server")
|
log.info(log_tag .. "session closed by server")
|
||||||
end
|
end
|
||||||
|
|
||||||
-- iterate the session
|
-- iterate the session
|
||||||
@@ -354,7 +358,7 @@ function rtu.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout, ad
|
|||||||
|
|
||||||
-- max 100ms spent processing queue
|
-- max 100ms spent processing queue
|
||||||
if util.time() - handle_start > 100 then
|
if util.time() - handle_start > 100 then
|
||||||
log.warning(log_header .. "exceeded 100ms queue process limit")
|
log.warning(log_tag .. "exceeded 100ms queue process limit")
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -362,7 +366,7 @@ function rtu.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout, ad
|
|||||||
-- exit if connection was closed
|
-- exit if connection was closed
|
||||||
if not self.connected then
|
if not self.connected then
|
||||||
println("RTU connection " .. id .. " closed by remote host")
|
println("RTU connection " .. id .. " closed by remote host")
|
||||||
log.info(log_header .. "session closed by remote host")
|
log.info(log_tag .. "session closed by remote host")
|
||||||
return self.connected
|
return self.connected
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -32,10 +32,10 @@ local PERIODICS = {
|
|||||||
|
|
||||||
-- create a new boilerv rtu session runner
|
-- create a new boilerv rtu session runner
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
---@param session_id integer RTU session ID
|
---@param session_id integer RTU gateway session ID
|
||||||
---@param unit_id integer RTU unit ID
|
---@param unit_id integer RTU ID
|
||||||
---@param advert rtu_advertisement RTU advertisement table
|
---@param advert rtu_advertisement RTU advertisement table
|
||||||
---@param out_queue mqueue RTU unit message out queue
|
---@param out_queue mqueue RTU message out queue
|
||||||
function boilerv.new(session_id, unit_id, advert, out_queue)
|
function boilerv.new(session_id, unit_id, advert, out_queue)
|
||||||
-- checks
|
-- checks
|
||||||
if advert.type ~= RTU_UNIT_TYPE.BOILER_VALVE then
|
if advert.type ~= RTU_UNIT_TYPE.BOILER_VALVE then
|
||||||
|
|||||||
@@ -44,10 +44,10 @@ local PERIODICS = {
|
|||||||
|
|
||||||
-- create a new dynamicv rtu session runner
|
-- create a new dynamicv rtu session runner
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
---@param session_id integer RTU session ID
|
---@param session_id integer RTU gateway session ID
|
||||||
---@param unit_id integer RTU unit ID
|
---@param unit_id integer RTU ID
|
||||||
---@param advert rtu_advertisement RTU advertisement table
|
---@param advert rtu_advertisement RTU advertisement table
|
||||||
---@param out_queue mqueue RTU unit message out queue
|
---@param out_queue mqueue RTU message out queue
|
||||||
function dynamicv.new(session_id, unit_id, advert, out_queue)
|
function dynamicv.new(session_id, unit_id, advert, out_queue)
|
||||||
-- checks
|
-- checks
|
||||||
if advert.type ~= RTU_UNIT_TYPE.DYNAMIC_VALVE then
|
if advert.type ~= RTU_UNIT_TYPE.DYNAMIC_VALVE then
|
||||||
|
|||||||
@@ -23,10 +23,10 @@ local PERIODICS = {
|
|||||||
|
|
||||||
-- create a new environment detector rtu session runner
|
-- create a new environment detector rtu session runner
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
---@param session_id integer
|
---@param session_id integer RTU gateway session ID
|
||||||
---@param unit_id integer
|
---@param unit_id integer RTU ID
|
||||||
---@param advert rtu_advertisement
|
---@param advert rtu_advertisement RTU advertisement table
|
||||||
---@param out_queue mqueue
|
---@param out_queue mqueue RTU message out queue
|
||||||
function envd.new(session_id, unit_id, advert, out_queue)
|
function envd.new(session_id, unit_id, advert, out_queue)
|
||||||
-- checks
|
-- checks
|
||||||
if advert.type ~= RTU_UNIT_TYPE.ENV_DETECTOR then
|
if advert.type ~= RTU_UNIT_TYPE.ENV_DETECTOR then
|
||||||
|
|||||||
@@ -32,10 +32,10 @@ local PERIODICS = {
|
|||||||
|
|
||||||
-- create a new imatrix rtu session runner
|
-- create a new imatrix rtu session runner
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
---@param session_id integer RTU session ID
|
---@param session_id integer RTU gateway session ID
|
||||||
---@param unit_id integer RTU unit ID
|
---@param unit_id integer RTU ID
|
||||||
---@param advert rtu_advertisement RTU advertisement table
|
---@param advert rtu_advertisement RTU advertisement table
|
||||||
---@param out_queue mqueue RTU unit message out queue
|
---@param out_queue mqueue RTU message out queue
|
||||||
function imatrix.new(session_id, unit_id, advert, out_queue)
|
function imatrix.new(session_id, unit_id, advert, out_queue)
|
||||||
-- checks
|
-- checks
|
||||||
if advert.type ~= RTU_UNIT_TYPE.IMATRIX then
|
if advert.type ~= RTU_UNIT_TYPE.IMATRIX then
|
||||||
|
|||||||
@@ -45,10 +45,10 @@ local PERIODICS = {
|
|||||||
|
|
||||||
-- create a new redstone rtu session runner
|
-- create a new redstone rtu session runner
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
---@param session_id integer
|
---@param session_id integer RTU gateway session ID
|
||||||
---@param unit_id integer
|
---@param unit_id integer RTU ID
|
||||||
---@param advert rtu_advertisement
|
---@param advert rtu_advertisement RTU advertisement table
|
||||||
---@param out_queue mqueue
|
---@param out_queue mqueue RTU message out queue
|
||||||
function redstone.new(session_id, unit_id, advert, out_queue)
|
function redstone.new(session_id, unit_id, advert, out_queue)
|
||||||
-- type check
|
-- type check
|
||||||
if advert.type ~= RTU_UNIT_TYPE.REDSTONE then
|
if advert.type ~= RTU_UNIT_TYPE.REDSTONE then
|
||||||
|
|||||||
@@ -29,10 +29,10 @@ local PERIODICS = {
|
|||||||
|
|
||||||
-- create a new sna rtu session runner
|
-- create a new sna rtu session runner
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
---@param session_id integer RTU session ID
|
---@param session_id integer RTU gateway session ID
|
||||||
---@param unit_id integer RTU unit ID
|
---@param unit_id integer RTU ID
|
||||||
---@param advert rtu_advertisement RTU advertisement table
|
---@param advert rtu_advertisement RTU advertisement table
|
||||||
---@param out_queue mqueue RTU unit message out queue
|
---@param out_queue mqueue RTU message out queue
|
||||||
function sna.new(session_id, unit_id, advert, out_queue)
|
function sna.new(session_id, unit_id, advert, out_queue)
|
||||||
-- type check
|
-- type check
|
||||||
if advert.type ~= RTU_UNIT_TYPE.SNA then
|
if advert.type ~= RTU_UNIT_TYPE.SNA then
|
||||||
|
|||||||
@@ -32,10 +32,10 @@ local PERIODICS = {
|
|||||||
|
|
||||||
-- create a new sps rtu session runner
|
-- create a new sps rtu session runner
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
---@param session_id integer RTU session ID
|
---@param session_id integer RTU gateway session ID
|
||||||
---@param unit_id integer RTU unit ID
|
---@param unit_id integer RTU ID
|
||||||
---@param advert rtu_advertisement RTU advertisement table
|
---@param advert rtu_advertisement RTU advertisement table
|
||||||
---@param out_queue mqueue RTU unit message out queue
|
---@param out_queue mqueue RTU message out queue
|
||||||
function sps.new(session_id, unit_id, advert, out_queue)
|
function sps.new(session_id, unit_id, advert, out_queue)
|
||||||
-- type check
|
-- type check
|
||||||
if advert.type ~= RTU_UNIT_TYPE.SPS then
|
if advert.type ~= RTU_UNIT_TYPE.SPS then
|
||||||
|
|||||||
@@ -44,10 +44,10 @@ local PERIODICS = {
|
|||||||
|
|
||||||
-- create a new turbinev rtu session runner
|
-- create a new turbinev rtu session runner
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
---@param session_id integer RTU session ID
|
---@param session_id integer RTU gateway session ID
|
||||||
---@param unit_id integer RTU unit ID
|
---@param unit_id integer RTU ID
|
||||||
---@param advert rtu_advertisement RTU advertisement table
|
---@param advert rtu_advertisement RTU advertisement table
|
||||||
---@param out_queue mqueue RTU unit message out queue
|
---@param out_queue mqueue RTU message out queue
|
||||||
function turbinev.new(session_id, unit_id, advert, out_queue)
|
function turbinev.new(session_id, unit_id, advert, out_queue)
|
||||||
-- checks
|
-- checks
|
||||||
if advert.type ~= RTU_UNIT_TYPE.TURBINE_VALVE then
|
if advert.type ~= RTU_UNIT_TYPE.TURBINE_VALVE then
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ unit_session.RTU_US_DATA = RTU_US_DATA
|
|||||||
|
|
||||||
-- create a new unit session runner
|
-- create a new unit session runner
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
---@param session_id integer RTU session ID
|
---@param session_id integer RTU gateway session ID
|
||||||
---@param unit_id integer MODBUS unit ID
|
---@param unit_id integer MODBUS unit ID
|
||||||
---@param advert rtu_advertisement RTU advertisement for this unit
|
---@param advert rtu_advertisement RTU advertisement for this unit
|
||||||
---@param out_queue mqueue send queue
|
---@param out_queue mqueue send queue
|
||||||
@@ -144,12 +144,15 @@ function unit_session.new(session_id, unit_id, advert, out_queue, log_tag, txn_t
|
|||||||
|
|
||||||
-- PUBLIC FUNCTIONS --
|
-- PUBLIC FUNCTIONS --
|
||||||
|
|
||||||
-- get the unit ID
|
-- get the RTU gateway session ID
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
function public.get_session_id() return session_id end
|
function public.get_session_id() return session_id end
|
||||||
-- get the unit ID
|
-- get the unit ID
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
function public.get_unit_id() return unit_id end
|
function public.get_unit_id() return unit_id end
|
||||||
|
-- get the RTU type
|
||||||
|
---@nodiscard
|
||||||
|
function public.get_unit_type() return advert.type end
|
||||||
-- get the device index
|
-- get the device index
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
function public.get_device_idx() return self.device_index or 0 end
|
function public.get_device_idx() return self.device_index or 0 end
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
|
--
|
||||||
|
-- Supervisor Sessions Handler
|
||||||
|
--
|
||||||
|
|
||||||
local log = require("scada-common.log")
|
local log = require("scada-common.log")
|
||||||
local mqueue = require("scada-common.mqueue")
|
local mqueue = require("scada-common.mqueue")
|
||||||
|
local types = require("scada-common.types")
|
||||||
local util = require("scada-common.util")
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
local databus = require("supervisor.databus")
|
local databus = require("supervisor.databus")
|
||||||
local facility = require("supervisor.facility")
|
|
||||||
|
local pgi = require("supervisor.panel.pgi")
|
||||||
|
|
||||||
local coordinator = require("supervisor.session.coordinator")
|
local coordinator = require("supervisor.session.coordinator")
|
||||||
local plc = require("supervisor.session.plc")
|
local plc = require("supervisor.session.plc")
|
||||||
@@ -11,13 +17,15 @@ local pocket = require("supervisor.session.pocket")
|
|||||||
local rtu = require("supervisor.session.rtu")
|
local rtu = require("supervisor.session.rtu")
|
||||||
local svqtypes = require("supervisor.session.svqtypes")
|
local svqtypes = require("supervisor.session.svqtypes")
|
||||||
|
|
||||||
-- Supervisor Sessions Handler
|
local RTU_ID_FAIL = types.RTU_ID_FAIL
|
||||||
|
local RTU_TYPES = types.RTU_UNIT_TYPE
|
||||||
|
|
||||||
local SV_Q_DATA = svqtypes.SV_Q_DATA
|
local SV_Q_DATA = svqtypes.SV_Q_DATA
|
||||||
|
|
||||||
local PLC_S_CMDS = plc.PLC_S_CMDS
|
local PLC_S_CMDS = plc.PLC_S_CMDS
|
||||||
local PLC_S_DATA = plc.PLC_S_DATA
|
local PLC_S_DATA = plc.PLC_S_DATA
|
||||||
local CRD_S_DATA = coordinator.CRD_S_DATA
|
|
||||||
|
local CRD_S_DATA = coordinator.CRD_S_DATA
|
||||||
|
|
||||||
local svsessions = {}
|
local svsessions = {}
|
||||||
|
|
||||||
@@ -37,12 +45,13 @@ local self = {
|
|||||||
config = nil, ---@type svr_config
|
config = nil, ---@type svr_config
|
||||||
facility = nil, ---@type facility|nil
|
facility = nil, ---@type facility|nil
|
||||||
sessions = { rtu = {}, plc = {}, crd = {}, pdg = {} },
|
sessions = { rtu = {}, plc = {}, crd = {}, pdg = {} },
|
||||||
next_ids = { rtu = 0, plc = 0, crd = 0, pdg = 0 }
|
next_ids = { rtu = 0, plc = 0, crd = 0, pdg = 0 },
|
||||||
|
dev_dbg = { duplicate = {}, out_of_range = {}, connected = {} }
|
||||||
}
|
}
|
||||||
|
|
||||||
---@alias sv_session_structs plc_session_struct|rtu_session_struct|crd_session_struct|pdg_session_struct
|
---@alias sv_session_structs plc_session_struct|rtu_session_struct|crd_session_struct|pdg_session_struct
|
||||||
|
|
||||||
-- PRIVATE FUNCTIONS --
|
--#region PRIVATE FUNCTIONS
|
||||||
|
|
||||||
-- handle a session output queue
|
-- handle a session output queue
|
||||||
---@param session sv_session_structs
|
---@param session sv_session_structs
|
||||||
@@ -190,18 +199,184 @@ local function _find_session(list, s_addr)
|
|||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
-- PUBLIC FUNCTIONS --
|
-- periodically remove disconnected RTU gateway's RTU ID warnings and update the missing device list
|
||||||
|
local function _update_dev_dbg()
|
||||||
|
-- remove disconnected units from check failures lists
|
||||||
|
|
||||||
|
local f = function (unit) return unit.is_connected() end
|
||||||
|
|
||||||
|
util.filter_table(self.dev_dbg.duplicate, f, pgi.delete_chk_entry)
|
||||||
|
util.filter_table(self.dev_dbg.out_of_range, f, pgi.delete_chk_entry)
|
||||||
|
|
||||||
|
-- update missing list
|
||||||
|
|
||||||
|
local conns = self.dev_dbg.connected
|
||||||
|
local units = self.facility.get_units()
|
||||||
|
local rtu_conns = self.facility.check_rtu_conns()
|
||||||
|
|
||||||
|
local function report(disconnected, msg)
|
||||||
|
if disconnected then pgi.create_missing_entry(msg) else pgi.delete_missing_entry(msg) end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- look for disconnected facility RTUs
|
||||||
|
|
||||||
|
if rtu_conns.induction ~= conns.induction then
|
||||||
|
report(conns.induction, util.c("the facility's induction matrix"))
|
||||||
|
conns.induction = rtu_conns.induction
|
||||||
|
end
|
||||||
|
|
||||||
|
if rtu_conns.sps ~= conns.sps then
|
||||||
|
report(conns.sps, util.c("the facility's SPS"))
|
||||||
|
conns.sps = rtu_conns.sps
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = 1, #conns.tanks do
|
||||||
|
if (rtu_conns.tanks[i] or false) ~= conns.tanks[i] then
|
||||||
|
report(conns.tanks[i], util.c("the facility's #", i, " dynamic tank"))
|
||||||
|
conns.tanks[i] = rtu_conns.tanks[i] or false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- look for disconnected unit RTUs
|
||||||
|
|
||||||
|
for u = 1, #units do
|
||||||
|
local u_conns = conns.units[u]
|
||||||
|
|
||||||
|
rtu_conns = units[u].check_rtu_conns()
|
||||||
|
|
||||||
|
for i = 1, #u_conns.boilers do
|
||||||
|
if (rtu_conns.boilers[i] or false) ~= u_conns.boilers[i] then
|
||||||
|
report(u_conns.boilers[i], util.c("unit ", u, "'s #", i, " boiler"))
|
||||||
|
u_conns.boilers[i] = rtu_conns.boilers[i] or false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = 1, #u_conns.turbines do
|
||||||
|
if (rtu_conns.turbines[i] or false) ~= u_conns.turbines[i] then
|
||||||
|
report(u_conns.turbines[i], util.c("unit ", u, "'s #", i, " turbine"))
|
||||||
|
u_conns.turbines[i] = rtu_conns.turbines[i] or false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = 1, #u_conns.tanks do
|
||||||
|
if (rtu_conns.tanks[i] or false) ~= u_conns.tanks[i] then
|
||||||
|
report(u_conns.tanks[i], util.c("unit ", u, "'s dynamic tank"))
|
||||||
|
u_conns.tanks[i] = rtu_conns.tanks[i] or false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
|
--#region PUBLIC FUNCTIONS
|
||||||
|
|
||||||
|
-- on attempted link of an RTU to a facility or unit object, verify its ID and report a problem if it can't be accepted
|
||||||
|
---@param unit unit_session RTU session
|
||||||
|
---@param list table table of RTU sessions
|
||||||
|
---@param max integer max of this type of RTU
|
||||||
|
---@return RTU_ID_FAIL fail_code, string fail_str
|
||||||
|
function svsessions.check_rtu_id(unit, list, max)
|
||||||
|
local fail_code, fail_str = RTU_ID_FAIL.OK, "OK"
|
||||||
|
|
||||||
|
if (unit.get_device_idx() < 1 and max ~= 1) or unit.get_device_idx() > max then
|
||||||
|
-- out-of-range
|
||||||
|
fail_code, fail_str = RTU_ID_FAIL.OUT_OF_RANGE, "index out of range"
|
||||||
|
table.insert(self.dev_dbg.out_of_range, unit)
|
||||||
|
else
|
||||||
|
for _, u in ipairs(list) do
|
||||||
|
if u.get_device_idx() == unit.get_device_idx() then
|
||||||
|
-- duplicate
|
||||||
|
fail_code, fail_str = RTU_ID_FAIL.DUPLICATE, "duplicate index"
|
||||||
|
table.insert(self.dev_dbg.duplicate, unit)
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- make sure this won't exceed the maximum allowable devices
|
||||||
|
if fail_code == RTU_ID_FAIL.OK and #list >= max then
|
||||||
|
fail_code, fail_str = RTU_ID_FAIL.MAX_DEVICES, "too many of this type"
|
||||||
|
end
|
||||||
|
|
||||||
|
-- add to the list for the user
|
||||||
|
if fail_code ~= RTU_ID_FAIL.OK and fail_code ~= RTU_ID_FAIL.MAX_DEVICES then
|
||||||
|
local r_id, idx, type = unit.get_reactor(), unit.get_device_idx(), unit.get_unit_type()
|
||||||
|
local msg
|
||||||
|
|
||||||
|
if r_id == 0 then
|
||||||
|
msg = "the facility's "
|
||||||
|
|
||||||
|
if type == RTU_TYPES.IMATRIX then
|
||||||
|
msg = msg .. "induction matrix"
|
||||||
|
elseif type == RTU_TYPES.SPS then
|
||||||
|
msg = msg .. "SPS"
|
||||||
|
elseif type == RTU_TYPES.DYNAMIC_VALVE then
|
||||||
|
msg = util.c(msg, "#", idx, " dynamic tank")
|
||||||
|
elseif type == RTU_TYPES.ENV_DETECTOR then
|
||||||
|
msg = util.c(msg, "#", idx, " env. detector")
|
||||||
|
else
|
||||||
|
msg = msg .. " ? (error)"
|
||||||
|
end
|
||||||
|
else
|
||||||
|
msg = util.c("unit ", r_id, "'s ")
|
||||||
|
|
||||||
|
if type == RTU_TYPES.BOILER_VALVE then
|
||||||
|
msg = util.c(msg, "#", idx, " boiler")
|
||||||
|
elseif type == RTU_TYPES.TURBINE_VALVE then
|
||||||
|
msg = util.c(msg, "#", idx, " turbine")
|
||||||
|
elseif type == RTU_TYPES.DYNAMIC_VALVE then
|
||||||
|
msg = msg .. "dynamic tank"
|
||||||
|
elseif type == RTU_TYPES.ENV_DETECTOR then
|
||||||
|
msg = util.c(msg, "#", idx, " env. detector")
|
||||||
|
else
|
||||||
|
msg = msg .. " ? (error)"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
pgi.create_chk_entry(unit, fail_code, msg)
|
||||||
|
end
|
||||||
|
|
||||||
|
return fail_code, fail_str
|
||||||
|
end
|
||||||
|
|
||||||
-- initialize svsessions
|
-- initialize svsessions
|
||||||
---@param nic nic network interface device
|
---@param nic nic network interface device
|
||||||
---@param fp_ok boolean front panel active
|
---@param fp_ok boolean front panel active
|
||||||
---@param config svr_config supervisor configuration
|
---@param config svr_config supervisor configuration
|
||||||
---@param cooling_conf sv_cooling_conf cooling configuration definition
|
---@param facility facility
|
||||||
function svsessions.init(nic, fp_ok, config, cooling_conf)
|
function svsessions.init(nic, fp_ok, config, facility)
|
||||||
self.nic = nic
|
self.nic = nic
|
||||||
self.fp_ok = fp_ok
|
self.fp_ok = fp_ok
|
||||||
self.config = config
|
self.config = config
|
||||||
self.facility = facility.new(config, cooling_conf)
|
self.facility = facility
|
||||||
|
|
||||||
|
-- initialize connection tracking table by setting all expected devices to true
|
||||||
|
-- if connections are missing, missing entries will then be created on the next update
|
||||||
|
|
||||||
|
self.dev_dbg.connected = { induction = true, sps = true, tanks = {}, units = {} }
|
||||||
|
|
||||||
|
local cool_conf = facility.get_cooling_conf()
|
||||||
|
|
||||||
|
for i = 1, #cool_conf.fac_tank_list do
|
||||||
|
if cool_conf.fac_tank_list[i] == 2 then
|
||||||
|
table.insert(self.dev_dbg.connected.tanks, true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = 1, config.UnitCount do
|
||||||
|
local r_cool = cool_conf.r_cool[i]
|
||||||
|
local conns = { boilers = {}, turbines = {}, tanks = {} }
|
||||||
|
|
||||||
|
for b = 1, r_cool.BoilerCount do conns.boilers[b] = true end
|
||||||
|
for t = 1, r_cool.TurbineCount do conns.turbines[t] = true end
|
||||||
|
|
||||||
|
if r_cool.TankConnection and cool_conf.fac_tank_defs[i] == 1 then
|
||||||
|
conns.tanks[1] = true
|
||||||
|
end
|
||||||
|
|
||||||
|
self.dev_dbg.connected.units[i] = conns
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- find an RTU session by the computer ID
|
-- find an RTU session by the computer ID
|
||||||
@@ -466,6 +641,9 @@ function svsessions.iterate_all()
|
|||||||
|
|
||||||
-- iterate units
|
-- iterate units
|
||||||
self.facility.update_units()
|
self.facility.update_units()
|
||||||
|
|
||||||
|
-- update tracking of bad RTU IDs and missing devices
|
||||||
|
_update_dev_dbg()
|
||||||
end
|
end
|
||||||
|
|
||||||
-- delete all closed sessions
|
-- delete all closed sessions
|
||||||
@@ -482,4 +660,6 @@ function svsessions.close_all()
|
|||||||
svsessions.free_all_closed()
|
svsessions.free_all_closed()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
return svsessions
|
return svsessions
|
||||||
|
|||||||
@@ -16,12 +16,13 @@ local core = require("graphics.core")
|
|||||||
|
|
||||||
local configure = require("supervisor.configure")
|
local configure = require("supervisor.configure")
|
||||||
local databus = require("supervisor.databus")
|
local databus = require("supervisor.databus")
|
||||||
|
local facility = require("supervisor.facility")
|
||||||
local renderer = require("supervisor.renderer")
|
local renderer = require("supervisor.renderer")
|
||||||
local supervisor = require("supervisor.supervisor")
|
local supervisor = require("supervisor.supervisor")
|
||||||
|
|
||||||
local svsessions = require("supervisor.session.svsessions")
|
local svsessions = require("supervisor.session.svsessions")
|
||||||
|
|
||||||
local SUPERVISOR_VERSION = "v1.4.0"
|
local SUPERVISOR_VERSION = "v1.5.2"
|
||||||
|
|
||||||
local println = util.println
|
local println = util.println
|
||||||
local println_ts = util.println_ts
|
local println_ts = util.println_ts
|
||||||
@@ -129,9 +130,12 @@ local function main()
|
|||||||
println_ts = function (_) end
|
println_ts = function (_) end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- create facility and unit objects
|
||||||
|
local sv_facility = facility.new(config)
|
||||||
|
|
||||||
-- create network interface then setup comms
|
-- create network interface then setup comms
|
||||||
local nic = network.nic(modem)
|
local nic = network.nic(modem)
|
||||||
local superv_comms = supervisor.comms(SUPERVISOR_VERSION, nic, fp_ok)
|
local superv_comms = supervisor.comms(SUPERVISOR_VERSION, nic, fp_ok, sv_facility)
|
||||||
|
|
||||||
-- base loop clock (6.67Hz, 3 ticks)
|
-- base loop clock (6.67Hz, 3 ticks)
|
||||||
local MAIN_CLOCK = 0.15
|
local MAIN_CLOCK = 0.15
|
||||||
|
|||||||
@@ -102,14 +102,12 @@ end
|
|||||||
---@param _version string supervisor version
|
---@param _version string supervisor version
|
||||||
---@param nic nic network interface device
|
---@param nic nic network interface device
|
||||||
---@param fp_ok boolean if the front panel UI is running
|
---@param fp_ok boolean if the front panel UI is running
|
||||||
|
---@param facility facility facility instance
|
||||||
---@diagnostic disable-next-line: unused-local
|
---@diagnostic disable-next-line: unused-local
|
||||||
function supervisor.comms(_version, nic, fp_ok)
|
function supervisor.comms(_version, nic, fp_ok, facility)
|
||||||
-- print a log message to the terminal as long as the UI isn't running
|
-- print a log message to the terminal as long as the UI isn't running
|
||||||
local function println(message) if not fp_ok then util.println_ts(message) end end
|
local function println(message) if not fp_ok then util.println_ts(message) end end
|
||||||
|
|
||||||
---@class sv_cooling_conf
|
|
||||||
local cooling_conf = { r_cool = config.CoolingConfig, fac_tank_mode = config.FacilityTankMode, fac_tank_defs = config.FacilityTankDefs }
|
|
||||||
|
|
||||||
local self = {
|
local self = {
|
||||||
last_est_acks = {}
|
last_est_acks = {}
|
||||||
}
|
}
|
||||||
@@ -122,8 +120,8 @@ function supervisor.comms(_version, nic, fp_ok)
|
|||||||
nic.closeAll()
|
nic.closeAll()
|
||||||
nic.open(config.SVR_Channel)
|
nic.open(config.SVR_Channel)
|
||||||
|
|
||||||
-- pass modem, status, and config data to svsessions
|
-- pass system data and objects to svsessions
|
||||||
svsessions.init(nic, fp_ok, config, cooling_conf)
|
svsessions.init(nic, fp_ok, config, facility)
|
||||||
|
|
||||||
-- send an establish request response
|
-- send an establish request response
|
||||||
---@param packet scada_packet
|
---@param packet scada_packet
|
||||||
@@ -373,7 +371,7 @@ function supervisor.comms(_version, nic, fp_ok)
|
|||||||
println(util.c("CRD (", firmware_v, ") [@", src_addr, "] \xbb connected"))
|
println(util.c("CRD (", firmware_v, ") [@", src_addr, "] \xbb connected"))
|
||||||
log.info(util.c("CRD_ESTABLISH: coordinator (", firmware_v, ") [@", src_addr, "] connected with session ID ", s_id))
|
log.info(util.c("CRD_ESTABLISH: coordinator (", firmware_v, ") [@", src_addr, "] connected with session ID ", s_id))
|
||||||
|
|
||||||
_send_establish(packet.scada_frame, ESTABLISH_ACK.ALLOW, { config.UnitCount, cooling_conf })
|
_send_establish(packet.scada_frame, ESTABLISH_ACK.ALLOW, { config.UnitCount, facility.get_cooling_conf() })
|
||||||
else
|
else
|
||||||
if last_ack ~= ESTABLISH_ACK.COLLISION then
|
if last_ack ~= ESTABLISH_ACK.COLLISION then
|
||||||
log.info("CRD_ESTABLISH: denied new coordinator [@" .. src_addr .. "] due to already being connected to another coordinator")
|
log.info("CRD_ESTABLISH: denied new coordinator [@" .. src_addr .. "] due to already being connected to another coordinator")
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
local log = require("scada-common.log")
|
local log = require("scada-common.log")
|
||||||
local rsio = require("scada-common.rsio")
|
local rsio = require("scada-common.rsio")
|
||||||
local types = require("scada-common.types")
|
local types = require("scada-common.types")
|
||||||
local util = require("scada-common.util")
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
local logic = require("supervisor.unitlogic")
|
local logic = require("supervisor.unitlogic")
|
||||||
|
|
||||||
local plc = require("supervisor.session.plc")
|
local plc = require("supervisor.session.plc")
|
||||||
local rsctl = require("supervisor.session.rsctl")
|
local rsctl = require("supervisor.session.rsctl")
|
||||||
|
local svsessions = require("supervisor.session.svsessions")
|
||||||
|
|
||||||
local WASTE_MODE = types.WASTE_MODE
|
local WASTE_MODE = types.WASTE_MODE
|
||||||
local WASTE = types.WASTE_PRODUCT
|
local WASTE = types.WASTE_PRODUCT
|
||||||
@@ -14,6 +15,7 @@ local ALARM = types.ALARM
|
|||||||
local PRIO = types.ALARM_PRIORITY
|
local PRIO = types.ALARM_PRIORITY
|
||||||
local ALARM_STATE = types.ALARM_STATE
|
local ALARM_STATE = types.ALARM_STATE
|
||||||
local TRI_FAIL = types.TRI_FAIL
|
local TRI_FAIL = types.TRI_FAIL
|
||||||
|
local RTU_ID_FAIL = types.RTU_ID_FAIL
|
||||||
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
|
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
|
||||||
|
|
||||||
local PLC_S_CMDS = plc.PLC_S_CMDS
|
local PLC_S_CMDS = plc.PLC_S_CMDS
|
||||||
@@ -68,6 +70,8 @@ function unit.new(reactor_id, num_boilers, num_turbines, ext_idle)
|
|||||||
-- time (ms) to idle for auto idling
|
-- time (ms) to idle for auto idling
|
||||||
local IDLE_TIME = util.trinary(ext_idle, 60000, 10000)
|
local IDLE_TIME = util.trinary(ext_idle, 60000, 10000)
|
||||||
|
|
||||||
|
local log_tag = "UNIT " .. reactor_id .. ": "
|
||||||
|
|
||||||
---@class _unit_self
|
---@class _unit_self
|
||||||
local self = {
|
local self = {
|
||||||
r_id = reactor_id,
|
r_id = reactor_id,
|
||||||
@@ -264,7 +268,7 @@ function unit.new(reactor_id, num_boilers, num_turbines, ext_idle)
|
|||||||
table.insert(self.db.annunciator.TurbineOverSpeed, false)
|
table.insert(self.db.annunciator.TurbineOverSpeed, false)
|
||||||
table.insert(self.db.annunciator.GeneratorTrip, false)
|
table.insert(self.db.annunciator.GeneratorTrip, false)
|
||||||
table.insert(self.db.annunciator.TurbineTrip, false)
|
table.insert(self.db.annunciator.TurbineTrip, false)
|
||||||
table.insert(self.turbine_stability_data, { time_state = 0, time_tanks = 0, rotation = 1 })
|
table.insert(self.turbine_stability_data, { time_state = 0, time_tanks = 0, rotation = 1, input_rate = 0 })
|
||||||
end
|
end
|
||||||
|
|
||||||
-- PRIVATE FUNCTIONS --
|
-- PRIVATE FUNCTIONS --
|
||||||
@@ -420,6 +424,8 @@ function unit.new(reactor_id, num_boilers, num_turbines, ext_idle)
|
|||||||
self.plc_s = plc_session
|
self.plc_s = plc_session
|
||||||
self.plc_i = plc_session.instance
|
self.plc_i = plc_session.instance
|
||||||
|
|
||||||
|
log.debug(util.c(log_tag, "linked PLC [", plc_session.s_addr, ":", plc_session.r_chan, "]"))
|
||||||
|
|
||||||
-- reset deltas
|
-- reset deltas
|
||||||
_reset_dt(DT_KEYS.ReactorTemp)
|
_reset_dt(DT_KEYS.ReactorTemp)
|
||||||
_reset_dt(DT_KEYS.ReactorFuel)
|
_reset_dt(DT_KEYS.ReactorFuel)
|
||||||
@@ -432,6 +438,7 @@ function unit.new(reactor_id, num_boilers, num_turbines, ext_idle)
|
|||||||
---@param rs_unit unit_session
|
---@param rs_unit unit_session
|
||||||
function public.add_redstone(rs_unit)
|
function public.add_redstone(rs_unit)
|
||||||
table.insert(self.redstone, rs_unit)
|
table.insert(self.redstone, rs_unit)
|
||||||
|
log.debug(util.c(log_tag, "linked redstone [", rs_unit.get_unit_id(), "@", rs_unit.get_session_id(), "]"))
|
||||||
|
|
||||||
-- send or re-send waste settings
|
-- send or re-send waste settings
|
||||||
_set_waste_valves(self.waste_product)
|
_set_waste_valves(self.waste_product)
|
||||||
@@ -441,42 +448,61 @@ function unit.new(reactor_id, num_boilers, num_turbines, ext_idle)
|
|||||||
---@param turbine unit_session
|
---@param turbine unit_session
|
||||||
---@return boolean linked turbine accepted to associated device slot
|
---@return boolean linked turbine accepted to associated device slot
|
||||||
function public.add_turbine(turbine)
|
function public.add_turbine(turbine)
|
||||||
if #self.turbines < num_turbines and turbine.get_device_idx() <= num_turbines then
|
local fail_code, fail_str = svsessions.check_rtu_id(turbine, self.turbines, num_turbines)
|
||||||
|
local ok = fail_code == RTU_ID_FAIL.OK
|
||||||
|
|
||||||
|
if ok then
|
||||||
table.insert(self.turbines, turbine)
|
table.insert(self.turbines, turbine)
|
||||||
|
log.debug(util.c(log_tag, "linked turbine #", turbine.get_device_idx(), " [", turbine.get_unit_id(), "@", turbine.get_session_id(), "]"))
|
||||||
|
|
||||||
-- reset deltas
|
-- reset deltas
|
||||||
_reset_dt(DT_KEYS.TurbineSteam .. turbine.get_device_idx())
|
_reset_dt(DT_KEYS.TurbineSteam .. turbine.get_device_idx())
|
||||||
_reset_dt(DT_KEYS.TurbinePower .. turbine.get_device_idx())
|
_reset_dt(DT_KEYS.TurbinePower .. turbine.get_device_idx())
|
||||||
|
else
|
||||||
|
log.warning(util.c(log_tag, "rejected turbine linking due to failure code ", fail_code, " (", fail_str, ")"))
|
||||||
|
end
|
||||||
|
|
||||||
return true
|
return ok
|
||||||
else return false end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- link a boiler RTU session
|
-- link a boiler RTU session
|
||||||
---@param boiler unit_session
|
---@param boiler unit_session
|
||||||
---@return boolean linked boiler accepted to associated device slot
|
---@return boolean linked boiler accepted to associated device slot
|
||||||
function public.add_boiler(boiler)
|
function public.add_boiler(boiler)
|
||||||
if #self.boilers < num_boilers and boiler.get_device_idx() <= num_boilers then
|
local fail_code, fail_str = svsessions.check_rtu_id(boiler, self.boilers, num_boilers)
|
||||||
|
local ok = fail_code == RTU_ID_FAIL.OK
|
||||||
|
|
||||||
|
if ok then
|
||||||
table.insert(self.boilers, boiler)
|
table.insert(self.boilers, boiler)
|
||||||
|
log.debug(util.c(log_tag, "linked boiler #", boiler.get_device_idx(), " [", boiler.get_unit_id(), "@", boiler.get_session_id(), "]"))
|
||||||
|
|
||||||
-- reset deltas
|
-- reset deltas
|
||||||
_reset_dt(DT_KEYS.BoilerWater .. boiler.get_device_idx())
|
_reset_dt(DT_KEYS.BoilerWater .. boiler.get_device_idx())
|
||||||
_reset_dt(DT_KEYS.BoilerSteam .. boiler.get_device_idx())
|
_reset_dt(DT_KEYS.BoilerSteam .. boiler.get_device_idx())
|
||||||
_reset_dt(DT_KEYS.BoilerCCool .. boiler.get_device_idx())
|
_reset_dt(DT_KEYS.BoilerCCool .. boiler.get_device_idx())
|
||||||
_reset_dt(DT_KEYS.BoilerHCool .. boiler.get_device_idx())
|
_reset_dt(DT_KEYS.BoilerHCool .. boiler.get_device_idx())
|
||||||
|
else
|
||||||
|
log.warning(util.c(log_tag, "rejected boiler linking due to failure code ", fail_code, " (", fail_str, ")"))
|
||||||
|
end
|
||||||
|
|
||||||
return true
|
return ok
|
||||||
else return false end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- link a dynamic tank RTU session
|
-- link a dynamic tank RTU session
|
||||||
---@param dynamic_tank unit_session
|
---@param dynamic_tank unit_session
|
||||||
---@return boolean linked dynamic tank accepted (max 1)
|
---@return boolean linked dynamic tank accepted (max 1)
|
||||||
function public.add_tank(dynamic_tank)
|
function public.add_tank(dynamic_tank)
|
||||||
if #self.tanks == 0 then
|
local fail_code, fail_str = svsessions.check_rtu_id(dynamic_tank, self.tanks, 1)
|
||||||
|
local ok = fail_code == RTU_ID_FAIL.OK
|
||||||
|
|
||||||
|
if ok then
|
||||||
table.insert(self.tanks, dynamic_tank)
|
table.insert(self.tanks, dynamic_tank)
|
||||||
return true
|
log.debug(util.c(log_tag, "linked dynamic tank [", dynamic_tank.get_unit_id(), "@", dynamic_tank.get_session_id(), "]"))
|
||||||
else return false end
|
else
|
||||||
|
log.warning(util.c(log_tag, "rejected dynamic tank linking due to failure code ", fail_code, " (", fail_str, ")"))
|
||||||
|
end
|
||||||
|
|
||||||
|
return ok
|
||||||
end
|
end
|
||||||
|
|
||||||
-- link a solar neutron activator RTU session
|
-- link a solar neutron activator RTU session
|
||||||
@@ -485,12 +511,19 @@ function unit.new(reactor_id, num_boilers, num_turbines, ext_idle)
|
|||||||
|
|
||||||
-- link an environment detector RTU session
|
-- link an environment detector RTU session
|
||||||
---@param envd unit_session
|
---@param envd unit_session
|
||||||
---@return boolean linked environment detector accepted (max 1)
|
---@return boolean linked environment detector accepted
|
||||||
function public.add_envd(envd)
|
function public.add_envd(envd)
|
||||||
if #self.envd == 0 then
|
local fail_code, fail_str = svsessions.check_rtu_id(envd, self.envd, 99)
|
||||||
|
local ok = fail_code == RTU_ID_FAIL.OK
|
||||||
|
|
||||||
|
if ok then
|
||||||
table.insert(self.envd, envd)
|
table.insert(self.envd, envd)
|
||||||
return true
|
log.debug(util.c(log_tag, "linked environment detector #", envd.get_device_idx(), " [", envd.get_unit_id(), "@", envd.get_session_id(), "]"))
|
||||||
else return false end
|
else
|
||||||
|
log.warning(util.c(log_tag, "rejected environment detector linking due to failure code ", fail_code, " (", fail_str, ")"))
|
||||||
|
end
|
||||||
|
|
||||||
|
return ok
|
||||||
end
|
end
|
||||||
|
|
||||||
-- purge devices associated with the given RTU session ID
|
-- purge devices associated with the given RTU session ID
|
||||||
@@ -512,7 +545,7 @@ function unit.new(reactor_id, num_boilers, num_turbines, ext_idle)
|
|||||||
self.db.control.br100 = 0
|
self.db.control.br100 = 0
|
||||||
end
|
end
|
||||||
|
|
||||||
-- unlink RTU unit sessions if they are closed
|
-- unlink RTU sessions if they are closed
|
||||||
for _, v in pairs(self.rtu_list) do util.filter_table(v, function (u) return u.is_connected() end) end
|
for _, v in pairs(self.rtu_list) do util.filter_table(v, function (u) return u.is_connected() end) end
|
||||||
|
|
||||||
-- update degraded state for auto control
|
-- update degraded state for auto control
|
||||||
@@ -547,7 +580,7 @@ function unit.new(reactor_id, num_boilers, num_turbines, ext_idle)
|
|||||||
|
|
||||||
-- stop idling when completed
|
-- stop idling when completed
|
||||||
if self.auto_idling and (((util.time_ms() - self.auto_idle_start) > IDLE_TIME) or not self.auto_idle) then
|
if self.auto_idling and (((util.time_ms() - self.auto_idle_start) > IDLE_TIME) or not self.auto_idle) then
|
||||||
log.info(util.c("UNIT ", self.r_id, ": completed idling period"))
|
log.info(util.c(log_tag, "completed idling period"))
|
||||||
self.auto_idling = false
|
self.auto_idling = false
|
||||||
self.plc_i.auto_set_burn(0, false)
|
self.plc_i.auto_set_burn(0, false)
|
||||||
end
|
end
|
||||||
@@ -584,7 +617,7 @@ function unit.new(reactor_id, num_boilers, num_turbines, ext_idle)
|
|||||||
function public.auto_engage()
|
function public.auto_engage()
|
||||||
self.auto_engaged = true
|
self.auto_engaged = true
|
||||||
if self.plc_i ~= nil then
|
if self.plc_i ~= nil then
|
||||||
log.debug(util.c("UNIT ", self.r_id, ": engaged auto control"))
|
log.debug(util.c(log_tag, "engaged auto control"))
|
||||||
self.plc_i.auto_lock(true)
|
self.plc_i.auto_lock(true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -593,7 +626,7 @@ function unit.new(reactor_id, num_boilers, num_turbines, ext_idle)
|
|||||||
function public.auto_disengage()
|
function public.auto_disengage()
|
||||||
self.auto_engaged = false
|
self.auto_engaged = false
|
||||||
if self.plc_i ~= nil then
|
if self.plc_i ~= nil then
|
||||||
log.debug(util.c("UNIT ", self.r_id, ": disengaged auto control"))
|
log.debug(util.c(log_tag, "disengaged auto control"))
|
||||||
self.plc_i.auto_lock(false)
|
self.plc_i.auto_lock(false)
|
||||||
self.db.control.br100 = 0
|
self.db.control.br100 = 0
|
||||||
end
|
end
|
||||||
@@ -610,7 +643,7 @@ function unit.new(reactor_id, num_boilers, num_turbines, ext_idle)
|
|||||||
end
|
end
|
||||||
|
|
||||||
if idle ~= self.auto_idle then
|
if idle ~= self.auto_idle then
|
||||||
log.debug(util.c("UNIT ", self.r_id, ": idling mode changed to ", idle))
|
log.debug(util.c(log_tag, "idling mode changed to ", idle))
|
||||||
end
|
end
|
||||||
|
|
||||||
self.auto_idle = idle
|
self.auto_idle = idle
|
||||||
@@ -623,7 +656,7 @@ function unit.new(reactor_id, num_boilers, num_turbines, ext_idle)
|
|||||||
function public.auto_get_effective_limit()
|
function public.auto_get_effective_limit()
|
||||||
local ctrl = self.db.control
|
local ctrl = self.db.control
|
||||||
if (not ctrl.ready) or ctrl.degraded or self.plc_cache.rps_trip then
|
if (not ctrl.ready) or ctrl.degraded or self.plc_cache.rps_trip then
|
||||||
-- log.debug(util.c("UNIT ", self.r_id, ": effective limit is zero! ready[", ctrl.ready, "] degraded[", ctrl.degraded, "] rps_trip[", self.plc_cache.rps_trip, "]"))
|
-- log.debug(util.c(log_tag, "effective limit is zero! ready[", ctrl.ready, "] degraded[", ctrl.degraded, "] rps_trip[", self.plc_cache.rps_trip, "]"))
|
||||||
ctrl.br100 = 0
|
ctrl.br100 = 0
|
||||||
return 0
|
return 0
|
||||||
else return ctrl.lim_br100 end
|
else return ctrl.lim_br100 end
|
||||||
@@ -634,7 +667,7 @@ function unit.new(reactor_id, num_boilers, num_turbines, ext_idle)
|
|||||||
function public.auto_commit_br100(ramp)
|
function public.auto_commit_br100(ramp)
|
||||||
if self.auto_engaged then
|
if self.auto_engaged then
|
||||||
if self.plc_i ~= nil then
|
if self.plc_i ~= nil then
|
||||||
log.debug(util.c("UNIT ", self.r_id, ": commit br100 of ", self.db.control.br100, " with ramp set to ", ramp))
|
log.debug(util.c(log_tag, "commit br100 of ", self.db.control.br100, " with ramp set to ", ramp))
|
||||||
|
|
||||||
local rate = self.db.control.br100 / 100
|
local rate = self.db.control.br100 / 100
|
||||||
|
|
||||||
@@ -643,16 +676,16 @@ function unit.new(reactor_id, num_boilers, num_turbines, ext_idle)
|
|||||||
if self.auto_idle_start == 0 then
|
if self.auto_idle_start == 0 then
|
||||||
self.auto_idling = true
|
self.auto_idling = true
|
||||||
self.auto_idle_start = util.time_ms()
|
self.auto_idle_start = util.time_ms()
|
||||||
log.info(util.c("UNIT ", self.r_id, ": started idling at ", IDLE_RATE, " mB/t"))
|
log.info(util.c(log_tag, "started idling at ", IDLE_RATE, " mB/t"))
|
||||||
|
|
||||||
rate = IDLE_RATE
|
rate = IDLE_RATE
|
||||||
elseif (util.time_ms() - self.auto_idle_start) > IDLE_TIME then
|
elseif (util.time_ms() - self.auto_idle_start) > IDLE_TIME then
|
||||||
if self.auto_idling then
|
if self.auto_idling then
|
||||||
self.auto_idling = false
|
self.auto_idling = false
|
||||||
log.info(util.c("UNIT ", self.r_id, ": completed idling period"))
|
log.info(util.c(log_tag, "completed idling period"))
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
log.debug(util.c("UNIT ", self.r_id, ": continuing idle at ", IDLE_RATE, " mB/t"))
|
log.debug(util.c(log_tag, "continuing idle at ", IDLE_RATE, " mB/t"))
|
||||||
|
|
||||||
rate = IDLE_RATE
|
rate = IDLE_RATE
|
||||||
end
|
end
|
||||||
@@ -891,6 +924,29 @@ function unit.new(reactor_id, num_boilers, num_turbines, ext_idle)
|
|||||||
return rate or 0
|
return rate or 0
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- check which RTUs are connected
|
||||||
|
---@nodiscard
|
||||||
|
function public.check_rtu_conns()
|
||||||
|
local conns = {}
|
||||||
|
|
||||||
|
conns.boilers = {}
|
||||||
|
for i = 1, #self.boilers do
|
||||||
|
conns.boilers[self.boilers[i].get_device_idx()] = true
|
||||||
|
end
|
||||||
|
|
||||||
|
conns.turbines = {}
|
||||||
|
for i = 1, #self.turbines do
|
||||||
|
conns.turbines[self.turbines[i].get_device_idx()] = true
|
||||||
|
end
|
||||||
|
|
||||||
|
conns.tanks = {}
|
||||||
|
for i = 1, #self.tanks do
|
||||||
|
conns.tanks[self.tanks[i].get_device_idx()] = true
|
||||||
|
end
|
||||||
|
|
||||||
|
return conns
|
||||||
|
end
|
||||||
|
|
||||||
-- get RTU statuses
|
-- get RTU statuses
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
function public.get_rtu_statuses()
|
function public.get_rtu_statuses()
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ function logic.update_annunciator(self)
|
|||||||
self.turbine_flow_stable = false
|
self.turbine_flow_stable = false
|
||||||
|
|
||||||
for t = 1, self.num_turbines do
|
for t = 1, self.num_turbines do
|
||||||
self.turbine_stability_data[t] = { time_state = 0, time_tanks = 0, rotation = 1 }
|
self.turbine_stability_data[t] = { time_state = 0, time_tanks = 0, rotation = 1, input_rate = 0 }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -317,7 +317,7 @@ function logic.update_annunciator(self)
|
|||||||
|
|
||||||
local last = self.turbine_stability_data[i]
|
local last = self.turbine_stability_data[i]
|
||||||
|
|
||||||
if (not self.turbine_flow_stable) and (turbine.state.steam_input_rate > 0) then
|
if not self.turbine_flow_stable then
|
||||||
local rotation = util.turbine_rotation(turbine)
|
local rotation = util.turbine_rotation(turbine)
|
||||||
local rotation_stable = false
|
local rotation_stable = false
|
||||||
|
|
||||||
@@ -351,13 +351,18 @@ function logic.update_annunciator(self)
|
|||||||
end
|
end
|
||||||
|
|
||||||
turbines_stable = turbines_stable and (rotation_stable or flow_stable)
|
turbines_stable = turbines_stable and (rotation_stable or flow_stable)
|
||||||
else
|
elseif math.abs(turbine.state.steam_input_rate - last.input_rate) > 1 then
|
||||||
|
-- reset to unstable to re-check
|
||||||
last.time_state = 0
|
last.time_state = 0
|
||||||
last.time_tanks = 0
|
last.time_tanks = 0
|
||||||
last.rotation = 1
|
last.rotation = 1
|
||||||
|
|
||||||
turbines_stable = false
|
turbines_stable = false
|
||||||
|
|
||||||
|
log.debug(util.c("UNIT ", self.r_id, ": turbine ", idx, " reset stability (new rate ", turbine.state.steam_input_rate, " != ", last.input_rate," mB/t)"))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
last.input_rate = turbine.state.steam_input_rate
|
||||||
end
|
end
|
||||||
|
|
||||||
self.turbine_flow_stable = self.turbine_flow_stable or turbines_stable
|
self.turbine_flow_stable = self.turbine_flow_stable or turbines_stable
|
||||||
|
|||||||
Reference in New Issue
Block a user