Compare commits
126 Commits
v1.8.10-be
...
v1.8.14-be
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4fe6792804 | ||
|
|
25dc47d520 | ||
|
|
f958b0e3b7 | ||
|
|
f621ff2482 | ||
|
|
eb45ff899b | ||
|
|
e91fd2fcaa | ||
|
|
d35b824458 | ||
|
|
165d1497f8 | ||
|
|
50bf057ca6 | ||
|
|
6f768ef6b3 | ||
|
|
826086951e | ||
|
|
35bf56663f | ||
|
|
7b8cea4a5c | ||
|
|
51d4a22532 | ||
|
|
fb85c2f05b | ||
|
|
712d018806 | ||
|
|
00a8d64a88 | ||
|
|
d9efd5b8d2 | ||
|
|
a786404092 | ||
|
|
1bd03c0b1a | ||
|
|
0d8d7aeb15 | ||
|
|
0b60dc9fa4 | ||
|
|
48b91ac808 | ||
|
|
4c9efc78b1 | ||
|
|
3c5857cd68 | ||
|
|
473b0dd10d | ||
|
|
d2df7db18b | ||
|
|
0ca002316f | ||
|
|
9fe34648c2 | ||
|
|
57dc20692b | ||
|
|
f22d699baf | ||
|
|
23b31e0049 | ||
|
|
2b4309afa7 | ||
|
|
89cd5a07f2 | ||
|
|
99213da760 | ||
|
|
f23f69d441 | ||
|
|
a99b7912f8 | ||
|
|
23ca5fb69e | ||
|
|
c243d064ef | ||
|
|
878c3b92e1 | ||
|
|
2aa52d2e2c | ||
|
|
dfc1ee6497 | ||
|
|
a6a1a61954 | ||
|
|
d0b50c834c | ||
|
|
612a06ba98 | ||
|
|
65d43d55c7 | ||
|
|
4fcd375ee2 | ||
|
|
a22f5562cf | ||
|
|
0365ea5e8a | ||
|
|
da300607f1 | ||
|
|
cc3174ee76 | ||
|
|
98c37caecd | ||
|
|
cc50e4a020 | ||
|
|
f734c4307b | ||
|
|
45573be8c7 | ||
|
|
eab1ffbe03 | ||
|
|
1504247b33 | ||
|
|
92a4277f73 | ||
|
|
31a663e86b | ||
|
|
5c0e2c32ee | ||
|
|
3537c59365 | ||
|
|
1d7104ae74 | ||
|
|
4629e1ba2a | ||
|
|
659644865a | ||
|
|
03d2b3f087 | ||
|
|
bea7b91ff1 | ||
|
|
b94c89f4ec | ||
|
|
55e4e5a68b | ||
|
|
e1ad76a00d | ||
|
|
93e4590947 | ||
|
|
2442e7f972 | ||
|
|
bb2c07963b | ||
|
|
44c6352a8c | ||
|
|
968b0a9122 | ||
|
|
19869416af | ||
|
|
598f6f08af | ||
|
|
ca2d8ab7da | ||
|
|
1c0f61b3e0 | ||
|
|
ecd3575643 | ||
|
|
9dc3a09f4d | ||
|
|
7a5d14d67f | ||
|
|
d6175e5cec | ||
|
|
a00a824a7f | ||
|
|
9393632428 | ||
|
|
886bd0d5d5 | ||
|
|
9c1d83fdfc | ||
|
|
bd88244681 | ||
|
|
8dae632b25 | ||
|
|
89d56d3101 | ||
|
|
1f451ff92a | ||
|
|
9d08b51f84 | ||
|
|
047ff5c203 | ||
|
|
895f768e58 | ||
|
|
b3f29566ea | ||
|
|
c0a5c8d504 | ||
|
|
bbe7b52662 | ||
|
|
d5b166dcc6 | ||
|
|
5d760a0524 | ||
|
|
6c89b3134c | ||
|
|
814043bf04 | ||
|
|
fc7896ebd3 | ||
|
|
48a8eadc55 | ||
|
|
f0f2aadf53 | ||
|
|
ce37a672a3 | ||
|
|
510995b04f | ||
|
|
560061d4ad | ||
|
|
d87e3893f0 | ||
|
|
fc198cd9d2 | ||
|
|
c714e49ad8 | ||
|
|
a318ffb283 | ||
|
|
a677e994d6 | ||
|
|
dbc1f41c5d | ||
|
|
6ef049baa1 | ||
|
|
a4214e8a4f | ||
|
|
45881067df | ||
|
|
c40aa229bf | ||
|
|
51f2bba4d1 | ||
|
|
628a50e1bd | ||
|
|
526a54903e | ||
|
|
e1a632dcc7 | ||
|
|
d5d818e625 | ||
|
|
03de90c3d8 | ||
|
|
99096e0fc9 | ||
|
|
0395aa95b6 | ||
|
|
c624baed2c | ||
|
|
1a40321c0f |
12
README.md
12
README.md
@@ -4,12 +4,11 @@ Configurable ComputerCraft SCADA system for multi-reactor control of Mekanism fi
|
||||

|
||||

|
||||

|
||||

|
||||

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

|
||||

|
||||
|
||||
## Released Component Versions
|
||||
|
||||
@@ -17,6 +16,7 @@ Configurable ComputerCraft SCADA system for multi-reactor control of Mekanism fi
|
||||
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
@@ -46,6 +46,12 @@ You can install this on a ComputerCraft computer using either:
|
||||
* `wget https://raw.githubusercontent.com/MikaylaFischler/cc-mek-scada/main/ccmsi.lua`
|
||||
* `pastebin get sqUN6VUb ccmsi.lua`
|
||||
|
||||
## Contributing
|
||||
|
||||
Please reach out to me via Discord or email (or GitHub in some way) if you are thinking of making any contributions at this time. I started this project as a challenge for myself and have been enjoying having something I can work on in my own way.
|
||||
|
||||
Once this is out of beta I will be more open to contributions, but for now I am hoping to keep them to a minimum as the remaining challenges are ones I am looking forward to solving.
|
||||
|
||||
## [SCADA](https://en.wikipedia.org/wiki/SCADA)
|
||||
> Supervisory control and data acquisition (SCADA) is a control system architecture comprising computers, networked data communications and graphical user interfaces for high-level supervision of machines and processes. It also covers sensors and other devices, such as programmable logic controllers, which interface with process plant or machinery.
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ 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 themes = require("graphics.themes")
|
||||
|
||||
local core = require("graphics.core")
|
||||
|
||||
@@ -24,6 +25,8 @@ 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 println = util.println
|
||||
local tri = util.trinary
|
||||
|
||||
@@ -40,7 +43,9 @@ local RIGHT = core.ALIGN.RIGHT
|
||||
|
||||
-- changes to the config data/format to let the user know
|
||||
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.3.3", { "Added standard with black off state color mode", "Added blue indicator color modes" } }
|
||||
}
|
||||
|
||||
---@class crd_configurator
|
||||
@@ -51,21 +56,7 @@ local style = {}
|
||||
style.root = cpair(colors.black, colors.lightGray)
|
||||
style.header = cpair(colors.white, colors.gray)
|
||||
|
||||
style.colors = {
|
||||
{ c = colors.red, hex = 0xdf4949 },
|
||||
{ c = colors.orange, hex = 0xffb659 },
|
||||
{ c = colors.yellow, hex = 0xfffc79 },
|
||||
{ c = colors.lime, hex = 0x80ff80 },
|
||||
{ c = colors.green, hex = 0x4aee8a },
|
||||
{ c = colors.cyan, hex = 0x34bac8 },
|
||||
{ c = colors.lightBlue, hex = 0x6cc0f2 },
|
||||
{ c = colors.blue, hex = 0x0096ff },
|
||||
{ c = colors.purple, hex = 0xb156ee },
|
||||
{ c = colors.pink, hex = 0xf26ba2 },
|
||||
{ c = colors.magenta, hex = 0xf9488a },
|
||||
{ c = colors.lightGray, hex = 0xcacaca },
|
||||
{ c = colors.gray, hex = 0x575757 }
|
||||
}
|
||||
style.colors = themes.smooth_stone.colors
|
||||
|
||||
local bw_fg_bg = cpair(colors.black, colors.white)
|
||||
local g_lg_fg_bg = cpair(colors.gray, colors.lightGray)
|
||||
@@ -73,6 +64,7 @@ local nav_fg_bg = bw_fg_bg
|
||||
local btn_act_fg_bg = cpair(colors.white, colors.gray)
|
||||
local dis_fg_bg = cpair(colors.lightGray,colors.white)
|
||||
|
||||
---@class _crd_cfg_tool_ctl
|
||||
local tool_ctl = {
|
||||
nic = nil, ---@type nic
|
||||
net_listen = false,
|
||||
@@ -86,8 +78,12 @@ local tool_ctl = {
|
||||
has_config = false,
|
||||
viewing_config = false,
|
||||
importing_legacy = false,
|
||||
jumped_to_color = false,
|
||||
|
||||
view_cfg = nil, ---@type graphics_element
|
||||
color_cfg = nil, ---@type graphics_element
|
||||
color_next = nil, ---@type graphics_element
|
||||
color_apply = nil, ---@type graphics_element
|
||||
settings_apply = nil, ---@type graphics_element
|
||||
|
||||
gen_summary = nil, ---@type function
|
||||
@@ -136,6 +132,9 @@ local tmp_cfg = {
|
||||
LogMode = 0,
|
||||
LogPath = "",
|
||||
LogDebug = false,
|
||||
MainTheme = 1,
|
||||
FrontPanelTheme = 1,
|
||||
ColorMode = 1
|
||||
}
|
||||
|
||||
---@class crd_config
|
||||
@@ -162,7 +161,10 @@ local fields = {
|
||||
{ "AuthKey", "Facility Auth Key" , ""},
|
||||
{ "LogMode", "Log Mode", log.MODE.APPEND },
|
||||
{ "LogPath", "Log Path", "/log.txt" },
|
||||
{ "LogDebug","Log Debug Messages", false }
|
||||
{ "LogDebug", "Log Debug Messages", false },
|
||||
{ "MainTheme", "Main UI Theme", themes.UI_THEME.SMOOTH_STONE },
|
||||
{ "FrontPanelTheme", "Front Panel Theme", themes.FP_THEME.SANDSTONE },
|
||||
{ "ColorMode", "Color Mode", themes.COLOR_MODE.STANDARD }
|
||||
}
|
||||
|
||||
-- check if a value is an integer within a range (inclusive)
|
||||
@@ -313,10 +315,11 @@ local function config_view(display)
|
||||
local spkr_cfg = Div{parent=root_pane_div,x=1,y=1}
|
||||
local crd_cfg = Div{parent=root_pane_div,x=1,y=1}
|
||||
local log_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 changelog = Div{parent=root_pane_div,x=1,y=1}
|
||||
|
||||
local main_pane = MultiPane{parent=root_pane_div,x=1,y=1,panes={main_page,net_cfg,fac_cfg,mon_cfg,spkr_cfg,crd_cfg,log_cfg,summary,changelog}}
|
||||
local main_pane = MultiPane{parent=root_pane_div,x=1,y=1,panes={main_page,net_cfg,fac_cfg,mon_cfg,spkr_cfg,crd_cfg,log_cfg,clr_cfg,summary,changelog}}
|
||||
|
||||
-- Main Page
|
||||
|
||||
@@ -337,7 +340,7 @@ local function config_view(display)
|
||||
tool_ctl.viewing_config = true
|
||||
tool_ctl.gen_summary(settings_cfg)
|
||||
tool_ctl.settings_apply.hide(true)
|
||||
main_pane.set_value(8)
|
||||
main_pane.set_value(9)
|
||||
end
|
||||
|
||||
if fs.exists("/coordinator/config.lua") then
|
||||
@@ -348,10 +351,21 @@ local function config_view(display)
|
||||
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=dis_fg_bg}
|
||||
|
||||
if not tool_ctl.has_config then tool_ctl.view_cfg.disable() end
|
||||
local function jump_color()
|
||||
tool_ctl.jumped_to_color = true
|
||||
tool_ctl.color_next.hide(true)
|
||||
tool_ctl.color_apply.show()
|
||||
main_pane.set_value(8)
|
||||
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=39,y=17,min_width=12,text="Change Log",callback=function()main_pane.set_value(9)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
tool_ctl.color_cfg = PushButton{parent=main_page,x=23,y=17,min_width=15,text="Color Options",callback=jump_color,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)}
|
||||
PushButton{parent=main_page,x=39,y=17,min_width=12,text="Change Log",callback=function()main_pane.set_value(10)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
if not tool_ctl.has_config then
|
||||
tool_ctl.view_cfg.disable()
|
||||
tool_ctl.color_cfg.disable()
|
||||
end
|
||||
|
||||
--#region Network
|
||||
|
||||
@@ -697,7 +711,7 @@ local function config_view(display)
|
||||
mon_pane.set_value(1)
|
||||
end
|
||||
|
||||
PushButton{parent=mon_c_4,x=1,y=14,text="\x1b Back",callback=back_from_legacy,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=mon_c_4,x=44,y=14,min_width=6,text="Done",callback=back_from_legacy,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
--#endregion
|
||||
|
||||
@@ -734,8 +748,6 @@ local function config_view(display)
|
||||
|
||||
local crd_c_1 = Div{parent=crd_cfg,x=2,y=4,width=49}
|
||||
|
||||
local crd_pane = MultiPane{parent=crd_cfg,x=1,y=4,panes={crd_c_1}}
|
||||
|
||||
TextBox{parent=crd_cfg,x=1,y=2,height=1,text=" Coordinator UI Configuration",fg_bg=cpair(colors.black,colors.lime)}
|
||||
|
||||
TextBox{parent=crd_c_1,x=1,y=1,height=3,text="Configure the UI interface options below if you wish to customize formats."}
|
||||
@@ -782,10 +794,8 @@ local function config_view(display)
|
||||
tmp_cfg.LogMode = mode.get_value() - 1
|
||||
tmp_cfg.LogPath = path.get_value()
|
||||
tmp_cfg.LogDebug = en_dbg.get_value()
|
||||
tool_ctl.gen_summary(tmp_cfg)
|
||||
tool_ctl.viewing_config = false
|
||||
tool_ctl.importing_legacy = false
|
||||
tool_ctl.settings_apply.show()
|
||||
tool_ctl.color_apply.hide(true)
|
||||
tool_ctl.color_next.show()
|
||||
main_pane.set_value(8)
|
||||
else path_err.show() end
|
||||
end
|
||||
@@ -795,6 +805,121 @@ local function config_view(display)
|
||||
|
||||
--#endregion
|
||||
|
||||
--#region Color Options
|
||||
|
||||
local clr_c_1 = Div{parent=clr_cfg,x=2,y=4,width=49}
|
||||
local clr_c_2 = Div{parent=clr_cfg,x=2,y=4,width=49}
|
||||
local clr_c_3 = Div{parent=clr_cfg,x=2,y=4,width=49}
|
||||
local clr_c_4 = Div{parent=clr_cfg,x=2,y=4,width=49}
|
||||
|
||||
local clr_pane = MultiPane{parent=clr_cfg,x=1,y=4,panes={clr_c_1,clr_c_2,clr_c_3,clr_c_4}}
|
||||
|
||||
TextBox{parent=clr_cfg,x=1,y=2,height=1,text=" Color Configuration",fg_bg=cpair(colors.black,colors.magenta)}
|
||||
|
||||
TextBox{parent=clr_c_1,x=1,y=1,height=2,text="Here you can select the color themes for the different UI displays."}
|
||||
TextBox{parent=clr_c_1,x=1,y=4,height=2,text="Click 'Accessibility' below to access colorblind assistive options.",fg_bg=g_lg_fg_bg}
|
||||
|
||||
TextBox{parent=clr_c_1,x=1,y=7,height=1,text="Main UI Theme"}
|
||||
local main_theme = RadioButton{parent=clr_c_1,x=1,y=8,default=ini_cfg.MainTheme,options=themes.UI_THEME_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.magenta}
|
||||
|
||||
TextBox{parent=clr_c_1,x=18,y=7,height=1,text="Front Panel Theme"}
|
||||
local fp_theme = RadioButton{parent=clr_c_1,x=18,y=8,default=ini_cfg.FrontPanelTheme,options=themes.FP_THEME_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.magenta}
|
||||
|
||||
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,height=1,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,height=1,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, 7))
|
||||
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.MainTheme = main_theme.get_value()
|
||||
tmp_cfg.FrontPanelTheme = fp_theme.get_value()
|
||||
tmp_cfg.ColorMode = c_mode.get_value()
|
||||
|
||||
if tool_ctl.jumped_to_color then
|
||||
settings.set("MainTheme", tmp_cfg.MainTheme)
|
||||
settings.set("FrontPanelTheme", tmp_cfg.FrontPanelTheme)
|
||||
settings.set("ColorMode", tmp_cfg.ColorMode)
|
||||
|
||||
if settings.save("/coordinator.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(9)
|
||||
end
|
||||
end
|
||||
|
||||
PushButton{parent=clr_c_1,x=1,y=14,text="\x1b Back",callback=back_from_colors,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=clr_c_1,x=8,y=14,min_width=15,text="Accessibility",callback=show_access,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
tool_ctl.color_next = PushButton{parent=clr_c_1,x=44,y=14,text="Next \x1a",callback=submit_colors,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
tool_ctl.color_apply = PushButton{parent=clr_c_1,x=43,y=14,min_width=7,text="Apply",callback=submit_colors,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
tool_ctl.color_apply.hide(true)
|
||||
|
||||
local function c_go_home()
|
||||
main_pane.set_value(1)
|
||||
clr_pane.set_value(1)
|
||||
end
|
||||
|
||||
TextBox{parent=clr_c_3,x=1,y=1,height=1,text="Settings saved!"}
|
||||
PushButton{parent=clr_c_3,x=1,y=14,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray)}
|
||||
PushButton{parent=clr_c_3,x=44,y=14,min_width=6,text="Home",callback=c_go_home,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
TextBox{parent=clr_c_4,x=1,y=1,height=5,text="Failed to save the settings file.\n\nThere may not be enough space for the modification or server file permissions may be denying writes."}
|
||||
PushButton{parent=clr_c_4,x=1,y=14,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray)}
|
||||
PushButton{parent=clr_c_4,x=44,y=14,min_width=6,text="Home",callback=c_go_home,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
--#endregion
|
||||
|
||||
--#region Summary and Saving
|
||||
|
||||
local sum_c_1 = Div{parent=summary,x=2,y=4,width=49}
|
||||
@@ -815,7 +940,7 @@ local function config_view(display)
|
||||
tool_ctl.importing_legacy = false
|
||||
tool_ctl.settings_apply.show()
|
||||
else
|
||||
main_pane.set_value(7)
|
||||
main_pane.set_value(8)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -846,12 +971,16 @@ local function config_view(display)
|
||||
try_set(mode, ini_cfg.LogMode)
|
||||
try_set(path, ini_cfg.LogPath)
|
||||
try_set(en_dbg, ini_cfg.LogDebug)
|
||||
try_set(main_theme, ini_cfg.MainTheme)
|
||||
try_set(fp_theme, ini_cfg.FrontPanelTheme)
|
||||
try_set(c_mode, ini_cfg.ColorMode)
|
||||
|
||||
preset_monitor_fields()
|
||||
|
||||
tool_ctl.gen_mon_list()
|
||||
|
||||
tool_ctl.view_cfg.enable()
|
||||
tool_ctl.color_cfg.enable()
|
||||
|
||||
if tool_ctl.importing_legacy then
|
||||
tool_ctl.importing_legacy = false
|
||||
@@ -875,7 +1004,7 @@ local function config_view(display)
|
||||
net_pane.set_value(1)
|
||||
fac_pane.set_value(1)
|
||||
mon_pane.set_value(1)
|
||||
crd_pane.set_value(1)
|
||||
clr_pane.set_value(1)
|
||||
sum_pane.set_value(1)
|
||||
end
|
||||
|
||||
@@ -972,7 +1101,7 @@ local function config_view(display)
|
||||
|
||||
tool_ctl.gen_summary(tmp_cfg)
|
||||
sum_pane.set_value(1)
|
||||
main_pane.set_value(8)
|
||||
main_pane.set_value(9)
|
||||
tool_ctl.importing_legacy = true
|
||||
end
|
||||
|
||||
@@ -1117,6 +1246,10 @@ local function config_view(display)
|
||||
function tool_ctl.gen_mon_list()
|
||||
mon_list.remove_all()
|
||||
|
||||
local missing = { main = tmp_cfg.MainDisplay ~= nil, flow = tmp_cfg.FlowDisplay ~= nil, unit = {} }
|
||||
for i = 1, tmp_cfg.UnitCount do missing.unit[i] = tmp_cfg.UnitDisplays[i] ~= nil end
|
||||
|
||||
-- list connected monitors
|
||||
local monitors = ppm.get_monitor_list()
|
||||
for iface, device in pairs(monitors) do
|
||||
local dev = device.dev
|
||||
@@ -1136,11 +1269,14 @@ local function config_view(display)
|
||||
|
||||
if tmp_cfg.MainDisplay == iface then
|
||||
assignment = "Main"
|
||||
missing.main = false
|
||||
elseif tmp_cfg.FlowDisplay == iface then
|
||||
assignment = "Flow"
|
||||
missing.flow = false
|
||||
else
|
||||
for i = 1, tmp_cfg.UnitCount do
|
||||
if tmp_cfg.UnitDisplays[i] == iface then
|
||||
missing.unit[i] = false
|
||||
assignment = "Unit " .. i
|
||||
break
|
||||
end
|
||||
@@ -1165,6 +1301,31 @@ local function config_view(display)
|
||||
|
||||
if assignment == "Unused" then unset.disable() end
|
||||
end
|
||||
|
||||
local dc_list = {} -- disconnected monitor list
|
||||
|
||||
if missing.main then table.insert(dc_list, { "Main", tmp_cfg.MainDisplay }) end
|
||||
if missing.flow then table.insert(dc_list, { "Flow", tmp_cfg.FlowDisplay }) end
|
||||
for i = 1, tmp_cfg.UnitCount do
|
||||
if missing.unit[i] then table.insert(dc_list, { "Unit " .. i, tmp_cfg.UnitDisplays[i] }) end
|
||||
end
|
||||
|
||||
-- add monitors that are assigned but not connected
|
||||
for i = 1, #dc_list do
|
||||
local line = Div{parent=mon_list,x=1,y=1,height=1}
|
||||
|
||||
TextBox{parent=line,x=1,y=1,width=6,height=1,text=dc_list[i][1],fg_bg=cpair(colors.blue,colors.white)}
|
||||
TextBox{parent=line,x=8,y=1,height=1,text="disconnected",fg_bg=cpair(colors.red,colors.white)}
|
||||
|
||||
local function unset_mon()
|
||||
purge_assignments(dc_list[i][2])
|
||||
tool_ctl.gen_mon_list()
|
||||
end
|
||||
|
||||
TextBox{parent=line,x=33,y=1,width=4,height=1,text="?x?",fg_bg=cpair(colors.black,colors.white)}
|
||||
PushButton{parent=line,x=37,y=1,min_width=5,height=1,text="SET",callback=function()end,dis_fg_bg=cpair(colors.black,colors.gray)}.disable()
|
||||
PushButton{parent=line,x=42,y=1,min_width=7,height=1,text="UNSET",callback=unset_mon,fg_bg=cpair(colors.black,colors.red),active_fg_bg=btn_act_fg_bg,dis_fg_bg=cpair(colors.black,colors.gray)}
|
||||
end
|
||||
end
|
||||
|
||||
-- expose the auth key on the summary page
|
||||
@@ -1195,7 +1356,13 @@ local function config_view(display)
|
||||
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] == "TempScale" then
|
||||
if raw == 1 then val = "Kelvin" elseif raw == 2 then val = "Celsius" elseif raw == 3 then val = "Fahrenheit" else val = "Rankine" end
|
||||
if raw == 1 then val = "Kelvin" elseif raw == 2 then val = "Celsius" elseif raw == 3 then val = "Fahrenheit" elseif raw == 4 then val = "Rankine" end
|
||||
elseif f[1] == "MainTheme" then
|
||||
val = util.strval(themes.ui_theme_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))
|
||||
elseif f[1] == "UnitDisplays" and type(cfg.UnitDisplays) == "table" then
|
||||
val = ""
|
||||
for idx = 1, #cfg.UnitDisplays do
|
||||
|
||||
@@ -4,6 +4,8 @@ local ppm = require("scada-common.ppm")
|
||||
local util = require("scada-common.util")
|
||||
local types = require("scada-common.types")
|
||||
|
||||
local themes = require("graphics.themes")
|
||||
|
||||
local iocontrol = require("coordinator.iocontrol")
|
||||
local process = require("coordinator.process")
|
||||
|
||||
@@ -54,6 +56,10 @@ function coordinator.load_config()
|
||||
config.LogPath = settings.get("LogPath")
|
||||
config.LogDebug = settings.get("LogDebug")
|
||||
|
||||
config.MainTheme = settings.get("MainTheme")
|
||||
config.FrontPanelTheme = settings.get("FrontPanelTheme")
|
||||
config.ColorMode = settings.get("ColorMode")
|
||||
|
||||
local cfv = util.new_validator()
|
||||
|
||||
cfv.assert_type_int(config.UnitCount)
|
||||
@@ -83,7 +89,7 @@ function coordinator.load_config()
|
||||
|
||||
if type(config.AuthKey) == "string" then
|
||||
local len = string.len(config.AuthKey)
|
||||
cfv.assert_eq(len == 0 or len >= 8, true)
|
||||
cfv.assert(len == 0 or len >= 8)
|
||||
end
|
||||
|
||||
cfv.assert_type_int(config.LogMode)
|
||||
@@ -91,6 +97,13 @@ function coordinator.load_config()
|
||||
cfv.assert_type_str(config.LogPath)
|
||||
cfv.assert_type_bool(config.LogDebug)
|
||||
|
||||
cfv.assert_type_int(config.MainTheme)
|
||||
cfv.assert_range(config.MainTheme, 1, 2)
|
||||
cfv.assert_type_int(config.FrontPanelTheme)
|
||||
cfv.assert_range(config.FrontPanelTheme, 1, 2)
|
||||
cfv.assert_type_int(config.ColorMode)
|
||||
cfv.assert_range(config.ColorMode, 1, themes.COLOR_MODE.NUM_MODES)
|
||||
|
||||
-- Monitor Setup
|
||||
|
||||
---@class monitors_struct
|
||||
@@ -179,7 +192,7 @@ end
|
||||
---@return function? update, function? done
|
||||
local function log_dmesg(message, dmesg_tag, working)
|
||||
local colors = {
|
||||
GRAPHICS = colors.green,
|
||||
RENDER = colors.green,
|
||||
SYSTEM = colors.cyan,
|
||||
BOOT = colors.blue,
|
||||
COMMS = colors.purple,
|
||||
@@ -193,7 +206,7 @@ local function log_dmesg(message, dmesg_tag, working)
|
||||
end
|
||||
end
|
||||
|
||||
function coordinator.log_graphics(message) log_dmesg(message, "GRAPHICS") end
|
||||
function coordinator.log_render(message) log_dmesg(message, "RENDER") end
|
||||
function coordinator.log_sys(message) log_dmesg(message, "SYSTEM") end
|
||||
function coordinator.log_boot(message) log_dmesg(message, "BOOT") end
|
||||
function coordinator.log_comms(message) log_dmesg(message, "COMMS") end
|
||||
@@ -266,11 +279,12 @@ function coordinator.comms(version, nic, sv_watchdog)
|
||||
-- send an API establish request response
|
||||
---@param packet scada_packet
|
||||
---@param ack ESTABLISH_ACK
|
||||
local function _send_api_establish_ack(packet, ack)
|
||||
---@param data any?
|
||||
local function _send_api_establish_ack(packet, ack, data)
|
||||
local s_pkt = comms.scada_packet()
|
||||
local m_pkt = comms.mgmt_packet()
|
||||
|
||||
m_pkt.make(MGMT_TYPE.ESTABLISH, { ack })
|
||||
m_pkt.make(MGMT_TYPE.ESTABLISH, { ack, data })
|
||||
s_pkt.make(packet.src_addr(), packet.seq_num() + 1, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable())
|
||||
|
||||
nic.transmit(config.PKT_Channel, config.CRD_Channel, s_pkt)
|
||||
@@ -334,6 +348,7 @@ function coordinator.comms(version, nic, sv_watchdog)
|
||||
|
||||
ok = false
|
||||
elseif self.sv_config_err then
|
||||
self.est_task_done(false)
|
||||
coordinator.log_comms("supervisor unit count does not match coordinator unit count, check configs")
|
||||
ok = false
|
||||
elseif (os.clock() - self.est_last) > 1.0 then
|
||||
@@ -457,10 +472,11 @@ function coordinator.comms(version, nic, sv_watchdog)
|
||||
elseif packet.type == MGMT_TYPE.ESTABLISH then
|
||||
-- establish a new session
|
||||
-- validate packet and continue
|
||||
if packet.length == 3 and type(packet.data[1]) == "string" and type(packet.data[2]) == "string" then
|
||||
local comms_v = packet.data[1]
|
||||
local firmware_v = packet.data[2]
|
||||
if packet.length == 4 then
|
||||
local comms_v = util.strval(packet.data[1])
|
||||
local firmware_v = util.strval(packet.data[2])
|
||||
local dev_type = packet.data[3]
|
||||
local api_v = util.strval(packet.data[4])
|
||||
|
||||
if comms_v ~= comms.version then
|
||||
if self.last_api_est_acks[src_addr] ~= ESTABLISH_ACK.BAD_VERSION then
|
||||
@@ -468,12 +484,19 @@ function coordinator.comms(version, nic, sv_watchdog)
|
||||
end
|
||||
|
||||
_send_api_establish_ack(packet.scada_frame, ESTABLISH_ACK.BAD_VERSION)
|
||||
elseif api_v ~= comms.api_version then
|
||||
if self.last_api_est_acks[src_addr] ~= ESTABLISH_ACK.BAD_API_VERSION then
|
||||
log.info(util.c("dropping API establish packet with incorrect api version v", api_v, " (expected v", comms.api_version, ")"))
|
||||
end
|
||||
|
||||
_send_api_establish_ack(packet.scada_frame, ESTABLISH_ACK.BAD_API_VERSION)
|
||||
elseif dev_type == DEVICE_TYPE.PKT then
|
||||
-- pocket linking request
|
||||
local id = apisessions.establish_session(src_addr, firmware_v)
|
||||
coordinator.log_comms(util.c("API_ESTABLISH: pocket (", firmware_v, ") [@", src_addr, "] connected with session ID ", id))
|
||||
|
||||
_send_api_establish_ack(packet.scada_frame, ESTABLISH_ACK.ALLOW)
|
||||
local conf = iocontrol.get_db().facility.conf
|
||||
_send_api_establish_ack(packet.scada_frame, ESTABLISH_ACK.ALLOW, { conf.num_units, conf.cooling })
|
||||
else
|
||||
log.debug(util.c("API_ESTABLISH: illegal establish packet for device ", dev_type, " on pocket channel"))
|
||||
_send_api_establish_ack(packet.scada_frame, ESTABLISH_ACK.DENY)
|
||||
|
||||
@@ -67,6 +67,7 @@ function iocontrol.init(conf, comms, temp_scale)
|
||||
-- facility data structure
|
||||
---@class ioctl_facility
|
||||
io.facility = {
|
||||
conf = conf,
|
||||
num_units = conf.num_units,
|
||||
tank_mode = conf.cooling.fac_tank_mode,
|
||||
tank_defs = conf.cooling.fac_tank_defs,
|
||||
@@ -91,6 +92,7 @@ function iocontrol.init(conf, comms, temp_scale)
|
||||
---@type WASTE_PRODUCT
|
||||
auto_current_waste_product = types.WASTE_PRODUCT.PLUTONIUM,
|
||||
auto_pu_fallback_active = false,
|
||||
auto_sps_disabled = false,
|
||||
|
||||
radiation = types.new_zero_radiation_reading(),
|
||||
|
||||
@@ -279,18 +281,18 @@ function iocontrol.init(conf, comms, temp_scale)
|
||||
|
||||
---@type alarms
|
||||
alarms = {
|
||||
ALARM_STATE.INACTIVE, -- containment breach
|
||||
ALARM_STATE.INACTIVE, -- containment radiation
|
||||
ALARM_STATE.INACTIVE, -- reactor lost
|
||||
ALARM_STATE.INACTIVE, -- damage critical
|
||||
ALARM_STATE.INACTIVE, -- reactor taking damage
|
||||
ALARM_STATE.INACTIVE, -- reactor over temperature
|
||||
ALARM_STATE.INACTIVE, -- reactor high temperature
|
||||
ALARM_STATE.INACTIVE, -- waste leak
|
||||
ALARM_STATE.INACTIVE, -- waste level high
|
||||
ALARM_STATE.INACTIVE, -- RPS transient
|
||||
ALARM_STATE.INACTIVE, -- RCS transient
|
||||
ALARM_STATE.INACTIVE -- turbine trip
|
||||
ALARM_STATE.INACTIVE, -- containment breach
|
||||
ALARM_STATE.INACTIVE, -- containment radiation
|
||||
ALARM_STATE.INACTIVE, -- reactor lost
|
||||
ALARM_STATE.INACTIVE, -- damage critical
|
||||
ALARM_STATE.INACTIVE, -- reactor taking damage
|
||||
ALARM_STATE.INACTIVE, -- reactor over temperature
|
||||
ALARM_STATE.INACTIVE, -- reactor high temperature
|
||||
ALARM_STATE.INACTIVE, -- waste leak
|
||||
ALARM_STATE.INACTIVE, -- waste level high
|
||||
ALARM_STATE.INACTIVE, -- RPS transient
|
||||
ALARM_STATE.INACTIVE, -- RCS transient
|
||||
ALARM_STATE.INACTIVE -- turbine trip
|
||||
},
|
||||
|
||||
annunciator = {}, ---@type annunciator
|
||||
@@ -376,6 +378,13 @@ function iocontrol.fp_monitor_state(id, connected)
|
||||
end
|
||||
end
|
||||
|
||||
-- report thread (routine) statuses
|
||||
---@param thread string thread name
|
||||
---@param ok boolean thread state
|
||||
function iocontrol.fp_rt_status(thread, ok)
|
||||
io.fp.ps.publish(util.c("routine__", thread), ok)
|
||||
end
|
||||
|
||||
-- report PKT firmware version and PKT session connection state
|
||||
---@param session_id integer PKT session
|
||||
---@param fw string firmware version
|
||||
@@ -403,7 +412,7 @@ function iocontrol.fp_pkt_rtt(session_id, rtt)
|
||||
elseif rtt > WARN_RTT then
|
||||
io.fp.ps.publish("pkt_" .. session_id .. "_rtt_color", colors.yellow_hc)
|
||||
else
|
||||
io.fp.ps.publish("pkt_" .. session_id .. "_rtt_color", colors.green)
|
||||
io.fp.ps.publish("pkt_" .. session_id .. "_rtt_color", colors.green_hc)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -585,7 +594,7 @@ function iocontrol.update_facility_status(status)
|
||||
|
||||
local ctl_status = status[1]
|
||||
|
||||
if type(ctl_status) == "table" and #ctl_status == 16 then
|
||||
if type(ctl_status) == "table" and #ctl_status == 17 then
|
||||
fac.all_sys_ok = ctl_status[1]
|
||||
fac.auto_ready = ctl_status[2]
|
||||
|
||||
@@ -636,9 +645,11 @@ function iocontrol.update_facility_status(status)
|
||||
|
||||
fac.auto_current_waste_product = ctl_status[15]
|
||||
fac.auto_pu_fallback_active = ctl_status[16]
|
||||
fac.auto_sps_disabled = ctl_status[17]
|
||||
|
||||
fac.ps.publish("current_waste_product", fac.auto_current_waste_product)
|
||||
fac.ps.publish("pu_fallback_active", fac.auto_pu_fallback_active)
|
||||
fac.ps.publish("sps_disabled_low_power", fac.auto_sps_disabled)
|
||||
else
|
||||
log.debug(log_header .. "control status not a table or length mismatch")
|
||||
valid = false
|
||||
@@ -655,10 +666,27 @@ function iocontrol.update_facility_status(status)
|
||||
fac.rtu_count = rtu_statuses.count
|
||||
|
||||
-- power statistics
|
||||
if type(rtu_statuses.power) == "table" then
|
||||
fac.induction_ps_tbl[1].publish("avg_charge", rtu_statuses.power[1])
|
||||
fac.induction_ps_tbl[1].publish("avg_inflow", rtu_statuses.power[2])
|
||||
fac.induction_ps_tbl[1].publish("avg_outflow", rtu_statuses.power[3])
|
||||
if type(rtu_statuses.power) == "table" and #rtu_statuses.power == 4 then
|
||||
local data = fac.induction_data_tbl[1] ---@type imatrix_session_db
|
||||
local ps = fac.induction_ps_tbl[1] ---@type psil
|
||||
|
||||
local chg = tonumber(rtu_statuses.power[1])
|
||||
local in_f = tonumber(rtu_statuses.power[2])
|
||||
local out_f = tonumber(rtu_statuses.power[3])
|
||||
local eta = tonumber(rtu_statuses.power[4])
|
||||
|
||||
ps.publish("avg_charge", chg)
|
||||
ps.publish("avg_inflow", in_f)
|
||||
ps.publish("avg_outflow", out_f)
|
||||
ps.publish("eta_ms", eta)
|
||||
|
||||
ps.publish("is_charging", in_f > out_f)
|
||||
ps.publish("is_discharging", out_f > in_f)
|
||||
|
||||
if data and data.build then
|
||||
local cap = util.joules_to_fe(data.build.transfer_cap)
|
||||
ps.publish("at_max_io", in_f >= cap or out_f >= cap)
|
||||
end
|
||||
else
|
||||
log.debug(log_header .. "power statistics list not a table")
|
||||
valid = false
|
||||
|
||||
@@ -29,7 +29,8 @@ local self = {
|
||||
gen_target = 0.0,
|
||||
limits = {},
|
||||
waste_product = PRODUCT.PLUTONIUM,
|
||||
pu_fallback = false
|
||||
pu_fallback = false,
|
||||
sps_low_power = false
|
||||
},
|
||||
waste_modes = {},
|
||||
priority_groups = {}
|
||||
@@ -65,6 +66,7 @@ function process.init(iocontrol, coord_comms)
|
||||
ctl_proc.limits = config.limits
|
||||
ctl_proc.waste_product = config.waste_product
|
||||
ctl_proc.pu_fallback = config.pu_fallback
|
||||
ctl_proc.sps_low_power = config.sps_low_power
|
||||
|
||||
self.io.facility.ps.publish("process_mode", ctl_proc.mode)
|
||||
self.io.facility.ps.publish("process_burn_target", ctl_proc.burn_target)
|
||||
@@ -72,6 +74,7 @@ function process.init(iocontrol, coord_comms)
|
||||
self.io.facility.ps.publish("process_gen_target", ctl_proc.gen_target)
|
||||
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_sps_low_power", ctl_proc.sps_low_power)
|
||||
|
||||
for id = 1, math.min(#ctl_proc.limits, self.io.facility.num_units) do
|
||||
local unit = self.io.units[id] ---@type ioctl_unit
|
||||
@@ -83,6 +86,7 @@ function process.init(iocontrol, coord_comms)
|
||||
-- notify supervisor of auto waste config
|
||||
self.comms.send_fac_command(FAC_COMMAND.SET_WASTE_MODE, ctl_proc.waste_product)
|
||||
self.comms.send_fac_command(FAC_COMMAND.SET_PU_FB, ctl_proc.pu_fallback)
|
||||
self.comms.send_fac_command(FAC_COMMAND.SET_SPS_LP, ctl_proc.sps_low_power)
|
||||
end
|
||||
|
||||
-- unit waste states
|
||||
@@ -259,6 +263,18 @@ function process.set_pu_fallback(enabled)
|
||||
_write_auto_config()
|
||||
end
|
||||
|
||||
-- set automatic process control SPS usage at low power
|
||||
---@param enabled boolean whether to enable SPS usage at low power
|
||||
function process.set_sps_low_power(enabled)
|
||||
self.comms.send_fac_command(FAC_COMMAND.SET_SPS_LP, enabled)
|
||||
|
||||
log.debug(util.c("PROCESS: SET SPS LOW POWER ", enabled))
|
||||
|
||||
-- update config table and save
|
||||
self.control_states.process.sps_low_power = enabled
|
||||
_write_auto_config()
|
||||
end
|
||||
|
||||
-- save process control settings
|
||||
---@param mode PROCESS control mode
|
||||
---@param burn_target number burn rate target
|
||||
|
||||
@@ -2,28 +2,33 @@
|
||||
-- Graphics Rendering Control
|
||||
--
|
||||
|
||||
local log = require("scada-common.log")
|
||||
local log = require("scada-common.log")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local iocontrol = require("coordinator.iocontrol")
|
||||
local coordinator = require("coordinator.coordinator")
|
||||
local iocontrol = require("coordinator.iocontrol")
|
||||
|
||||
local style = require("coordinator.ui.style")
|
||||
local pgi = require("coordinator.ui.pgi")
|
||||
local style = require("coordinator.ui.style")
|
||||
local pgi = require("coordinator.ui.pgi")
|
||||
|
||||
local flow_view = require("coordinator.ui.layout.flow_view")
|
||||
local panel_view = require("coordinator.ui.layout.front_panel")
|
||||
local main_view = require("coordinator.ui.layout.main_view")
|
||||
local unit_view = require("coordinator.ui.layout.unit_view")
|
||||
local flow_view = require("coordinator.ui.layout.flow_view")
|
||||
local panel_view = require("coordinator.ui.layout.front_panel")
|
||||
local main_view = require("coordinator.ui.layout.main_view")
|
||||
local unit_view = require("coordinator.ui.layout.unit_view")
|
||||
|
||||
local core = require("graphics.core")
|
||||
local flasher = require("graphics.flasher")
|
||||
local core = require("graphics.core")
|
||||
local flasher = require("graphics.flasher")
|
||||
|
||||
local DisplayBox = require("graphics.elements.displaybox")
|
||||
local DisplayBox = require("graphics.elements.displaybox")
|
||||
|
||||
local log_render = coordinator.log_render
|
||||
|
||||
---@class coord_renderer
|
||||
local renderer = {}
|
||||
|
||||
-- render engine
|
||||
local engine = {
|
||||
color_mode = 1, ---@type COLOR_MODE
|
||||
monitors = nil, ---@type monitors_struct|nil
|
||||
dmesg_window = nil, ---@type table|nil
|
||||
ui_ready = false,
|
||||
@@ -47,8 +52,14 @@ local function _init_display(monitor)
|
||||
monitor.setCursorPos(1, 1)
|
||||
|
||||
-- set overridden colors
|
||||
for i = 1, #style.colors do
|
||||
monitor.setPaletteColor(style.colors[i].c, style.colors[i].hex)
|
||||
for i = 1, #style.theme.colors do
|
||||
monitor.setPaletteColor(style.theme.colors[i].c, style.theme.colors[i].hex)
|
||||
end
|
||||
|
||||
-- apply color mode
|
||||
local c_mode_overrides = style.theme.color_modes[engine.color_mode]
|
||||
for i = 1, #c_mode_overrides do
|
||||
monitor.setPaletteColor(c_mode_overrides[i].c, c_mode_overrides[i].hex)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -62,10 +73,13 @@ local function _print_too_small(monitor)
|
||||
monitor.write("monitor too small")
|
||||
end
|
||||
|
||||
-- disable the flow view
|
||||
---@param disable boolean
|
||||
function renderer.legacy_disable_flow_view(disable)
|
||||
engine.disable_flow_view = disable
|
||||
-- apply renderer configurations
|
||||
---@param config crd_config
|
||||
function renderer.configure(config)
|
||||
style.set_themes(config.MainTheme, config.FrontPanelTheme, config.ColorMode)
|
||||
|
||||
engine.color_mode = config.ColorMode
|
||||
engine.disable_flow_view = config.DisableFlowView
|
||||
end
|
||||
|
||||
-- link to the monitor peripherals
|
||||
@@ -97,8 +111,14 @@ function renderer.init_displays()
|
||||
term.setCursorPos(1, 1)
|
||||
|
||||
-- set overridden colors
|
||||
for i = 1, #style.fp.colors do
|
||||
term.setPaletteColor(style.fp.colors[i].c, style.fp.colors[i].hex)
|
||||
for i = 1, #style.fp_theme.colors do
|
||||
term.setPaletteColor(style.fp_theme.colors[i].c, style.fp_theme.colors[i].hex)
|
||||
end
|
||||
|
||||
-- apply color mode
|
||||
local c_mode_overrides = style.fp_theme.color_modes[engine.color_mode]
|
||||
for i = 1, #c_mode_overrides do
|
||||
term.setPaletteColor(c_mode_overrides[i].c, c_mode_overrides[i].hex)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -152,9 +172,9 @@ function renderer.close_fp()
|
||||
engine.fp_ready = false
|
||||
|
||||
-- restore colors
|
||||
for i = 1, #style.colors do
|
||||
local r, g, b = term.nativePaletteColor(style.colors[i].c)
|
||||
term.setPaletteColor(style.colors[i].c, r, g, b)
|
||||
for i = 1, #style.fp_theme.colors do
|
||||
local r, g, b = term.nativePaletteColor(style.fp_theme.colors[i].c)
|
||||
term.setPaletteColor(style.fp_theme.colors[i].c, r, g, b)
|
||||
end
|
||||
|
||||
-- reset terminal
|
||||
@@ -179,18 +199,21 @@ function renderer.try_start_ui()
|
||||
if engine.monitors.main ~= nil then
|
||||
engine.ui.main_display = DisplayBox{window=engine.monitors.main,fg_bg=style.root}
|
||||
main_view(engine.ui.main_display)
|
||||
util.nop()
|
||||
end
|
||||
|
||||
-- show flow view on flow monitor
|
||||
if engine.monitors.flow ~= nil then
|
||||
engine.ui.flow_display = DisplayBox{window=engine.monitors.flow,fg_bg=style.root}
|
||||
flow_view(engine.ui.flow_display)
|
||||
util.nop()
|
||||
end
|
||||
|
||||
-- show unit views on unit displays
|
||||
for idx, display in pairs(engine.monitors.unit_displays) do
|
||||
engine.ui.unit_displays[idx] = DisplayBox{window=display,fg_bg=style.root}
|
||||
unit_view(engine.ui.unit_displays[idx], idx)
|
||||
util.nop()
|
||||
end
|
||||
end)
|
||||
|
||||
@@ -231,6 +254,11 @@ function renderer.close_ui()
|
||||
-- clear unit monitors
|
||||
for _, monitor in ipairs(engine.monitors.unit_displays) do monitor.clear() end
|
||||
|
||||
if not engine.disable_flow_view then
|
||||
-- clear flow monitor
|
||||
engine.monitors.flow.clear()
|
||||
end
|
||||
|
||||
-- re-draw dmesg
|
||||
engine.dmesg_window.setVisible(true)
|
||||
engine.dmesg_window.redraw()
|
||||
@@ -367,12 +395,15 @@ function renderer.handle_resize(name)
|
||||
engine.dmesg_window.setVisible(not engine.ui_ready)
|
||||
|
||||
if engine.ui_ready then
|
||||
local draw_start = util.time_ms()
|
||||
local ok = pcall(function ()
|
||||
ui.main_display = DisplayBox{window=device,fg_bg=style.root}
|
||||
main_view(ui.main_display)
|
||||
end)
|
||||
|
||||
if not ok then
|
||||
if ok then
|
||||
log_render("main view re-draw completed in " .. (util.time_ms() - draw_start) .. "ms")
|
||||
else
|
||||
if ui.main_display then
|
||||
ui.main_display.delete()
|
||||
ui.main_display = nil
|
||||
@@ -400,14 +431,15 @@ function renderer.handle_resize(name)
|
||||
iocontrol.fp_monitor_state("flow", true)
|
||||
|
||||
if engine.ui_ready then
|
||||
engine.dmesg_window.setVisible(false)
|
||||
|
||||
local draw_start = util.time_ms()
|
||||
local ok = pcall(function ()
|
||||
ui.flow_display = DisplayBox{window=device,fg_bg=style.root}
|
||||
flow_view(ui.flow_display)
|
||||
end)
|
||||
|
||||
if not ok then
|
||||
if ok then
|
||||
log_render("flow view re-draw completed in " .. (util.time_ms() - draw_start) .. "ms")
|
||||
else
|
||||
if ui.flow_display then
|
||||
ui.flow_display.delete()
|
||||
ui.flow_display = nil
|
||||
@@ -437,14 +469,15 @@ function renderer.handle_resize(name)
|
||||
iocontrol.fp_monitor_state(idx, true)
|
||||
|
||||
if engine.ui_ready then
|
||||
engine.dmesg_window.setVisible(false)
|
||||
|
||||
local draw_start = util.time_ms()
|
||||
local ok = pcall(function ()
|
||||
ui.unit_displays[idx] = DisplayBox{window=device,fg_bg=style.root}
|
||||
unit_view(ui.unit_displays[idx], idx)
|
||||
end)
|
||||
|
||||
if not ok then
|
||||
if ok then
|
||||
log_render("unit " .. idx .. " view re-draw completed in " .. (util.time_ms() - draw_start) .. "ms")
|
||||
else
|
||||
if ui.unit_displays[idx] then
|
||||
ui.unit_displays[idx].delete()
|
||||
ui.unit_displays[idx] = nil
|
||||
|
||||
@@ -8,7 +8,7 @@ local iocontrol = require("coordinator.iocontrol")
|
||||
local pocket = {}
|
||||
|
||||
local PROTOCOL = comms.PROTOCOL
|
||||
-- local CRDN_TYPE = comms.CRDN_TYPE
|
||||
local CRDN_TYPE = comms.CRDN_TYPE
|
||||
local MGMT_TYPE = comms.MGMT_TYPE
|
||||
|
||||
-- retry time constants in ms
|
||||
@@ -73,18 +73,18 @@ function pocket.new_session(id, s_addr, in_queue, out_queue, timeout)
|
||||
end
|
||||
|
||||
-- send a CRDN packet
|
||||
-----@param msg_type CRDN_TYPE
|
||||
-----@param msg table
|
||||
-- local function _send(msg_type, msg)
|
||||
-- local s_pkt = comms.scada_packet()
|
||||
-- local c_pkt = comms.crdn_packet()
|
||||
---@param msg_type CRDN_TYPE
|
||||
---@param msg table
|
||||
local function _send(msg_type, msg)
|
||||
local s_pkt = comms.scada_packet()
|
||||
local c_pkt = comms.crdn_packet()
|
||||
|
||||
-- c_pkt.make(msg_type, msg)
|
||||
-- s_pkt.make(self.seq_num, PROTOCOL.SCADA_CRDN, c_pkt.raw_sendable())
|
||||
c_pkt.make(msg_type, msg)
|
||||
s_pkt.make(s_addr, self.seq_num, PROTOCOL.SCADA_CRDN, c_pkt.raw_sendable())
|
||||
|
||||
-- out_queue.push_packet(s_pkt)
|
||||
-- self.seq_num = self.seq_num + 1
|
||||
-- end
|
||||
out_queue.push_packet(s_pkt)
|
||||
self.seq_num = self.seq_num + 1
|
||||
end
|
||||
|
||||
-- send a SCADA management packet
|
||||
---@param msg_type MGMT_TYPE
|
||||
@@ -120,8 +120,39 @@ function pocket.new_session(id, s_addr, in_queue, out_queue, timeout)
|
||||
if pkt.scada_frame.protocol() == PROTOCOL.SCADA_CRDN then
|
||||
---@cast pkt crdn_frame
|
||||
|
||||
local db = iocontrol.get_db()
|
||||
|
||||
-- handle packet by type
|
||||
if pkt.type == nil then
|
||||
if pkt.type == CRDN_TYPE.API_GET_FAC then
|
||||
local fac = db.facility
|
||||
|
||||
local data = {
|
||||
fac.all_sys_ok,
|
||||
fac.rtu_count,
|
||||
fac.radiation,
|
||||
{ fac.auto_ready, fac.auto_active, fac.auto_ramping, fac.auto_saturated },
|
||||
{ fac.auto_current_waste_product, fac.auto_pu_fallback_active },
|
||||
util.table_len(fac.tank_data_tbl),
|
||||
fac.induction_data_tbl[1] ~= nil,
|
||||
fac.sps_data_tbl[1] ~= nil,
|
||||
}
|
||||
|
||||
_send(CRDN_TYPE.API_GET_FAC, data)
|
||||
elseif pkt.type == CRDN_TYPE.API_GET_UNITS then
|
||||
local data = {}
|
||||
|
||||
for i = 1, #db.units do
|
||||
local u = db.units[i] ---@type ioctl_unit
|
||||
table.insert(data, {
|
||||
u.unit_id,
|
||||
u.num_boilers,
|
||||
u.num_turbines,
|
||||
u.num_snas,
|
||||
u.has_tank
|
||||
})
|
||||
end
|
||||
|
||||
_send(CRDN_TYPE.API_GET_UNITS, data)
|
||||
else
|
||||
log.debug(log_header .. "handler received unsupported CRDN packet type " .. pkt.type)
|
||||
end
|
||||
|
||||
@@ -7,32 +7,29 @@ require("/initenv").init_env()
|
||||
local comms = require("scada-common.comms")
|
||||
local crash = require("scada-common.crash")
|
||||
local log = require("scada-common.log")
|
||||
local mqueue = require("scada-common.mqueue")
|
||||
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 core = require("graphics.core")
|
||||
|
||||
local configure = require("coordinator.configure")
|
||||
local coordinator = require("coordinator.coordinator")
|
||||
local iocontrol = require("coordinator.iocontrol")
|
||||
local renderer = require("coordinator.renderer")
|
||||
local sounder = require("coordinator.sounder")
|
||||
local threads = require("coordinator.threads")
|
||||
|
||||
local apisessions = require("coordinator.session.apisessions")
|
||||
|
||||
local COORDINATOR_VERSION = "v1.2.11"
|
||||
local COORDINATOR_VERSION = "v1.4.5"
|
||||
|
||||
local CHUNK_LOAD_DELAY_S = 30.0
|
||||
|
||||
local println = util.println
|
||||
local println = util.println
|
||||
local println_ts = util.println_ts
|
||||
|
||||
local log_graphics = coordinator.log_graphics
|
||||
local log_sys = coordinator.log_sys
|
||||
local log_boot = coordinator.log_boot
|
||||
local log_comms = coordinator.log_comms
|
||||
local log_render = coordinator.log_render
|
||||
local log_sys = coordinator.log_sys
|
||||
local log_boot = coordinator.log_boot
|
||||
local log_comms = coordinator.log_comms
|
||||
local log_crypto = coordinator.log_crypto
|
||||
|
||||
----------------------------------------
|
||||
@@ -103,6 +100,7 @@ log.info("========================================")
|
||||
println(">> SCADA Coordinator " .. COORDINATOR_VERSION .. " <<")
|
||||
|
||||
crash.set_env("coordinator", COORDINATOR_VERSION)
|
||||
crash.dbg_log_env()
|
||||
|
||||
----------------------------------------
|
||||
-- main application
|
||||
@@ -120,7 +118,7 @@ local function main()
|
||||
iocontrol.init_fp(COORDINATOR_VERSION, comms.version)
|
||||
|
||||
-- init renderer
|
||||
renderer.legacy_disable_flow_view(config.DisableFlowView)
|
||||
renderer.configure(config)
|
||||
renderer.set_displays(monitors)
|
||||
renderer.init_displays()
|
||||
renderer.init_dmesg()
|
||||
@@ -128,16 +126,58 @@ local function main()
|
||||
-- lets get started!
|
||||
log.info("monitors ready, dmesg output incoming...")
|
||||
|
||||
log_graphics("displays connected and reset")
|
||||
log_render("displays connected and reset")
|
||||
log_sys("system start on " .. os.date("%c"))
|
||||
log_boot("starting " .. COORDINATOR_VERSION)
|
||||
|
||||
----------------------------------------
|
||||
-- memory allocation
|
||||
----------------------------------------
|
||||
|
||||
-- shared memory across threads
|
||||
---@class crd_shared_memory
|
||||
local __shared_memory = {
|
||||
-- time and date format for display
|
||||
date_format = util.trinary(config.Time24Hour, "%X \x04 %A, %B %d %Y", "%r \x04 %A, %B %d %Y"),
|
||||
|
||||
-- coordinator system state flags
|
||||
---@class crd_state
|
||||
crd_state = {
|
||||
fp_ok = false,
|
||||
ui_ok = true, -- default true, used to abort on fail
|
||||
link_fail = false,
|
||||
shutdown = false
|
||||
},
|
||||
|
||||
-- core coordinator devices
|
||||
crd_dev = {
|
||||
speaker = ppm.get_device("speaker"),
|
||||
modem = ppm.get_wireless_modem()
|
||||
},
|
||||
|
||||
-- system objects
|
||||
crd_sys = {
|
||||
nic = nil, ---@type nic
|
||||
coord_comms = nil, ---@type coord_comms
|
||||
conn_watchdog = nil ---@type watchdog
|
||||
},
|
||||
|
||||
-- message queues
|
||||
q = {
|
||||
mq_render = mqueue.new()
|
||||
}
|
||||
}
|
||||
|
||||
local smem_dev = __shared_memory.crd_dev
|
||||
local smem_sys = __shared_memory.crd_sys
|
||||
|
||||
local crd_state = __shared_memory.crd_state
|
||||
|
||||
----------------------------------------
|
||||
-- setup alarm sounder subsystem
|
||||
----------------------------------------
|
||||
|
||||
local speaker = ppm.get_device("speaker")
|
||||
if speaker == nil then
|
||||
if smem_dev.speaker == nil then
|
||||
log_boot("annunciator alarm speaker not found")
|
||||
println("startup> speaker not found")
|
||||
log.fatal("no annunciator alarm speaker found")
|
||||
@@ -145,7 +185,7 @@ local function main()
|
||||
else
|
||||
local sounder_start = util.time_ms()
|
||||
log_boot("annunciator alarm speaker connected")
|
||||
sounder.init(speaker, config.SpeakerVolume)
|
||||
sounder.init(smem_dev.speaker, config.SpeakerVolume)
|
||||
log_boot("tone generation took " .. (util.time_ms() - sounder_start) .. "ms")
|
||||
log_sys("annunciator alarm configured")
|
||||
iocontrol.fp_has_speaker(true)
|
||||
@@ -162,8 +202,7 @@ local function main()
|
||||
end
|
||||
|
||||
-- get the communications modem
|
||||
local modem = ppm.get_wireless_modem()
|
||||
if modem == nil then
|
||||
if smem_dev.modem == nil then
|
||||
log_comms("wireless modem not found")
|
||||
println("startup> wireless modem not found")
|
||||
log.fatal("no wireless modem on startup")
|
||||
@@ -174,243 +213,54 @@ local function main()
|
||||
end
|
||||
|
||||
-- create connection watchdog
|
||||
local conn_watchdog = util.new_watchdog(config.SVR_Timeout)
|
||||
conn_watchdog.cancel()
|
||||
smem_sys.conn_watchdog = util.new_watchdog(config.SVR_Timeout)
|
||||
smem_sys.conn_watchdog.cancel()
|
||||
log.debug("startup> conn watchdog created")
|
||||
|
||||
-- create network interface then setup comms
|
||||
local nic = network.nic(modem)
|
||||
local coord_comms = coordinator.comms(COORDINATOR_VERSION, nic, conn_watchdog)
|
||||
smem_sys.nic = network.nic(smem_dev.modem)
|
||||
smem_sys.coord_comms = coordinator.comms(COORDINATOR_VERSION, smem_sys.nic, smem_sys.conn_watchdog)
|
||||
log.debug("startup> comms init")
|
||||
log_comms("comms initialized")
|
||||
|
||||
-- base loop clock (2Hz, 10 ticks)
|
||||
local MAIN_CLOCK = 0.5
|
||||
local loop_clock = util.new_clock(MAIN_CLOCK)
|
||||
|
||||
----------------------------------------
|
||||
-- start front panel & UI start function
|
||||
-- start front panel
|
||||
----------------------------------------
|
||||
|
||||
log_graphics("starting front panel UI...")
|
||||
log_render("starting front panel UI...")
|
||||
|
||||
local fp_ok, fp_message = renderer.try_start_fp()
|
||||
if not fp_ok then
|
||||
log_graphics(util.c("front panel UI error: ", fp_message))
|
||||
local fp_message
|
||||
crd_state.fp_ok, fp_message = renderer.try_start_fp()
|
||||
if not crd_state.fp_ok then
|
||||
log_render(util.c("front panel UI error: ", fp_message))
|
||||
println_ts("front panel UI creation failed")
|
||||
log.fatal(util.c("front panel GUI render failed with error ", fp_message))
|
||||
return
|
||||
else log_graphics("front panel ready") end
|
||||
|
||||
-- start up the main UI
|
||||
---@return boolean ui_ok started ok
|
||||
local function start_main_ui()
|
||||
log_graphics("starting main UI...")
|
||||
|
||||
local draw_start = util.time_ms()
|
||||
|
||||
local ui_ok, ui_message = renderer.try_start_ui()
|
||||
if not ui_ok then
|
||||
log_graphics(util.c("main UI error: ", ui_message))
|
||||
log.fatal(util.c("main GUI render failed with error ", ui_message))
|
||||
else
|
||||
log_graphics("main UI draw took " .. (util.time_ms() - draw_start) .. "ms")
|
||||
end
|
||||
|
||||
return ui_ok
|
||||
end
|
||||
else log_render("front panel ready") end
|
||||
|
||||
----------------------------------------
|
||||
-- main event loop
|
||||
-- start system
|
||||
----------------------------------------
|
||||
|
||||
local link_failed = false
|
||||
local ui_ok = true
|
||||
local date_format = util.trinary(config.Time24Hour, "%X \x04 %A, %B %d %Y", "%r \x04 %A, %B %d %Y")
|
||||
-- init threads
|
||||
local main_thread = threads.thread__main(__shared_memory)
|
||||
local render_thread = threads.thread__render(__shared_memory)
|
||||
|
||||
-- start clock
|
||||
loop_clock.start()
|
||||
log.info("startup> completed")
|
||||
|
||||
log_sys("system started successfully")
|
||||
|
||||
-- main event loop
|
||||
while true do
|
||||
local event, param1, param2, param3, param4, param5 = util.pull_event()
|
||||
|
||||
-- handle event
|
||||
if event == "peripheral_detach" then
|
||||
local type, device = ppm.handle_unmount(param1)
|
||||
|
||||
if type ~= nil and device ~= nil then
|
||||
if type == "modem" then
|
||||
-- we only really care if this is our wireless modem
|
||||
-- if it is another modem, handle other peripheral losses separately
|
||||
if nic.is_modem(device) then
|
||||
nic.disconnect()
|
||||
log_sys("comms modem disconnected")
|
||||
|
||||
local other_modem = ppm.get_wireless_modem()
|
||||
if other_modem then
|
||||
log_sys("found another wireless modem, using it for comms")
|
||||
nic.connect(other_modem)
|
||||
else
|
||||
-- close out main UI
|
||||
renderer.close_ui()
|
||||
|
||||
-- alert user to status
|
||||
log_sys("awaiting comms modem reconnect...")
|
||||
|
||||
iocontrol.fp_has_modem(false)
|
||||
end
|
||||
else
|
||||
log_sys("non-comms modem disconnected")
|
||||
end
|
||||
elseif type == "monitor" then
|
||||
if renderer.handle_disconnect(device) then
|
||||
log_sys("lost a configured monitor")
|
||||
else
|
||||
log_sys("lost an unused monitor")
|
||||
end
|
||||
elseif type == "speaker" then
|
||||
log_sys("lost alarm sounder speaker")
|
||||
iocontrol.fp_has_speaker(false)
|
||||
end
|
||||
end
|
||||
elseif event == "peripheral" then
|
||||
local type, device = ppm.mount(param1)
|
||||
|
||||
if type ~= nil and device ~= nil then
|
||||
if type == "modem" then
|
||||
if device.isWireless() and not nic.is_connected() then
|
||||
-- reconnected modem
|
||||
log_sys("comms modem reconnected")
|
||||
nic.connect(device)
|
||||
iocontrol.fp_has_modem(true)
|
||||
elseif device.isWireless() then
|
||||
log.info("unused wireless modem reconnected")
|
||||
else
|
||||
log_sys("wired modem reconnected")
|
||||
end
|
||||
elseif type == "monitor" then
|
||||
if renderer.handle_reconnect(param1, device) then
|
||||
log_sys(util.c("configured monitor ", param1, " reconnected"))
|
||||
else
|
||||
log_sys(util.c("unused monitor ", param1, " connected"))
|
||||
end
|
||||
elseif type == "speaker" then
|
||||
log_sys("alarm sounder speaker reconnected")
|
||||
sounder.reconnect(device)
|
||||
iocontrol.fp_has_speaker(true)
|
||||
end
|
||||
end
|
||||
elseif event == "monitor_resize" then
|
||||
local is_used, is_ok = renderer.handle_resize(param1)
|
||||
if is_used then
|
||||
log_sys(util.c("configured monitor ", param1, " resized, ", util.trinary(is_ok, "display still fits", "display no longer fits")))
|
||||
end
|
||||
elseif event == "timer" then
|
||||
if loop_clock.is_clock(param1) then
|
||||
-- main loop tick
|
||||
|
||||
-- toggle heartbeat
|
||||
iocontrol.heartbeat()
|
||||
|
||||
-- maintain connection
|
||||
if nic.is_connected() then
|
||||
local ok, start_ui = coord_comms.try_connect()
|
||||
if not ok then
|
||||
link_failed = true
|
||||
log_sys("supervisor connection failed, shutting down...")
|
||||
log.fatal("failed to connect to supervisor")
|
||||
break
|
||||
elseif start_ui then
|
||||
log_sys("supervisor connected, proceeding to main UI start")
|
||||
ui_ok = start_main_ui()
|
||||
if not ui_ok then break end
|
||||
end
|
||||
end
|
||||
|
||||
-- iterate sessions
|
||||
apisessions.iterate_all()
|
||||
|
||||
-- free any closed sessions
|
||||
apisessions.free_all_closed()
|
||||
|
||||
-- update date and time string for main display
|
||||
if coord_comms.is_linked() then
|
||||
iocontrol.get_db().facility.ps.publish("date_time", os.date(date_format))
|
||||
end
|
||||
|
||||
loop_clock.start()
|
||||
elseif conn_watchdog.is_timer(param1) then
|
||||
-- supervisor watchdog timeout
|
||||
log_comms("supervisor server timeout")
|
||||
|
||||
-- close connection, main UI, and stop sounder
|
||||
coord_comms.close()
|
||||
renderer.close_ui()
|
||||
sounder.stop()
|
||||
else
|
||||
-- a non-clock/main watchdog timer event
|
||||
|
||||
-- check API watchdogs
|
||||
apisessions.check_all_watchdogs(param1)
|
||||
|
||||
-- notify timer callback dispatcher
|
||||
tcd.handle(param1)
|
||||
end
|
||||
elseif event == "modem_message" then
|
||||
-- got a packet
|
||||
local packet = coord_comms.parse_packet(param1, param2, param3, param4, param5)
|
||||
|
||||
-- handle then check if it was a disconnect
|
||||
if coord_comms.handle_packet(packet) then
|
||||
log_comms("supervisor closed connection")
|
||||
|
||||
-- close connection, main UI, and stop sounder
|
||||
coord_comms.close()
|
||||
renderer.close_ui()
|
||||
sounder.stop()
|
||||
end
|
||||
elseif event == "monitor_touch" or event == "mouse_click" or event == "mouse_up" or
|
||||
event == "mouse_drag" or event == "mouse_scroll" or event == "double_click" then
|
||||
-- handle a mouse event
|
||||
renderer.handle_mouse(core.events.new_mouse_event(event, param1, param2, param3))
|
||||
elseif event == "speaker_audio_empty" then
|
||||
-- handle speaker buffer emptied
|
||||
sounder.continue()
|
||||
end
|
||||
|
||||
-- check for termination request
|
||||
if event == "terminate" or ppm.should_terminate() then
|
||||
-- handle supervisor connection
|
||||
coord_comms.try_connect(true)
|
||||
|
||||
if coord_comms.is_linked() then
|
||||
log_comms("terminate requested, closing supervisor connection...")
|
||||
else link_failed = true end
|
||||
|
||||
coord_comms.close()
|
||||
log_comms("supervisor connection closed")
|
||||
|
||||
-- handle API sessions
|
||||
log_comms("closing api sessions...")
|
||||
apisessions.close_all()
|
||||
log_comms("api sessions closed")
|
||||
break
|
||||
end
|
||||
end
|
||||
-- run threads
|
||||
parallel.waitForAll(main_thread.p_exec, render_thread.p_exec)
|
||||
|
||||
renderer.close_ui()
|
||||
renderer.close_fp()
|
||||
sounder.stop()
|
||||
log_sys("system shutdown")
|
||||
|
||||
if link_failed then println_ts("failed to connect to supervisor") end
|
||||
if not ui_ok then println_ts("main UI creation failed") end
|
||||
if crd_state.link_fail then println_ts("failed to connect to supervisor") end
|
||||
if not crd_state.ui_ok then println_ts("main UI creation failed") end
|
||||
|
||||
-- close on error exit (such as UI error)
|
||||
if coord_comms.is_linked() then coord_comms.close() end
|
||||
if smem_sys.coord_comms.is_linked() then smem_sys.coord_comms.close() end
|
||||
|
||||
println_ts("exited")
|
||||
log.info("exited")
|
||||
|
||||
363
coordinator/threads.lua
Normal file
363
coordinator/threads.lua
Normal file
@@ -0,0 +1,363 @@
|
||||
local log = require("scada-common.log")
|
||||
local mqueue = require("scada-common.mqueue")
|
||||
local ppm = require("scada-common.ppm")
|
||||
local tcd = require("scada-common.tcd")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local coordinator = require("coordinator.coordinator")
|
||||
local iocontrol = require("coordinator.iocontrol")
|
||||
local renderer = require("coordinator.renderer")
|
||||
local sounder = require("coordinator.sounder")
|
||||
|
||||
local apisessions = require("coordinator.session.apisessions")
|
||||
|
||||
local core = require("graphics.core")
|
||||
|
||||
local log_render = coordinator.log_render
|
||||
local log_sys = coordinator.log_sys
|
||||
local log_comms = coordinator.log_comms
|
||||
|
||||
local threads = {}
|
||||
|
||||
local MAIN_CLOCK = 0.5 -- (2Hz, 10 ticks)
|
||||
local RENDER_SLEEP = 100 -- (100ms, 2 ticks)
|
||||
|
||||
local MQ__RENDER_CMD = {
|
||||
START_MAIN_UI = 1
|
||||
}
|
||||
|
||||
local MQ__RENDER_DATA = {
|
||||
MON_CONNECT = 1,
|
||||
MON_DISCONNECT = 2,
|
||||
MON_RESIZE = 3
|
||||
}
|
||||
|
||||
-- main thread
|
||||
---@nodiscard
|
||||
---@param smem crd_shared_memory
|
||||
function threads.thread__main(smem)
|
||||
---@class parallel_thread
|
||||
local public = {}
|
||||
|
||||
-- execute thread
|
||||
function public.exec()
|
||||
iocontrol.fp_rt_status("main", true)
|
||||
log.debug("main thread start")
|
||||
|
||||
local loop_clock = util.new_clock(MAIN_CLOCK)
|
||||
|
||||
-- start clock
|
||||
loop_clock.start()
|
||||
|
||||
log_sys("system started successfully")
|
||||
|
||||
-- load in from shared memory
|
||||
local crd_state = smem.crd_state
|
||||
local nic = smem.crd_sys.nic
|
||||
local coord_comms = smem.crd_sys.coord_comms
|
||||
local conn_watchdog = smem.crd_sys.conn_watchdog
|
||||
|
||||
-- event loop
|
||||
while true do
|
||||
local event, param1, param2, param3, param4, param5 = util.pull_event()
|
||||
|
||||
-- handle event
|
||||
if event == "peripheral_detach" then
|
||||
local type, device = ppm.handle_unmount(param1)
|
||||
|
||||
if type ~= nil and device ~= nil then
|
||||
if type == "modem" then
|
||||
-- we only really care if this is our wireless modem
|
||||
-- if it is another modem, handle other peripheral losses separately
|
||||
if nic.is_modem(device) then
|
||||
nic.disconnect()
|
||||
log_sys("comms modem disconnected")
|
||||
|
||||
local other_modem = ppm.get_wireless_modem()
|
||||
if other_modem then
|
||||
log_sys("found another wireless modem, using it for comms")
|
||||
nic.connect(other_modem)
|
||||
else
|
||||
-- close out main UI
|
||||
renderer.close_ui()
|
||||
|
||||
-- alert user to status
|
||||
log_sys("awaiting comms modem reconnect...")
|
||||
|
||||
iocontrol.fp_has_modem(false)
|
||||
end
|
||||
else
|
||||
log_sys("non-comms modem disconnected")
|
||||
end
|
||||
elseif type == "monitor" then
|
||||
smem.q.mq_render.push_data(MQ__RENDER_DATA.MON_DISCONNECT, device)
|
||||
elseif type == "speaker" then
|
||||
log_sys("lost alarm sounder speaker")
|
||||
iocontrol.fp_has_speaker(false)
|
||||
end
|
||||
end
|
||||
elseif event == "peripheral" then
|
||||
local type, device = ppm.mount(param1)
|
||||
|
||||
if type ~= nil and device ~= nil then
|
||||
if type == "modem" then
|
||||
if device.isWireless() and not nic.is_connected() then
|
||||
-- reconnected modem
|
||||
log_sys("comms modem reconnected")
|
||||
nic.connect(device)
|
||||
iocontrol.fp_has_modem(true)
|
||||
elseif device.isWireless() then
|
||||
log.info("unused wireless modem reconnected")
|
||||
else
|
||||
log_sys("wired modem reconnected")
|
||||
end
|
||||
elseif type == "monitor" then
|
||||
smem.q.mq_render.push_data(MQ__RENDER_DATA.MON_CONNECT, { name = param1, device = device })
|
||||
elseif type == "speaker" then
|
||||
log_sys("alarm sounder speaker reconnected")
|
||||
sounder.reconnect(device)
|
||||
iocontrol.fp_has_speaker(true)
|
||||
end
|
||||
end
|
||||
elseif event == "monitor_resize" then
|
||||
smem.q.mq_render.push_data(MQ__RENDER_DATA.MON_RESIZE, param1)
|
||||
elseif event == "timer" then
|
||||
if loop_clock.is_clock(param1) then
|
||||
-- main loop tick
|
||||
|
||||
-- toggle heartbeat
|
||||
iocontrol.heartbeat()
|
||||
|
||||
-- maintain connection
|
||||
if nic.is_connected() then
|
||||
local ok, start_ui = coord_comms.try_connect()
|
||||
if not ok then
|
||||
crd_state.link_fail = true
|
||||
crd_state.shutdown = true
|
||||
log_sys("supervisor connection failed, shutting down...")
|
||||
log.fatal("failed to connect to supervisor")
|
||||
break
|
||||
elseif start_ui then
|
||||
log_sys("supervisor connected, dispatching main UI start")
|
||||
smem.q.mq_render.push_command(MQ__RENDER_CMD.START_MAIN_UI)
|
||||
end
|
||||
end
|
||||
|
||||
-- iterate sessions and free any closed ones
|
||||
apisessions.iterate_all()
|
||||
apisessions.free_all_closed()
|
||||
|
||||
if renderer.ui_ready() then
|
||||
-- update clock used on main and flow monitors
|
||||
iocontrol.get_db().facility.ps.publish("date_time", os.date(smem.date_format))
|
||||
end
|
||||
|
||||
loop_clock.start()
|
||||
elseif conn_watchdog.is_timer(param1) then
|
||||
-- supervisor watchdog timeout
|
||||
log_comms("supervisor server timeout")
|
||||
|
||||
-- close connection, main UI, and stop sounder
|
||||
coord_comms.close()
|
||||
renderer.close_ui()
|
||||
sounder.stop()
|
||||
else
|
||||
-- a non-clock/main watchdog timer event
|
||||
|
||||
-- check API watchdogs
|
||||
apisessions.check_all_watchdogs(param1)
|
||||
|
||||
-- notify timer callback dispatcher
|
||||
tcd.handle(param1)
|
||||
end
|
||||
elseif event == "modem_message" then
|
||||
-- got a packet
|
||||
local packet = coord_comms.parse_packet(param1, param2, param3, param4, param5)
|
||||
|
||||
-- handle then check if it was a disconnect
|
||||
if coord_comms.handle_packet(packet) then
|
||||
log_comms("supervisor closed connection")
|
||||
|
||||
-- close connection, main UI, and stop sounder
|
||||
coord_comms.close()
|
||||
renderer.close_ui()
|
||||
sounder.stop()
|
||||
end
|
||||
elseif event == "monitor_touch" or event == "mouse_click" or event == "mouse_up" or
|
||||
event == "mouse_drag" or event == "mouse_scroll" or event == "double_click" then
|
||||
-- handle a mouse event
|
||||
renderer.handle_mouse(core.events.new_mouse_event(event, param1, param2, param3))
|
||||
elseif event == "speaker_audio_empty" then
|
||||
-- handle speaker buffer emptied
|
||||
sounder.continue()
|
||||
end
|
||||
|
||||
-- check for termination request or UI crash
|
||||
if event == "terminate" or ppm.should_terminate() then
|
||||
crd_state.shutdown = true
|
||||
log.info("terminate requested, main thread exiting")
|
||||
elseif not crd_state.ui_ok then
|
||||
crd_state.shutdown = true
|
||||
log.info("terminating due to fatal UI error")
|
||||
end
|
||||
|
||||
if crd_state.shutdown then
|
||||
-- handle closing supervisor connection
|
||||
coord_comms.try_connect(true)
|
||||
|
||||
if coord_comms.is_linked() then
|
||||
log_comms("closing supervisor connection...")
|
||||
else crd_state.link_fail = true end
|
||||
|
||||
coord_comms.close()
|
||||
log_comms("supervisor connection closed")
|
||||
|
||||
-- handle API sessions
|
||||
log_comms("closing api sessions...")
|
||||
apisessions.close_all()
|
||||
log_comms("api sessions closed")
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- execute the thread in a protected mode, retrying it on return if not shutting down
|
||||
function public.p_exec()
|
||||
local crd_state = smem.crd_state
|
||||
|
||||
while not crd_state.shutdown do
|
||||
local status, result = pcall(public.exec)
|
||||
if status == false then
|
||||
log.fatal(util.strval(result))
|
||||
end
|
||||
|
||||
iocontrol.fp_rt_status("main", false)
|
||||
|
||||
-- if status is true, then we are probably exiting, so this won't matter
|
||||
-- this thread cannot be slept because it will miss events (namely "terminate")
|
||||
if not crd_state.shutdown then
|
||||
log.info("main thread restarting now...")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return public
|
||||
end
|
||||
|
||||
-- coordinator renderer thread, tasked with long duration re-draws
|
||||
---@nodiscard
|
||||
---@param smem crd_shared_memory
|
||||
function threads.thread__render(smem)
|
||||
---@class parallel_thread
|
||||
local public = {}
|
||||
|
||||
-- execute thread
|
||||
function public.exec()
|
||||
iocontrol.fp_rt_status("render", true)
|
||||
log.debug("render thread start")
|
||||
|
||||
-- load in from shared memory
|
||||
local crd_state = smem.crd_state
|
||||
local render_queue = smem.q.mq_render
|
||||
|
||||
local last_update = util.time()
|
||||
|
||||
-- thread loop
|
||||
while true do
|
||||
-- check for messages in the message queue
|
||||
while render_queue.ready() and not crd_state.shutdown do
|
||||
local msg = render_queue.pop()
|
||||
|
||||
if msg ~= nil then
|
||||
if msg.qtype == mqueue.TYPE.COMMAND then
|
||||
-- received a command
|
||||
if msg.message == MQ__RENDER_CMD.START_MAIN_UI then
|
||||
-- stop the UI if it was already started
|
||||
-- this may occur on a quick supervisor disconnect -> connect
|
||||
if renderer.ui_ready() then
|
||||
log_render("closing main UI before executing new request to start")
|
||||
renderer.close_ui()
|
||||
end
|
||||
|
||||
-- start up the main UI
|
||||
log_render("starting main UI...")
|
||||
|
||||
local draw_start = util.time_ms()
|
||||
|
||||
local ui_message
|
||||
crd_state.ui_ok, ui_message = renderer.try_start_ui()
|
||||
if not crd_state.ui_ok then
|
||||
log_render(util.c("main UI error: ", ui_message))
|
||||
log.fatal(util.c("main GUI render failed with error ", ui_message))
|
||||
else
|
||||
log_render("main UI draw took " .. (util.time_ms() - draw_start) .. "ms")
|
||||
end
|
||||
end
|
||||
elseif msg.qtype == mqueue.TYPE.DATA then
|
||||
-- received data
|
||||
local cmd = msg.message ---@type queue_data
|
||||
|
||||
if cmd.key == MQ__RENDER_DATA.MON_CONNECT then
|
||||
-- monitor connected
|
||||
if renderer.handle_reconnect(cmd.val.name, cmd.val.device) then
|
||||
log_sys(util.c("configured monitor ", cmd.val.name, " reconnected"))
|
||||
else
|
||||
log_sys(util.c("unused monitor ", cmd.val.name, " connected"))
|
||||
end
|
||||
elseif cmd.key == MQ__RENDER_DATA.MON_DISCONNECT then
|
||||
-- monitor disconnected
|
||||
if renderer.handle_disconnect(cmd.val) then
|
||||
log_sys("lost a configured monitor")
|
||||
else
|
||||
log_sys("lost an unused monitor")
|
||||
end
|
||||
elseif cmd.key == MQ__RENDER_DATA.MON_RESIZE then
|
||||
-- monitor resized
|
||||
local is_used, is_ok = renderer.handle_resize(cmd.val)
|
||||
if is_used then
|
||||
log_sys(util.c("configured monitor ", cmd.val, " resized, ", util.trinary(is_ok, "display fits", "display does not fit")))
|
||||
end
|
||||
end
|
||||
elseif msg.qtype == mqueue.TYPE.PACKET then
|
||||
-- received a packet
|
||||
end
|
||||
end
|
||||
|
||||
-- quick yield
|
||||
util.nop()
|
||||
end
|
||||
|
||||
-- check for termination request
|
||||
if crd_state.shutdown then
|
||||
log.info("render thread exiting")
|
||||
break
|
||||
end
|
||||
|
||||
-- delay before next check
|
||||
last_update = util.adaptive_delay(RENDER_SLEEP, last_update)
|
||||
end
|
||||
end
|
||||
|
||||
-- execute the thread in a protected mode, retrying it on return if not shutting down
|
||||
function public.p_exec()
|
||||
local crd_state = smem.crd_state
|
||||
|
||||
while not crd_state.shutdown do
|
||||
local status, result = pcall(public.exec)
|
||||
if status == false then
|
||||
log.fatal(util.strval(result))
|
||||
end
|
||||
|
||||
iocontrol.fp_rt_status("render", false)
|
||||
|
||||
if not crd_state.shutdown then
|
||||
log.info("render thread restarting in 5 seconds...")
|
||||
util.psleep(5)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return public
|
||||
end
|
||||
|
||||
return threads
|
||||
@@ -14,31 +14,31 @@ local VerticalBar = require("graphics.elements.indicators.vbar")
|
||||
local cpair = core.cpair
|
||||
local border = core.border
|
||||
|
||||
local text_fg_bg = style.text_colors
|
||||
local lu_col = style.lu_colors
|
||||
|
||||
-- new boiler view
|
||||
---@param root graphics_element parent
|
||||
---@param x integer top left x
|
||||
---@param y integer top left y
|
||||
---@param ps psil ps interface
|
||||
local function new_view(root, x, y, ps)
|
||||
local text_fg = style.theme.text_fg
|
||||
local lu_col = style.lu_colors
|
||||
|
||||
local db = iocontrol.get_db()
|
||||
|
||||
local boiler = Rectangle{parent=root,border=border(1,colors.gray,true),width=31,height=7,x=x,y=y}
|
||||
|
||||
local status = StateIndicator{parent=boiler,x=9,y=1,states=style.boiler.states,value=1,min_width=12}
|
||||
local temp = DataIndicator{parent=boiler,x=5,y=3,lu_colors=lu_col,label="Temp:",unit=db.temp_label,format="%10.2f",value=0,commas=true,width=22,fg_bg=text_fg_bg}
|
||||
local boil_r = DataIndicator{parent=boiler,x=5,y=4,lu_colors=lu_col,label="Boil:",unit="mB/t",format="%10.0f",value=0,commas=true,width=22,fg_bg=text_fg_bg}
|
||||
local temp = DataIndicator{parent=boiler,x=5,y=3,lu_colors=lu_col,label="Temp:",unit=db.temp_label,format="%10.2f",value=0,commas=true,width=22,fg_bg=text_fg}
|
||||
local boil_r = DataIndicator{parent=boiler,x=5,y=4,lu_colors=lu_col,label="Boil:",unit="mB/t",format="%10.0f",value=0,commas=true,width=22,fg_bg=text_fg}
|
||||
|
||||
status.register(ps, "computed_status", status.update)
|
||||
temp.register(ps, "temperature", function (t) temp.update(db.temp_convert(t)) end)
|
||||
boil_r.register(ps, "boil_rate", boil_r.update)
|
||||
|
||||
TextBox{parent=boiler,text="H",x=2,y=5,height=1,width=1,fg_bg=text_fg_bg}
|
||||
TextBox{parent=boiler,text="W",x=3,y=5,height=1,width=1,fg_bg=text_fg_bg}
|
||||
TextBox{parent=boiler,text="S",x=27,y=5,height=1,width=1,fg_bg=text_fg_bg}
|
||||
TextBox{parent=boiler,text="C",x=28,y=5,height=1,width=1,fg_bg=text_fg_bg}
|
||||
TextBox{parent=boiler,text="H",x=2,y=5,height=1,width=1,fg_bg=text_fg}
|
||||
TextBox{parent=boiler,text="W",x=3,y=5,height=1,width=1,fg_bg=text_fg}
|
||||
TextBox{parent=boiler,text="S",x=27,y=5,height=1,width=1,fg_bg=text_fg}
|
||||
TextBox{parent=boiler,text="C",x=28,y=5,height=1,width=1,fg_bg=text_fg}
|
||||
|
||||
local hcool = VerticalBar{parent=boiler,x=2,y=1,fg_bg=cpair(colors.orange,colors.gray),height=4,width=1}
|
||||
local water = VerticalBar{parent=boiler,x=3,y=1,fg_bg=cpair(colors.blue,colors.gray),height=4,width=1}
|
||||
|
||||
@@ -9,6 +9,7 @@ local Rectangle = require("graphics.elements.rectangle")
|
||||
local TextBox = require("graphics.elements.textbox")
|
||||
|
||||
local DataIndicator = require("graphics.elements.indicators.data")
|
||||
local IndicatorLight = require("graphics.elements.indicators.light")
|
||||
local PowerIndicator = require("graphics.elements.indicators.power")
|
||||
local StateIndicator = require("graphics.elements.indicators.state")
|
||||
local VerticalBar = require("graphics.elements.indicators.vbar")
|
||||
@@ -18,9 +19,6 @@ local border = core.border
|
||||
|
||||
local ALIGN = core.ALIGN
|
||||
|
||||
local text_fg_bg = style.text_colors
|
||||
local lu_col = style.lu_colors
|
||||
|
||||
-- new induction matrix view
|
||||
---@param root graphics_element parent
|
||||
---@param x integer top left x
|
||||
@@ -29,57 +27,67 @@ local lu_col = style.lu_colors
|
||||
---@param ps psil ps interface
|
||||
---@param id number? matrix ID
|
||||
local function new_view(root, x, y, data, ps, id)
|
||||
local label_fg = style.theme.label_fg
|
||||
local text_fg = style.theme.text_fg
|
||||
local lu_col = style.lu_colors
|
||||
|
||||
local ind_yel = style.ind_yel
|
||||
local ind_wht = style.ind_wht
|
||||
|
||||
local title = "INDUCTION MATRIX"
|
||||
if type(id) == "number" then title = title .. id end
|
||||
|
||||
local matrix = Div{parent=root,fg_bg=style.root,width=33,height=24,x=x,y=y}
|
||||
|
||||
TextBox{parent=matrix,text=" ",width=33,height=1,x=1,y=1,fg_bg=style.lg_gray}
|
||||
TextBox{parent=matrix,text=title,alignment=ALIGN.CENTER,width=33,height=1,x=1,y=2,fg_bg=style.lg_gray}
|
||||
-- black has low contrast with dark gray, so if background is black use white instead
|
||||
local cutout_fg_bg = cpair(util.trinary(style.theme.bg == colors.black, colors.white, style.theme.bg), colors.gray)
|
||||
|
||||
TextBox{parent=matrix,text=" ",width=33,height=1,x=1,y=1,fg_bg=cutout_fg_bg}
|
||||
TextBox{parent=matrix,text=title,alignment=ALIGN.CENTER,width=33,height=1,x=1,y=2,fg_bg=cutout_fg_bg}
|
||||
|
||||
local rect = Rectangle{parent=matrix,border=border(1,colors.gray,true),width=33,height=22,x=1,y=3}
|
||||
|
||||
local label_fg_bg = cpair(colors.gray, colors.lightGray)
|
||||
|
||||
local status = StateIndicator{parent=rect,x=10,y=1,states=style.imatrix.states,value=1,min_width=14}
|
||||
local energy = PowerIndicator{parent=rect,x=7,y=3,lu_colors=lu_col,label="Energy: ",format="%8.2f",value=0,width=26,fg_bg=text_fg_bg}
|
||||
local capacity = PowerIndicator{parent=rect,x=7,y=4,lu_colors=lu_col,label="Capacity:",format="%8.2f",value=0,width=26,fg_bg=text_fg_bg}
|
||||
local input = PowerIndicator{parent=rect,x=7,y=5,lu_colors=lu_col,label="Input: ",format="%8.2f",rate=true,value=0,width=26,fg_bg=text_fg_bg}
|
||||
local output = PowerIndicator{parent=rect,x=7,y=6,lu_colors=lu_col,label="Output: ",format="%8.2f",rate=true,value=0,width=26,fg_bg=text_fg_bg}
|
||||
|
||||
local avg_chg = PowerIndicator{parent=rect,x=7,y=8,lu_colors=lu_col,label="Avg. Chg:",format="%8.2f",value=0,width=26,fg_bg=text_fg_bg}
|
||||
local avg_in = PowerIndicator{parent=rect,x=7,y=9,lu_colors=lu_col,label="Avg. In: ",format="%8.2f",rate=true,value=0,width=26,fg_bg=text_fg_bg}
|
||||
local avg_out = PowerIndicator{parent=rect,x=7,y=10,lu_colors=lu_col,label="Avg. Out:",format="%8.2f",rate=true,value=0,width=26,fg_bg=text_fg_bg}
|
||||
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 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 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 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 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 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 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 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}
|
||||
|
||||
status.register(ps, "computed_status", status.update)
|
||||
energy.register(ps, "energy", function (val) energy.update(util.joules_to_fe(val)) end)
|
||||
capacity.register(ps, "max_energy", function (val) capacity.update(util.joules_to_fe(val)) end)
|
||||
input.register(ps, "last_input", function (val) input.update(util.joules_to_fe(val)) end)
|
||||
output.register(ps, "last_output", function (val) output.update(util.joules_to_fe(val)) end)
|
||||
|
||||
energy.register(ps, "energy", function (val) energy.update(util.joules_to_fe(val)) end)
|
||||
avg_chg.register(ps, "avg_charge", avg_chg.update)
|
||||
input.register(ps, "last_input", function (val) input.update(util.joules_to_fe(val)) end)
|
||||
avg_in.register(ps, "avg_inflow", avg_in.update)
|
||||
output.register(ps, "last_output", function (val) output.update(util.joules_to_fe(val)) end)
|
||||
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)
|
||||
|
||||
local fill = DataIndicator{parent=rect,x=11,y=12,lu_colors=lu_col,label="Fill:",unit="%",format="%8.2f",value=0,width=18,fg_bg=text_fg_bg}
|
||||
|
||||
local cells = DataIndicator{parent=rect,x=11,y=14,lu_colors=lu_col,label="Cells: ",format="%7d",value=0,width=18,fg_bg=text_fg_bg}
|
||||
local providers = DataIndicator{parent=rect,x=11,y=15,lu_colors=lu_col,label="Providers:",format="%7d",value=0,width=18,fg_bg=text_fg_bg}
|
||||
|
||||
TextBox{parent=rect,text="Transfer Capacity",x=11,y=17,height=1,width=17,fg_bg=label_fg_bg}
|
||||
local trans_cap = PowerIndicator{parent=rect,x=19,y=18,lu_colors=lu_col,label="",format="%5.2f",rate=true,value=0,width=12,fg_bg=text_fg_bg}
|
||||
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 providers = DataIndicator{parent=rect,x=11,y=14,lu_colors=lu_col,label="Providers:",format="%7d",value=0,width=18,fg_bg=text_fg}
|
||||
|
||||
fill.register(ps, "energy_fill", function (val) fill.update(val * 100) end)
|
||||
cells.register(ps, "cells", cells.update)
|
||||
providers.register(ps, "providers", providers.update)
|
||||
fill.register(ps, "energy_fill", function (val) fill.update(val * 100) end)
|
||||
trans_cap.register(ps, "transfer_cap", function (val) trans_cap.update(util.joules_to_fe(val)) end)
|
||||
|
||||
local chging = IndicatorLight{parent=rect,x=11,y=16,label="Charging",colors=ind_wht}
|
||||
local dischg = IndicatorLight{parent=rect,x=11,y=17,label="Discharging",colors=ind_wht}
|
||||
local max_io = IndicatorLight{parent=rect,x=11,y=18,label="Max I/O Rate",colors=ind_yel}
|
||||
|
||||
chging.register(ps, "is_charging", chging.update)
|
||||
dischg.register(ps, "is_discharging", dischg.update)
|
||||
max_io.register(ps, "at_max_io", max_io.update)
|
||||
|
||||
local charge = VerticalBar{parent=rect,x=2,y=2,fg_bg=cpair(colors.green,colors.gray),height=17,width=4}
|
||||
local in_cap = VerticalBar{parent=rect,x=7,y=12,fg_bg=cpair(colors.red,colors.gray),height=7,width=1}
|
||||
local out_cap = VerticalBar{parent=rect,x=9,y=12,fg_bg=cpair(colors.blue,colors.gray),height=7,width=1}
|
||||
|
||||
TextBox{parent=rect,text="FILL",x=2,y=20,height=1,width=4,fg_bg=text_fg_bg}
|
||||
TextBox{parent=rect,text="I/O",x=7,y=20,height=1,width=3,fg_bg=text_fg_bg}
|
||||
TextBox{parent=rect,text="FILL I/O",x=2,y=20,height=1,width=8,fg_bg=label_fg}
|
||||
|
||||
local function calc_saturation(val)
|
||||
if (type(data.build) == "table") and (type(data.build.transfer_cap) == "number") and (data.build.transfer_cap > 0) then
|
||||
@@ -90,6 +98,49 @@ local function new_view(root, x, y, data, ps, id)
|
||||
charge.register(ps, "energy_fill", charge.update)
|
||||
in_cap.register(ps, "last_input", function (val) in_cap.update(calc_saturation(val)) end)
|
||||
out_cap.register(ps, "last_output", function (val) out_cap.update(calc_saturation(val)) end)
|
||||
|
||||
local eta = TextBox{parent=rect,x=11,y=20,width=20,height=1,text="ETA Unknown",alignment=ALIGN.CENTER,fg_bg=style.theme.field_box}
|
||||
|
||||
eta.register(ps, "eta_ms", function (eta_ms)
|
||||
local str, pre = "", util.trinary(eta_ms >= 0, "Full in ", "Empty in ")
|
||||
|
||||
local seconds = math.abs(eta_ms) / 1000
|
||||
local minutes = seconds / 60
|
||||
local hours = minutes / 60
|
||||
local days = hours / 24
|
||||
|
||||
if math.abs(eta_ms) < 1000 or (eta_ms ~= eta_ms) then
|
||||
-- really small or NaN
|
||||
str = "No ETA"
|
||||
elseif days < 1000 then
|
||||
days = math.floor(days)
|
||||
hours = math.floor(hours % 24)
|
||||
minutes = math.floor(minutes % 60)
|
||||
seconds = math.floor(seconds % 60)
|
||||
|
||||
if days > 0 then
|
||||
str = days .. "d"
|
||||
elseif hours > 0 then
|
||||
str = hours .. "h " .. minutes .. "m"
|
||||
elseif minutes > 0 then
|
||||
str = minutes .. "m " .. seconds .. "s"
|
||||
elseif seconds > 0 then
|
||||
str = seconds .. "s"
|
||||
end
|
||||
|
||||
str = pre .. str
|
||||
else
|
||||
local years = math.floor(days / 365.25)
|
||||
|
||||
if years <= 99999999 then
|
||||
str = pre .. years .. "y"
|
||||
else
|
||||
str = pre .. "eras"
|
||||
end
|
||||
end
|
||||
|
||||
eta.set_value(str)
|
||||
end)
|
||||
end
|
||||
|
||||
return new_view
|
||||
|
||||
@@ -17,33 +17,35 @@ local ALIGN = core.ALIGN
|
||||
|
||||
local cpair = core.cpair
|
||||
|
||||
local text_fg_bg = style.text_colors
|
||||
local lg_wh = style.lg_white
|
||||
|
||||
-- create a pocket list entry
|
||||
---@param parent graphics_element parent
|
||||
---@param id integer PKT session ID
|
||||
local function init(parent, id)
|
||||
local s_hi_box = style.fp_theme.highlight_box
|
||||
local s_hi_bright = style.fp_theme.highlight_box_bright
|
||||
|
||||
local label_fg = style.fp.label_fg
|
||||
|
||||
local ps = iocontrol.get_db().fp.ps
|
||||
|
||||
-- root div
|
||||
local root = Div{parent=parent,x=2,y=2,height=4,width=parent.get_width()-2,hidden=true}
|
||||
local entry = Div{parent=root,x=2,y=1,height=3,fg_bg=style.bw_fg_bg}
|
||||
local entry = Div{parent=root,x=2,y=1,height=3,fg_bg=s_hi_bright}
|
||||
|
||||
local ps_prefix = "pkt_" .. id .. "_"
|
||||
|
||||
TextBox{parent=entry,x=1,y=1,text="",width=8,height=1,fg_bg=text_fg_bg}
|
||||
local pkt_addr = TextBox{parent=entry,x=1,y=2,text="@ C ??",alignment=ALIGN.CENTER,width=8,height=1,fg_bg=text_fg_bg,nav_active=cpair(colors.gray,colors.black)}
|
||||
TextBox{parent=entry,x=1,y=3,text="",width=8,height=1,fg_bg=text_fg_bg}
|
||||
TextBox{parent=entry,x=1,y=1,text="",width=8,height=1,fg_bg=s_hi_box}
|
||||
local pkt_addr = TextBox{parent=entry,x=1,y=2,text="@ C ??",alignment=ALIGN.CENTER,width=8,height=1,fg_bg=s_hi_box,nav_active=cpair(colors.gray,colors.black)}
|
||||
TextBox{parent=entry,x=1,y=3,text="",width=8,height=1,fg_bg=s_hi_box}
|
||||
pkt_addr.register(ps, ps_prefix .. "addr", pkt_addr.set_value)
|
||||
|
||||
TextBox{parent=entry,x=10,y=2,text="FW:",width=3,height=1}
|
||||
local pkt_fw_v = TextBox{parent=entry,x=14,y=2,text=" ------- ",width=20,height=1,fg_bg=lg_wh}
|
||||
local pkt_fw_v = TextBox{parent=entry,x=14,y=2,text=" ------- ",width=20,height=1,fg_bg=label_fg}
|
||||
pkt_fw_v.register(ps, ps_prefix .. "fw", pkt_fw_v.set_value)
|
||||
|
||||
TextBox{parent=entry,x=35,y=2,text="RTT:",width=4,height=1}
|
||||
local pkt_rtt = DataIndicator{parent=entry,x=40,y=2,label="",unit="",format="%5d",value=0,width=5,fg_bg=lg_wh}
|
||||
TextBox{parent=entry,x=46,y=2,text="ms",width=4,height=1,fg_bg=lg_wh}
|
||||
local pkt_rtt = DataIndicator{parent=entry,x=40,y=2,label="",unit="",format="%5d",value=0,width=5,fg_bg=label_fg}
|
||||
TextBox{parent=entry,x=46,y=2,text="ms",width=4,height=1,fg_bg=label_fg}
|
||||
pkt_rtt.register(ps, ps_prefix .. "rtt", pkt_rtt.update)
|
||||
pkt_rtt.register(ps, ps_prefix .. "rtt_color", pkt_rtt.recolor)
|
||||
|
||||
|
||||
@@ -29,16 +29,6 @@ local cpair = core.cpair
|
||||
local border = core.border
|
||||
|
||||
local bw_fg_bg = style.bw_fg_bg
|
||||
local lu_cpair = style.lu_colors
|
||||
local hzd_fg_bg = style.hzd_fg_bg
|
||||
local dis_colors = style.dis_colors
|
||||
|
||||
local gry_wht = style.gray_white
|
||||
|
||||
local ind_grn = style.ind_grn
|
||||
local ind_yel = style.ind_yel
|
||||
local ind_red = style.ind_red
|
||||
local ind_wht = style.ind_wht
|
||||
|
||||
local period = core.flasher.PERIOD
|
||||
|
||||
@@ -47,6 +37,19 @@ local period = core.flasher.PERIOD
|
||||
---@param x integer top left x
|
||||
---@param y integer top left y
|
||||
local function new_view(root, x, y)
|
||||
local s_hi_box = style.theme.highlight_box
|
||||
local s_field = style.theme.field_box
|
||||
|
||||
local lu_cpair = style.lu_colors
|
||||
local hzd_fg_bg = style.hzd_fg_bg
|
||||
local dis_colors = style.dis_colors
|
||||
local arrow_fg_bg = cpair(style.theme.label, s_hi_box.bkg)
|
||||
|
||||
local ind_grn = style.ind_grn
|
||||
local ind_yel = style.ind_yel
|
||||
local ind_red = style.ind_red
|
||||
local ind_wht = style.ind_wht
|
||||
|
||||
assert(root.get_height() >= (y + 24), "main display not of sufficient vertical resolution (add an additional row of monitors)")
|
||||
|
||||
local black = cpair(colors.black, colors.black)
|
||||
@@ -65,7 +68,7 @@ local function new_view(root, x, y)
|
||||
facility.ack_alarms_ack = ack_a.on_response
|
||||
|
||||
local all_ok = IndicatorLight{parent=main,y=5,label="Unit Systems Online",colors=ind_grn}
|
||||
local rad_mon = TriIndicatorLight{parent=main,label="Radiation Monitor",c1=colors.gray,c2=colors.yellow,c3=colors.green}
|
||||
local rad_mon = TriIndicatorLight{parent=main,label="Radiation Monitor",c1=style.ind_bkg,c2=ind_yel.fgd,c3=ind_grn.fgd}
|
||||
local ind_mat = IndicatorLight{parent=main,label="Induction Matrix",colors=ind_grn}
|
||||
local sps = IndicatorLight{parent=main,label="SPS Connected",colors=ind_grn}
|
||||
|
||||
@@ -103,11 +106,11 @@ local function new_view(root, x, y)
|
||||
gen_fault.register(facility.ps, "as_gen_fault", gen_fault.update)
|
||||
|
||||
TextBox{parent=main,y=23,text="Radiation",height=1,width=13,fg_bg=style.label}
|
||||
local radiation = RadIndicator{parent=main,label="",format="%9.3f",lu_colors=lu_cpair,width=13,fg_bg=bw_fg_bg}
|
||||
local radiation = RadIndicator{parent=main,label="",format="%9.3f",lu_colors=lu_cpair,width=13,fg_bg=s_field}
|
||||
radiation.register(facility.ps, "radiation", radiation.update)
|
||||
|
||||
TextBox{parent=main,x=15,y=23,text="Linked RTUs",height=1,width=11,fg_bg=style.label}
|
||||
local rtu_count = DataIndicator{parent=main,x=15,y=24,label="",format="%11d",value=0,lu_colors=lu_cpair,width=11,fg_bg=bw_fg_bg}
|
||||
local rtu_count = DataIndicator{parent=main,x=15,y=24,label="",format="%11d",value=0,lu_colors=lu_cpair,width=11,fg_bg=s_field}
|
||||
rtu_count.register(facility.ps, "rtu_count", rtu_count.update)
|
||||
|
||||
---------------------
|
||||
@@ -125,9 +128,9 @@ local function new_view(root, x, y)
|
||||
local burn_tag = Div{parent=targets,x=1,y=1,width=8,height=4,fg_bg=blk_pur}
|
||||
TextBox{parent=burn_tag,x=2,y=2,text="Burn Target",width=7,height=2}
|
||||
|
||||
local burn_target = Div{parent=targets,x=9,y=1,width=23,height=3,fg_bg=gry_wht}
|
||||
local b_target = SpinboxNumeric{parent=burn_target,x=11,y=1,whole_num_precision=4,fractional_precision=1,min=0.1,arrow_fg_bg=gry_wht,fg_bg=bw_fg_bg}
|
||||
TextBox{parent=burn_target,x=18,y=2,text="mB/t"}
|
||||
local burn_target = Div{parent=targets,x=9,y=1,width=23,height=3,fg_bg=s_hi_box}
|
||||
local b_target = SpinboxNumeric{parent=burn_target,x=11,y=1,whole_num_precision=4,fractional_precision=1,min=0.1,arrow_fg_bg=arrow_fg_bg,arrow_disable=style.theme.disabled}
|
||||
TextBox{parent=burn_target,x=18,y=2,text="mB/t",fg_bg=style.theme.label_fg}
|
||||
local burn_sum = DataIndicator{parent=targets,x=9,y=4,label="",format="%18.1f",value=0,unit="mB/t",commas=true,lu_colors=black,width=23,fg_bg=blk_brn}
|
||||
|
||||
b_target.register(facility.ps, "process_burn_target", b_target.set_value)
|
||||
@@ -136,20 +139,20 @@ local function new_view(root, x, y)
|
||||
local chg_tag = Div{parent=targets,x=1,y=6,width=8,height=4,fg_bg=blk_pur}
|
||||
TextBox{parent=chg_tag,x=2,y=2,text="Charge Target",width=7,height=2}
|
||||
|
||||
local chg_target = Div{parent=targets,x=9,y=6,width=23,height=3,fg_bg=gry_wht}
|
||||
local c_target = SpinboxNumeric{parent=chg_target,x=2,y=1,whole_num_precision=15,fractional_precision=0,min=0,arrow_fg_bg=gry_wht,fg_bg=bw_fg_bg}
|
||||
TextBox{parent=chg_target,x=18,y=2,text="MFE"}
|
||||
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}
|
||||
TextBox{parent=chg_target,x=18,y=2,text="MFE",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}
|
||||
|
||||
c_target.register(facility.ps, "process_charge_target", c_target.set_value)
|
||||
cur_charge.register(facility.induction_ps_tbl[1], "energy", function (j) cur_charge.update(util.joules_to_fe(j) / 1000000) end)
|
||||
cur_charge.register(facility.induction_ps_tbl[1], "avg_charge", function (fe) cur_charge.update(fe / 1000000) end)
|
||||
|
||||
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}
|
||||
|
||||
local gen_target = Div{parent=targets,x=9,y=11,width=23,height=3,fg_bg=gry_wht}
|
||||
local g_target = SpinboxNumeric{parent=gen_target,x=8,y=1,whole_num_precision=9,fractional_precision=0,min=0,arrow_fg_bg=gry_wht,fg_bg=bw_fg_bg}
|
||||
TextBox{parent=gen_target,x=18,y=2,text="kFE/t"}
|
||||
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}
|
||||
TextBox{parent=gen_target,x=18,y=2,text="kFE/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}
|
||||
|
||||
g_target.register(facility.ps, "process_gen_target", g_target.set_value)
|
||||
@@ -165,17 +168,17 @@ local function new_view(root, x, y)
|
||||
|
||||
for i = 1, 4 do
|
||||
local unit
|
||||
local tag_fg_bg = gry_wht
|
||||
local lim_fg_bg = style.lg_white
|
||||
local ctl_fg = colors.lightGray
|
||||
local cur_fg_bg = style.lg_white
|
||||
local cur_lu = colors.lightGray
|
||||
local tag_fg_bg = cpair(style.theme.disabled, s_hi_box.bkg)
|
||||
local lim_fg_bg = cpair(style.theme.disabled, s_hi_box.bkg)
|
||||
local label_fg = style.theme.disabled_fg
|
||||
local cur_fg_bg = cpair(style.theme.disabled, s_hi_box.bkg)
|
||||
local cur_lu = style.theme.disabled
|
||||
|
||||
if i <= facility.num_units then
|
||||
unit = units[i] ---@type ioctl_unit
|
||||
tag_fg_bg = cpair(colors.black,colors.lightBlue)
|
||||
lim_fg_bg = bw_fg_bg
|
||||
ctl_fg = colors.gray
|
||||
tag_fg_bg = cpair(colors.black, colors.lightBlue)
|
||||
lim_fg_bg = s_hi_box
|
||||
label_fg = style.theme.label_fg
|
||||
cur_fg_bg = blk_brn
|
||||
cur_lu = colors.black
|
||||
end
|
||||
@@ -185,9 +188,9 @@ local function new_view(root, x, y)
|
||||
local unit_tag = Div{parent=limit_div,x=1,y=_y,width=8,height=4,fg_bg=tag_fg_bg}
|
||||
TextBox{parent=unit_tag,x=2,y=2,text="Unit "..i.." Limit",width=7,height=2}
|
||||
|
||||
local lim_ctl = Div{parent=limit_div,x=9,y=_y,width=14,height=3,fg_bg=cpair(ctl_fg,colors.white)}
|
||||
local lim = SpinboxNumeric{parent=lim_ctl,x=2,y=1,whole_num_precision=4,fractional_precision=1,min=0.1,arrow_fg_bg=gry_wht,fg_bg=lim_fg_bg}
|
||||
TextBox{parent=lim_ctl,x=9,y=2,text="mB/t",width=4,height=1}
|
||||
local lim_ctl = Div{parent=limit_div,x=9,y=_y,width=14,height=3,fg_bg=s_hi_box}
|
||||
local lim = SpinboxNumeric{parent=lim_ctl,x=2,y=1,whole_num_precision=4,fractional_precision=1,min=0.1,arrow_fg_bg=arrow_fg_bg,arrow_disable=style.theme.disabled,fg_bg=lim_fg_bg}
|
||||
TextBox{parent=lim_ctl,x=9,y=2,text="mB/t",width=4,height=1,fg_bg=label_fg}
|
||||
|
||||
local cur_burn = DataIndicator{parent=limit_div,x=9,y=_y+3,label="",format="%7.1f",value=0,unit="mB/t",commas=false,lu_colors=cpair(cur_lu,cur_lu),width=14,fg_bg=cur_fg_bg}
|
||||
|
||||
@@ -209,14 +212,14 @@ local function new_view(root, x, y)
|
||||
local stat_div = Div{parent=proc,width=22,height=24,x=57,y=6}
|
||||
|
||||
for i = 1, 4 do
|
||||
local tag_fg_bg = gry_wht
|
||||
local ind_fg_bg = style.lg_white
|
||||
local ind_off = colors.lightGray
|
||||
local tag_fg_bg = cpair(style.theme.disabled, s_hi_box.bkg)
|
||||
local ind_fg_bg = cpair(style.theme.disabled, s_hi_box.bkg)
|
||||
local ind_off = style.theme.disabled
|
||||
|
||||
if i <= facility.num_units then
|
||||
tag_fg_bg = cpair(colors.black, colors.cyan)
|
||||
ind_fg_bg = bw_fg_bg
|
||||
ind_off = colors.gray
|
||||
ind_fg_bg = cpair(style.theme.text, s_hi_box.bkg)
|
||||
ind_off = style.ind_hi_box_bg
|
||||
end
|
||||
|
||||
local _y = ((i - 1) * 5) + 1
|
||||
@@ -225,8 +228,8 @@ local function new_view(root, x, y)
|
||||
TextBox{parent=unit_tag,x=2,y=2,text="Unit "..i.." Status",width=7,height=2}
|
||||
|
||||
local lights = Div{parent=stat_div,x=9,y=_y,width=14,height=4,fg_bg=ind_fg_bg}
|
||||
local ready = IndicatorLight{parent=lights,x=2,y=2,label="Ready",colors=cpair(colors.green,ind_off)}
|
||||
local degraded = IndicatorLight{parent=lights,x=2,y=3,label="Degraded",colors=cpair(colors.red,ind_off),flash=true,period=period.BLINK_250_MS}
|
||||
local ready = IndicatorLight{parent=lights,x=2,y=2,label="Ready",colors=cpair(ind_grn.fgd,ind_off)}
|
||||
local degraded = IndicatorLight{parent=lights,x=2,y=3,label="Degraded",colors=cpair(ind_red.fgd,ind_off),flash=true,period=period.BLINK_250_MS}
|
||||
|
||||
if i <= facility.num_units then
|
||||
local unit = units[i] ---@type ioctl_unit
|
||||
@@ -241,18 +244,18 @@ local function new_view(root, x, y)
|
||||
-------------------------
|
||||
|
||||
local ctl_opts = { "Monitored Max Burn", "Combined Burn Rate", "Charge Level", "Generation Rate" }
|
||||
local mode = RadioButton{parent=proc,x=34,y=1,options=ctl_opts,callback=function()end,radio_colors=cpair(colors.gray,colors.white),select_color=colors.purple}
|
||||
local mode = RadioButton{parent=proc,x=34,y=1,options=ctl_opts,callback=function()end,radio_colors=cpair(style.theme.accent_dark,style.theme.accent_light),select_color=colors.purple}
|
||||
|
||||
mode.register(facility.ps, "process_mode", mode.set_value)
|
||||
|
||||
local u_stat = Rectangle{parent=proc,border=border(1,colors.gray,true),thin=true,width=31,height=4,x=1,y=16,fg_bg=bw_fg_bg}
|
||||
local stat_line_1 = TextBox{parent=u_stat,x=1,y=1,text="UNKNOWN",width=31,height=1,alignment=ALIGN.CENTER,fg_bg=bw_fg_bg}
|
||||
local stat_line_2 = TextBox{parent=u_stat,x=1,y=2,text="awaiting data...",width=31,height=1,alignment=ALIGN.CENTER,fg_bg=gry_wht}
|
||||
local stat_line_2 = TextBox{parent=u_stat,x=1,y=2,text="awaiting data...",width=31,height=1,alignment=ALIGN.CENTER,fg_bg=cpair(colors.gray,colors.white)}
|
||||
|
||||
stat_line_1.register(facility.ps, "status_line_1", stat_line_1.set_value)
|
||||
stat_line_2.register(facility.ps, "status_line_2", stat_line_2.set_value)
|
||||
|
||||
local auto_controls = Div{parent=proc,x=1,y=20,width=31,height=5,fg_bg=gry_wht}
|
||||
local auto_controls = Div{parent=proc,x=1,y=20,width=31,height=5,fg_bg=s_hi_box}
|
||||
|
||||
-- save the automatic process control configuration without starting
|
||||
local function _save_cfg()
|
||||
@@ -327,40 +330,36 @@ local function new_view(root, x, y)
|
||||
|
||||
local waste_sel = Div{parent=proc,width=21,height=24,x=81,y=1}
|
||||
|
||||
TextBox{parent=waste_sel,text=" ",width=21,height=1,x=1,y=1,fg_bg=blk_brn}
|
||||
TextBox{parent=waste_sel,text="WASTE PRODUCTION",alignment=ALIGN.CENTER,width=21,height=1,x=1,y=2,fg_bg=cpair(colors.lightGray,colors.brown)}
|
||||
local cutout_fg_bg = cpair(style.theme.bg, colors.brown)
|
||||
|
||||
TextBox{parent=waste_sel,text=" ",width=21,height=1,x=1,y=1,fg_bg=cutout_fg_bg}
|
||||
TextBox{parent=waste_sel,text="WASTE PRODUCTION",alignment=ALIGN.CENTER,width=21,height=1,x=1,y=2,fg_bg=cutout_fg_bg}
|
||||
|
||||
local rect = Rectangle{parent=waste_sel,border=border(1,colors.brown,true),width=21,height=22,x=1,y=3}
|
||||
local status = StateIndicator{parent=rect,x=2,y=1,states=style.waste.states,value=1,min_width=17}
|
||||
|
||||
status.register(facility.ps, "current_waste_product", status.update)
|
||||
|
||||
local waste_prod = RadioButton{parent=rect,x=2,y=3,options=style.waste.options,callback=process.set_process_waste,radio_colors=cpair(colors.gray,colors.white),select_color=colors.brown}
|
||||
local pu_fallback = Checkbox{parent=rect,x=2,y=7,label="Pu Fallback",callback=process.set_pu_fallback,box_fg_bg=cpair(colors.green,colors.black)}
|
||||
local waste_prod = RadioButton{parent=rect,x=2,y=3,options=style.waste.options,callback=process.set_process_waste,radio_colors=cpair(style.theme.accent_dark,style.theme.accent_light),select_color=colors.brown}
|
||||
|
||||
waste_prod.register(facility.ps, "process_waste_product", waste_prod.set_value)
|
||||
pu_fallback.register(facility.ps, "process_pu_fallback", pu_fallback.set_value)
|
||||
|
||||
local fb_active = IndicatorLight{parent=rect,x=2,y=9,label="Fallback Active",colors=ind_wht}
|
||||
local fb_active = IndicatorLight{parent=rect,x=2,y=7,label="Fallback Active",colors=ind_wht}
|
||||
local sps_disabled = IndicatorLight{parent=rect,x=2,y=8,label="SPS Disabled LC",colors=ind_yel}
|
||||
|
||||
fb_active.register(facility.ps, "pu_fallback_active", fb_active.update)
|
||||
sps_disabled.register(facility.ps, "sps_disabled_low_power", sps_disabled.update)
|
||||
|
||||
TextBox{parent=rect,x=2,y=11,text="Plutonium Rate",height=1,width=17,fg_bg=style.label}
|
||||
local pu_rate = DataIndicator{parent=rect,x=2,label="",unit="mB/t",format="%12.2f",value=0,lu_colors=lu_cpair,fg_bg=bw_fg_bg,width=17}
|
||||
local pu_fallback = Checkbox{parent=rect,x=2,y=10,label="Pu Fallback",callback=process.set_pu_fallback,box_fg_bg=cpair(colors.brown,style.theme.checkbox_bg)}
|
||||
|
||||
TextBox{parent=rect,x=2,y=14,text="Polonium Rate",height=1,width=17,fg_bg=style.label}
|
||||
local po_rate = DataIndicator{parent=rect,x=2,label="",unit="mB/t",format="%12.2f",value=0,lu_colors=lu_cpair,fg_bg=bw_fg_bg,width=17}
|
||||
TextBox{parent=rect,x=2,y=12,height=3,text="Switch to Pu when SNAs cannot keep up with waste.",fg_bg=style.label}
|
||||
|
||||
TextBox{parent=rect,x=2,y=17,text="Antimatter Rate",height=1,width=17,fg_bg=style.label}
|
||||
local am_rate = DataIndicator{parent=rect,x=2,label="",unit="\xb5B/t",format="%12d",value=0,lu_colors=lu_cpair,fg_bg=bw_fg_bg,width=17}
|
||||
local lc_sps = Checkbox{parent=rect,x=2,y=16,label="Low Charge SPS",callback=process.set_sps_low_power,box_fg_bg=cpair(colors.brown,style.theme.checkbox_bg)}
|
||||
|
||||
pu_rate.register(facility.ps, "pu_rate", pu_rate.update)
|
||||
po_rate.register(facility.ps, "po_rate", po_rate.update)
|
||||
am_rate.register(facility.ps, "am_rate", am_rate.update)
|
||||
TextBox{parent=rect,x=2,y=18,height=3,text="Use SPS at low charge, otherwise switches to Po.",fg_bg=style.label}
|
||||
|
||||
local sna_count = DataIndicator{parent=rect,x=2,y=20,label="Linked SNAs:",format="%4d",value=0,lu_colors=lu_cpair,width=17}
|
||||
|
||||
sna_count.register(facility.ps, "sna_count", sna_count.update)
|
||||
pu_fallback.register(facility.ps, "process_pu_fallback", pu_fallback.set_value)
|
||||
lc_sps.register(facility.ps, "process_sps_low_power", lc_sps.set_value)
|
||||
end
|
||||
|
||||
return new_view
|
||||
|
||||
@@ -16,23 +16,23 @@ local StateIndicator = require("graphics.elements.indicators.state")
|
||||
local cpair = core.cpair
|
||||
local border = core.border
|
||||
|
||||
local text_fg_bg = style.text_colors
|
||||
local lu_col = style.lu_colors
|
||||
|
||||
-- create new reactor view
|
||||
---@param root graphics_element parent
|
||||
---@param x integer top left x
|
||||
---@param y integer top left y
|
||||
---@param ps psil ps interface
|
||||
local function new_view(root, x, y, ps)
|
||||
local text_fg = style.theme.text_fg
|
||||
local lu_col = style.lu_colors
|
||||
|
||||
local db = iocontrol.get_db()
|
||||
|
||||
local reactor = Rectangle{parent=root,border=border(1, colors.gray, true),width=30,height=7,x=x,y=y}
|
||||
local reactor = Rectangle{parent=root,border=border(1,colors.gray,true),width=30,height=7,x=x,y=y}
|
||||
|
||||
local status = StateIndicator{parent=reactor,x=6,y=1,states=style.reactor.states,value=1,min_width=16}
|
||||
local core_temp = DataIndicator{parent=reactor,x=2,y=3,lu_colors=lu_col,label="Core Temp:",unit=db.temp_label,format="%10.2f",value=0,commas=true,width=26,fg_bg=text_fg_bg}
|
||||
local burn_r = DataIndicator{parent=reactor,x=2,y=4,lu_colors=lu_col,label="Burn Rate:",unit="mB/t",format="%10.2f",value=0,width=26,fg_bg=text_fg_bg}
|
||||
local heating_r = DataIndicator{parent=reactor,x=2,y=5,lu_colors=lu_col,label="Heating:",unit="mB/t",format="%12.0f",value=0,commas=true,width=26,fg_bg=text_fg_bg}
|
||||
local core_temp = DataIndicator{parent=reactor,x=2,y=3,lu_colors=lu_col,label="Core Temp:",unit=db.temp_label,format="%10.2f",value=0,commas=true,width=26,fg_bg=text_fg}
|
||||
local burn_r = DataIndicator{parent=reactor,x=2,y=4,lu_colors=lu_col,label="Burn Rate:",unit="mB/t",format="%10.2f",value=0,width=26,fg_bg=text_fg}
|
||||
local heating_r = DataIndicator{parent=reactor,x=2,y=5,lu_colors=lu_col,label="Heating:",unit="mB/t",format="%12.0f",value=0,commas=true,width=26,fg_bg=text_fg}
|
||||
|
||||
status.register(ps, "computed_status", status.update)
|
||||
core_temp.register(ps, "temp", function (t) core_temp.update(db.temp_convert(t)) end)
|
||||
@@ -41,12 +41,12 @@ local function new_view(root, x, y, ps)
|
||||
|
||||
local reactor_fills = Rectangle{parent=root,border=border(1, colors.gray, true),width=24,height=7,x=(x + 29),y=y}
|
||||
|
||||
TextBox{parent=reactor_fills,text="FUEL",x=2,y=1,height=1,fg_bg=text_fg_bg}
|
||||
TextBox{parent=reactor_fills,text="COOL",x=2,y=2,height=1,fg_bg=text_fg_bg}
|
||||
TextBox{parent=reactor_fills,text="HCOOL",x=2,y=4,height=1,fg_bg=text_fg_bg}
|
||||
TextBox{parent=reactor_fills,text="WASTE",x=2,y=5,height=1,fg_bg=text_fg_bg}
|
||||
TextBox{parent=reactor_fills,text="FUEL",x=2,y=1,height=1,fg_bg=text_fg}
|
||||
TextBox{parent=reactor_fills,text="COOL",x=2,y=2,height=1,fg_bg=text_fg}
|
||||
TextBox{parent=reactor_fills,text="HCOOL",x=2,y=4,height=1,fg_bg=text_fg}
|
||||
TextBox{parent=reactor_fills,text="WASTE",x=2,y=5,height=1,fg_bg=text_fg}
|
||||
|
||||
local fuel = HorizontalBar{parent=reactor_fills,x=8,y=1,show_percent=true,bar_fg_bg=cpair(colors.black,colors.gray),height=1,width=14}
|
||||
local fuel = HorizontalBar{parent=reactor_fills,x=8,y=1,show_percent=true,bar_fg_bg=cpair(style.theme.fuel_color,colors.gray),height=1,width=14}
|
||||
local ccool = HorizontalBar{parent=reactor_fills,x=8,y=2,show_percent=true,bar_fg_bg=cpair(colors.blue,colors.gray),height=1,width=14}
|
||||
local hcool = HorizontalBar{parent=reactor_fills,x=8,y=4,show_percent=true,bar_fg_bg=cpair(colors.white,colors.gray),height=1,width=14}
|
||||
local waste = HorizontalBar{parent=reactor_fills,x=8,y=5,show_percent=true,bar_fg_bg=cpair(colors.brown,colors.gray),height=1,width=14}
|
||||
|
||||
@@ -15,20 +15,20 @@ local VerticalBar = require("graphics.elements.indicators.vbar")
|
||||
local cpair = core.cpair
|
||||
local border = core.border
|
||||
|
||||
local text_fg_bg = style.text_colors
|
||||
local lu_col = style.lu_colors
|
||||
|
||||
-- new turbine view
|
||||
---@param root graphics_element parent
|
||||
---@param x integer top left x
|
||||
---@param y integer top left y
|
||||
---@param ps psil ps interface
|
||||
local function new_view(root, x, y, ps)
|
||||
local text_fg = style.theme.text_fg
|
||||
local lu_col = style.lu_colors
|
||||
|
||||
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 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_bg}
|
||||
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_bg}
|
||||
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 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)
|
||||
prod_rate.register(ps, "prod_rate", function (val) prod_rate.update(util.joules_to_fe(val)) end)
|
||||
@@ -37,8 +37,8 @@ local function new_view(root, x, y, ps)
|
||||
local steam = VerticalBar{parent=turbine,x=2,y=1,fg_bg=cpair(colors.white,colors.gray),height=4,width=1}
|
||||
local energy = VerticalBar{parent=turbine,x=3,y=1,fg_bg=cpair(colors.green,colors.gray),height=4,width=1}
|
||||
|
||||
TextBox{parent=turbine,text="S",x=2,y=5,height=1,width=1,fg_bg=text_fg_bg}
|
||||
TextBox{parent=turbine,text="E",x=3,y=5,height=1,width=1,fg_bg=text_fg_bg}
|
||||
TextBox{parent=turbine,text="S",x=2,y=5,height=1,width=1,fg_bg=text_fg}
|
||||
TextBox{parent=turbine,text="E",x=3,y=5,height=1,width=1,fg_bg=text_fg}
|
||||
|
||||
steam.register(ps, "steam_fill", steam.update)
|
||||
energy.register(ps, "energy_fill", energy.update)
|
||||
|
||||
@@ -35,23 +35,30 @@ local cpair = core.cpair
|
||||
local border = core.border
|
||||
|
||||
local bw_fg_bg = style.bw_fg_bg
|
||||
local lu_cpair = style.lu_colors
|
||||
local hzd_fg_bg = style.hzd_fg_bg
|
||||
local dis_colors = style.dis_colors
|
||||
|
||||
local gry_wht = style.gray_white
|
||||
|
||||
local ind_grn = style.ind_grn
|
||||
local ind_yel = style.ind_yel
|
||||
local ind_red = style.ind_red
|
||||
local ind_wht = style.ind_wht
|
||||
|
||||
local period = core.flasher.PERIOD
|
||||
|
||||
-- create a unit view
|
||||
---@param parent graphics_element parent
|
||||
---@param id integer
|
||||
local function init(parent, id)
|
||||
local s_hi_box = style.theme.highlight_box
|
||||
local s_hi_bright = style.theme.highlight_box_bright
|
||||
local s_field = style.theme.field_box
|
||||
|
||||
local hc_text = style.hc_text
|
||||
local lu_cpair = style.lu_colors
|
||||
local hzd_fg_bg = style.hzd_fg_bg
|
||||
local dis_colors = style.dis_colors
|
||||
local arrow_fg_bg = cpair(style.theme.label, s_hi_box.bkg)
|
||||
|
||||
local ind_bkg = style.ind_bkg
|
||||
local ind_grn = style.ind_grn
|
||||
local ind_yel = style.ind_yel
|
||||
local ind_red = style.ind_red
|
||||
local ind_wht = style.ind_wht
|
||||
|
||||
local db = iocontrol.get_db()
|
||||
local unit = db.units[id] ---@type ioctl_unit
|
||||
local f_ps = db.facility.ps
|
||||
@@ -64,7 +71,7 @@ local function init(parent, id)
|
||||
local b_ps = unit.boiler_ps_tbl
|
||||
local t_ps = unit.turbine_ps_tbl
|
||||
|
||||
TextBox{parent=main,text="Reactor Unit #" .. id,alignment=ALIGN.CENTER,height=1,fg_bg=style.header}
|
||||
TextBox{parent=main,text="Reactor Unit #" .. id,alignment=ALIGN.CENTER,height=1,fg_bg=style.theme.header}
|
||||
|
||||
-----------------------------
|
||||
-- main stats and core map --
|
||||
@@ -75,11 +82,11 @@ local function init(parent, id)
|
||||
core_map.register(u_ps, "size", function (s) core_map.resize(s[1], s[2]) end)
|
||||
|
||||
TextBox{parent=main,x=12,y=22,text="Heating Rate",height=1,width=12,fg_bg=style.label}
|
||||
local heating_r = DataIndicator{parent=main,x=12,label="",format="%14.0f",value=0,unit="mB/t",commas=true,lu_colors=lu_cpair,width=19,fg_bg=bw_fg_bg}
|
||||
local heating_r = DataIndicator{parent=main,x=12,label="",format="%14.0f",value=0,unit="mB/t",commas=true,lu_colors=lu_cpair,width=19,fg_bg=s_field}
|
||||
heating_r.register(u_ps, "heating_rate", heating_r.update)
|
||||
|
||||
TextBox{parent=main,x=12,y=25,text="Commanded Burn Rate",height=1,width=19,fg_bg=style.label}
|
||||
local burn_r = DataIndicator{parent=main,x=12,label="",format="%14.2f",value=0,unit="mB/t",lu_colors=lu_cpair,width=19,fg_bg=bw_fg_bg}
|
||||
local burn_r = DataIndicator{parent=main,x=12,label="",format="%14.2f",value=0,unit="mB/t",lu_colors=lu_cpair,width=19,fg_bg=s_field}
|
||||
burn_r.register(u_ps, "burn_rate", burn_r.update)
|
||||
|
||||
TextBox{parent=main,text="F",x=2,y=22,width=1,height=1,fg_bg=style.label}
|
||||
@@ -89,7 +96,7 @@ local function init(parent, id)
|
||||
TextBox{parent=main,text="H",x=8,y=22,width=1,height=1,fg_bg=style.label}
|
||||
TextBox{parent=main,text="W",x=10,y=22,width=1,height=1,fg_bg=style.label}
|
||||
|
||||
local fuel = VerticalBar{parent=main,x=2,y=23,fg_bg=cpair(colors.black,colors.gray),height=4,width=1}
|
||||
local fuel = VerticalBar{parent=main,x=2,y=23,fg_bg=cpair(style.theme.fuel_color,colors.gray),height=4,width=1}
|
||||
local ccool = VerticalBar{parent=main,x=4,y=23,fg_bg=cpair(colors.blue,colors.gray),height=4,width=1}
|
||||
local hcool = VerticalBar{parent=main,x=8,y=23,fg_bg=cpair(colors.white,colors.gray),height=4,width=1}
|
||||
local waste = VerticalBar{parent=main,x=10,y=23,fg_bg=cpair(colors.brown,colors.gray),height=4,width=1}
|
||||
@@ -117,19 +124,19 @@ local function init(parent, id)
|
||||
|
||||
TextBox{parent=main,x=32,y=22,text="Core Temp",height=1,width=9,fg_bg=style.label}
|
||||
local fmt = util.trinary(string.len(db.temp_label) == 2, "%10.2f", "%11.2f")
|
||||
local core_temp = DataIndicator{parent=main,x=32,label="",format=fmt,value=0,commas=true,unit=db.temp_label,lu_colors=lu_cpair,width=13,fg_bg=bw_fg_bg}
|
||||
local core_temp = DataIndicator{parent=main,x=32,label="",format=fmt,value=0,commas=true,unit=db.temp_label,lu_colors=lu_cpair,width=13,fg_bg=s_field}
|
||||
core_temp.register(u_ps, "temp", function (t) core_temp.update(db.temp_convert(t)) end)
|
||||
|
||||
TextBox{parent=main,x=32,y=25,text="Burn Rate",height=1,width=9,fg_bg=style.label}
|
||||
local act_burn_r = DataIndicator{parent=main,x=32,label="",format="%8.2f",value=0,unit="mB/t",lu_colors=lu_cpair,width=13,fg_bg=bw_fg_bg}
|
||||
local act_burn_r = DataIndicator{parent=main,x=32,label="",format="%8.2f",value=0,unit="mB/t",lu_colors=lu_cpair,width=13,fg_bg=s_field}
|
||||
act_burn_r.register(u_ps, "act_burn_rate", act_burn_r.update)
|
||||
|
||||
TextBox{parent=main,x=32,y=28,text="Damage",height=1,width=6,fg_bg=style.label}
|
||||
local damage_p = DataIndicator{parent=main,x=32,label="",format="%11.0f",value=0,unit="%",lu_colors=lu_cpair,width=13,fg_bg=bw_fg_bg}
|
||||
local damage_p = DataIndicator{parent=main,x=32,label="",format="%11.0f",value=0,unit="%",lu_colors=lu_cpair,width=13,fg_bg=s_field}
|
||||
damage_p.register(u_ps, "damage", damage_p.update)
|
||||
|
||||
TextBox{parent=main,x=32,y=31,text="Radiation",height=1,width=21,fg_bg=style.label}
|
||||
local radiation = RadIndicator{parent=main,x=32,label="",format="%9.3f",lu_colors=lu_cpair,width=13,fg_bg=bw_fg_bg}
|
||||
local radiation = RadIndicator{parent=main,x=32,label="",format="%9.3f",lu_colors=lu_cpair,width=13,fg_bg=s_field}
|
||||
radiation.register(u_ps, "radiation", radiation.update)
|
||||
|
||||
-------------------
|
||||
@@ -152,9 +159,9 @@ local function init(parent, id)
|
||||
local annunciator = Div{parent=main,width=23,height=18,x=22,y=3}
|
||||
|
||||
-- connectivity
|
||||
local plc_online = IndicatorLight{parent=annunciator,label="PLC Online",colors=cpair(colors.green,colors.red)}
|
||||
local plc_online = IndicatorLight{parent=annunciator,label="PLC Online",colors=cpair(ind_grn.fgd,ind_red.fgd)}
|
||||
local plc_hbeat = IndicatorLight{parent=annunciator,label="PLC Heartbeat",colors=ind_wht}
|
||||
local rad_mon = TriIndicatorLight{parent=annunciator,label="Radiation Monitor",c1=colors.gray,c2=colors.yellow,c3=colors.green}
|
||||
local rad_mon = TriIndicatorLight{parent=annunciator,label="Radiation Monitor",c1=ind_bkg,c2=ind_yel.fgd,c3=ind_grn.fgd}
|
||||
|
||||
plc_online.register(u_ps, "PLCOnline", plc_online.update)
|
||||
plc_hbeat.register(u_ps, "PLCHeartbeat", plc_hbeat.update)
|
||||
@@ -211,7 +218,7 @@ local function init(parent, id)
|
||||
local rps_loc = IndicatorLight{parent=rps_annunc,label="Coolant Level Low Low",colors=ind_yel}
|
||||
local rps_flt = IndicatorLight{parent=rps_annunc,label="PPM Fault",colors=ind_yel,flash=true,period=period.BLINK_500_MS}
|
||||
local rps_tmo = IndicatorLight{parent=rps_annunc,label="Connection Timeout",colors=ind_yel,flash=true,period=period.BLINK_500_MS}
|
||||
local rps_sfl = IndicatorLight{parent=rps_annunc,label="System Failure",colors=cpair(colors.orange,colors.gray),flash=true,period=period.BLINK_500_MS}
|
||||
local rps_sfl = IndicatorLight{parent=rps_annunc,label="System Failure",colors=ind_red,flash=true,period=period.BLINK_500_MS}
|
||||
|
||||
rps_trp.register(u_ps, "rps_tripped", rps_trp.update)
|
||||
rps_dmg.register(u_ps, "high_dmg", rps_dmg.update)
|
||||
@@ -232,7 +239,7 @@ local function init(parent, id)
|
||||
local rcs_tags = Div{parent=rcs,width=2,height=16,x=1,y=7}
|
||||
|
||||
local c_flt = IndicatorLight{parent=rcs_annunc,label="RCS Hardware Fault",colors=ind_yel}
|
||||
local c_emg = TriIndicatorLight{parent=rcs_annunc,label="Emergency Coolant",c1=colors.gray,c2=colors.white,c3=colors.green}
|
||||
local c_emg = TriIndicatorLight{parent=rcs_annunc,label="Emergency Coolant",c1=ind_bkg,c2=ind_wht.fgd,c3=ind_grn.fgd}
|
||||
local c_cfm = IndicatorLight{parent=rcs_annunc,label="Coolant Feed Mismatch",colors=ind_yel}
|
||||
local c_brm = IndicatorLight{parent=rcs_annunc,label="Boil Rate Mismatch",colors=ind_yel}
|
||||
local c_sfm = IndicatorLight{parent=rcs_annunc,label="Steam Feed Mismatch",colors=ind_yel}
|
||||
@@ -255,14 +262,14 @@ local function init(parent, id)
|
||||
|
||||
-- boiler annunciator panel(s)
|
||||
|
||||
if available_space > 0 then _add_space() end
|
||||
|
||||
if unit.num_boilers > 0 then
|
||||
TextBox{parent=rcs_tags,x=1,text="B1",width=2,height=1,fg_bg=bw_fg_bg}
|
||||
if available_space > 0 then _add_space() end
|
||||
|
||||
TextBox{parent=rcs_tags,x=1,text="B1",width=2,height=1,fg_bg=hc_text}
|
||||
local b1_wll = IndicatorLight{parent=rcs_annunc,label="Water Level Low",colors=ind_red}
|
||||
b1_wll.register(b_ps[1], "WaterLevelLow", b1_wll.update)
|
||||
|
||||
TextBox{parent=rcs_tags,text="B1",width=2,height=1,fg_bg=bw_fg_bg}
|
||||
TextBox{parent=rcs_tags,text="B1",width=2,height=1,fg_bg=hc_text}
|
||||
local b1_hr = IndicatorLight{parent=rcs_annunc,label="Heating Rate Low",colors=ind_yel}
|
||||
b1_hr.register(b_ps[1], "HeatingRateLow", b1_hr.update)
|
||||
end
|
||||
@@ -274,11 +281,11 @@ local function init(parent, id)
|
||||
_add_space()
|
||||
end
|
||||
|
||||
TextBox{parent=rcs_tags,text="B2",width=2,height=1,fg_bg=bw_fg_bg}
|
||||
TextBox{parent=rcs_tags,text="B2",width=2,height=1,fg_bg=hc_text}
|
||||
local b2_wll = IndicatorLight{parent=rcs_annunc,label="Water Level Low",colors=ind_red}
|
||||
b2_wll.register(b_ps[2], "WaterLevelLow", b2_wll.update)
|
||||
|
||||
TextBox{parent=rcs_tags,text="B2",width=2,height=1,fg_bg=bw_fg_bg}
|
||||
TextBox{parent=rcs_tags,text="B2",width=2,height=1,fg_bg=hc_text}
|
||||
local b2_hr = IndicatorLight{parent=rcs_annunc,label="Heating Rate Low",colors=ind_yel}
|
||||
b2_hr.register(b_ps[2], "HeatingRateLow", b2_hr.update)
|
||||
end
|
||||
@@ -287,19 +294,19 @@ local function init(parent, id)
|
||||
|
||||
if available_space > 1 then _add_space() end
|
||||
|
||||
TextBox{parent=rcs_tags,text="T1",width=2,height=1,fg_bg=bw_fg_bg}
|
||||
local t1_sdo = TriIndicatorLight{parent=rcs_annunc,label="Steam Relief Valve Open",c1=colors.gray,c2=colors.yellow,c3=colors.red}
|
||||
TextBox{parent=rcs_tags,text="T1",width=2,height=1,fg_bg=hc_text}
|
||||
local t1_sdo = TriIndicatorLight{parent=rcs_annunc,label="Steam Relief Valve Open",c1=ind_bkg,c2=ind_yel.fgd,c3=ind_red.fgd}
|
||||
t1_sdo.register(t_ps[1], "SteamDumpOpen", t1_sdo.update)
|
||||
|
||||
TextBox{parent=rcs_tags,text="T1",width=2,height=1,fg_bg=bw_fg_bg}
|
||||
TextBox{parent=rcs_tags,text="T1",width=2,height=1,fg_bg=hc_text}
|
||||
local t1_tos = IndicatorLight{parent=rcs_annunc,label="Turbine Over Speed",colors=ind_red}
|
||||
t1_tos.register(t_ps[1], "TurbineOverSpeed", t1_tos.update)
|
||||
|
||||
TextBox{parent=rcs_tags,text="T1",width=2,height=1,fg_bg=bw_fg_bg}
|
||||
TextBox{parent=rcs_tags,text="T1",width=2,height=1,fg_bg=hc_text}
|
||||
local t1_gtrp = IndicatorLight{parent=rcs_annunc,label="Generator Trip",colors=ind_yel,flash=true,period=period.BLINK_250_MS}
|
||||
t1_gtrp.register(t_ps[1], "GeneratorTrip", t1_gtrp.update)
|
||||
|
||||
TextBox{parent=rcs_tags,text="T1",width=2,height=1,fg_bg=bw_fg_bg}
|
||||
TextBox{parent=rcs_tags,text="T1",width=2,height=1,fg_bg=hc_text}
|
||||
local t1_trp = IndicatorLight{parent=rcs_annunc,label="Turbine Trip",colors=ind_red,flash=true,period=period.BLINK_250_MS}
|
||||
t1_trp.register(t_ps[1], "TurbineTrip", t1_trp.update)
|
||||
|
||||
@@ -308,19 +315,19 @@ local function init(parent, id)
|
||||
_add_space()
|
||||
end
|
||||
|
||||
TextBox{parent=rcs_tags,text="T2",width=2,height=1,fg_bg=bw_fg_bg}
|
||||
local t2_sdo = TriIndicatorLight{parent=rcs_annunc,label="Steam Relief Valve Open",c1=colors.gray,c2=colors.yellow,c3=colors.red}
|
||||
TextBox{parent=rcs_tags,text="T2",width=2,height=1,fg_bg=hc_text}
|
||||
local t2_sdo = TriIndicatorLight{parent=rcs_annunc,label="Steam Relief Valve Open",c1=ind_bkg,c2=ind_yel.fgd,c3=ind_red.fgd}
|
||||
t2_sdo.register(t_ps[2], "SteamDumpOpen", t2_sdo.update)
|
||||
|
||||
TextBox{parent=rcs_tags,text="T2",width=2,height=1,fg_bg=bw_fg_bg}
|
||||
TextBox{parent=rcs_tags,text="T2",width=2,height=1,fg_bg=hc_text}
|
||||
local t2_tos = IndicatorLight{parent=rcs_annunc,label="Turbine Over Speed",colors=ind_red}
|
||||
t2_tos.register(t_ps[2], "TurbineOverSpeed", t2_tos.update)
|
||||
|
||||
TextBox{parent=rcs_tags,text="T2",width=2,height=1,fg_bg=bw_fg_bg}
|
||||
TextBox{parent=rcs_tags,text="T2",width=2,height=1,fg_bg=hc_text}
|
||||
local t2_gtrp = IndicatorLight{parent=rcs_annunc,label="Generator Trip",colors=ind_yel,flash=true,period=period.BLINK_250_MS}
|
||||
t2_gtrp.register(t_ps[2], "GeneratorTrip", t2_gtrp.update)
|
||||
|
||||
TextBox{parent=rcs_tags,text="T2",width=2,height=1,fg_bg=bw_fg_bg}
|
||||
TextBox{parent=rcs_tags,text="T2",width=2,height=1,fg_bg=hc_text}
|
||||
local t2_trp = IndicatorLight{parent=rcs_annunc,label="Turbine Trip",colors=ind_red,flash=true,period=period.BLINK_250_MS}
|
||||
t2_trp.register(t_ps[2], "TurbineTrip", t2_trp.update)
|
||||
end
|
||||
@@ -328,30 +335,32 @@ local function init(parent, id)
|
||||
if unit.num_turbines > 2 then
|
||||
if available_space > 3 then _add_space() end
|
||||
|
||||
TextBox{parent=rcs_tags,text="T3",width=2,height=1,fg_bg=bw_fg_bg}
|
||||
local t3_sdo = TriIndicatorLight{parent=rcs_annunc,label="Steam Relief Valve Open",c1=colors.gray,c2=colors.yellow,c3=colors.red}
|
||||
TextBox{parent=rcs_tags,text="T3",width=2,height=1,fg_bg=hc_text}
|
||||
local t3_sdo = TriIndicatorLight{parent=rcs_annunc,label="Steam Relief Valve Open",c1=ind_bkg,c2=ind_yel.fgd,c3=ind_red.fgd}
|
||||
t3_sdo.register(t_ps[3], "SteamDumpOpen", t3_sdo.update)
|
||||
|
||||
TextBox{parent=rcs_tags,text="T3",width=2,height=1,fg_bg=bw_fg_bg}
|
||||
TextBox{parent=rcs_tags,text="T3",width=2,height=1,fg_bg=hc_text}
|
||||
local t3_tos = IndicatorLight{parent=rcs_annunc,label="Turbine Over Speed",colors=ind_red}
|
||||
t3_tos.register(t_ps[3], "TurbineOverSpeed", t3_tos.update)
|
||||
|
||||
TextBox{parent=rcs_tags,text="T3",width=2,height=1,fg_bg=bw_fg_bg}
|
||||
TextBox{parent=rcs_tags,text="T3",width=2,height=1,fg_bg=hc_text}
|
||||
local t3_gtrp = IndicatorLight{parent=rcs_annunc,label="Generator Trip",colors=ind_yel,flash=true,period=period.BLINK_250_MS}
|
||||
t3_gtrp.register(t_ps[3], "GeneratorTrip", t3_gtrp.update)
|
||||
|
||||
TextBox{parent=rcs_tags,text="T3",width=2,height=1,fg_bg=bw_fg_bg}
|
||||
TextBox{parent=rcs_tags,text="T3",width=2,height=1,fg_bg=hc_text}
|
||||
local t3_trp = IndicatorLight{parent=rcs_annunc,label="Turbine Trip",colors=ind_red,flash=true,period=period.BLINK_250_MS}
|
||||
t3_trp.register(t_ps[3], "TurbineTrip", t3_trp.update)
|
||||
end
|
||||
|
||||
util.nop()
|
||||
|
||||
----------------------
|
||||
-- reactor controls --
|
||||
----------------------
|
||||
|
||||
local burn_control = Div{parent=main,x=12,y=28,width=19,height=3,fg_bg=gry_wht}
|
||||
local burn_rate = SpinboxNumeric{parent=burn_control,x=2,y=1,whole_num_precision=4,fractional_precision=1,min=0.1,arrow_fg_bg=gry_wht,fg_bg=bw_fg_bg}
|
||||
TextBox{parent=burn_control,x=9,y=2,text="mB/t"}
|
||||
local burn_control = Div{parent=main,x=12,y=28,width=19,height=3,fg_bg=s_hi_box}
|
||||
local burn_rate = SpinboxNumeric{parent=burn_control,x=2,y=1,whole_num_precision=4,fractional_precision=1,min=0.1,arrow_fg_bg=arrow_fg_bg,arrow_disable=style.theme.disabled}
|
||||
TextBox{parent=burn_control,x=9,y=2,text="mB/t",fg_bg=style.theme.label_fg}
|
||||
|
||||
local set_burn = function () unit.set_burn(burn_rate.get_value()) end
|
||||
local set_burn_btn = PushButton{parent=burn_control,x=14,y=2,text="SET",min_width=5,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=style.wh_gray,dis_fg_bg=dis_colors,callback=set_burn}
|
||||
@@ -397,22 +406,22 @@ local function init(parent, id)
|
||||
-- alarm management --
|
||||
----------------------
|
||||
|
||||
local alarm_panel = Div{parent=main,x=2,y=36,width=29,height=16,fg_bg=bw_fg_bg}
|
||||
local alarm_panel = Div{parent=main,x=2,y=36,width=29,height=16,fg_bg=s_hi_bright}
|
||||
|
||||
local a_brc = AlarmLight{parent=alarm_panel,x=6,y=2,label="Containment Breach",c1=colors.gray,c2=colors.red,c3=colors.green,flash=true,period=period.BLINK_250_MS}
|
||||
local a_rad = AlarmLight{parent=alarm_panel,x=6,label="Containment Radiation",c1=colors.gray,c2=colors.red,c3=colors.green,flash=true,period=period.BLINK_250_MS}
|
||||
local a_dmg = AlarmLight{parent=alarm_panel,x=6,label="Critical Damage",c1=colors.gray,c2=colors.red,c3=colors.green,flash=true,period=period.BLINK_250_MS}
|
||||
local a_brc = AlarmLight{parent=alarm_panel,x=6,y=2,label="Containment Breach",c1=ind_bkg,c2=ind_red.fgd,c3=ind_grn.fgd,flash=true,period=period.BLINK_250_MS}
|
||||
local a_rad = AlarmLight{parent=alarm_panel,x=6,label="Containment Radiation",c1=ind_bkg,c2=ind_red.fgd,c3=ind_grn.fgd,flash=true,period=period.BLINK_250_MS}
|
||||
local a_dmg = AlarmLight{parent=alarm_panel,x=6,label="Critical Damage",c1=ind_bkg,c2=ind_red.fgd,c3=ind_grn.fgd,flash=true,period=period.BLINK_250_MS}
|
||||
alarm_panel.line_break()
|
||||
local a_rcl = AlarmLight{parent=alarm_panel,x=6,label="Reactor Lost",c1=colors.gray,c2=colors.red,c3=colors.green,flash=true,period=period.BLINK_250_MS}
|
||||
local a_rcd = AlarmLight{parent=alarm_panel,x=6,label="Reactor Damage",c1=colors.gray,c2=colors.red,c3=colors.green,flash=true,period=period.BLINK_250_MS}
|
||||
local a_rot = AlarmLight{parent=alarm_panel,x=6,label="Reactor Over Temp",c1=colors.gray,c2=colors.red,c3=colors.green,flash=true,period=period.BLINK_250_MS}
|
||||
local a_rht = AlarmLight{parent=alarm_panel,x=6,label="Reactor High Temp",c1=colors.gray,c2=colors.yellow,c3=colors.green,flash=true,period=period.BLINK_500_MS}
|
||||
local a_rwl = AlarmLight{parent=alarm_panel,x=6,label="Reactor Waste Leak",c1=colors.gray,c2=colors.red,c3=colors.green,flash=true,period=period.BLINK_250_MS}
|
||||
local a_rwh = AlarmLight{parent=alarm_panel,x=6,label="Reactor Waste High",c1=colors.gray,c2=colors.yellow,c3=colors.green,flash=true,period=period.BLINK_500_MS}
|
||||
local a_rcl = AlarmLight{parent=alarm_panel,x=6,label="Reactor Lost",c1=ind_bkg,c2=ind_red.fgd,c3=ind_grn.fgd,flash=true,period=period.BLINK_250_MS}
|
||||
local a_rcd = AlarmLight{parent=alarm_panel,x=6,label="Reactor Damage",c1=ind_bkg,c2=ind_red.fgd,c3=ind_grn.fgd,flash=true,period=period.BLINK_250_MS}
|
||||
local a_rot = AlarmLight{parent=alarm_panel,x=6,label="Reactor Over Temp",c1=ind_bkg,c2=ind_red.fgd,c3=ind_grn.fgd,flash=true,period=period.BLINK_250_MS}
|
||||
local a_rht = AlarmLight{parent=alarm_panel,x=6,label="Reactor High Temp",c1=ind_bkg,c2=ind_yel.fgd,c3=ind_grn.fgd,flash=true,period=period.BLINK_500_MS}
|
||||
local a_rwl = AlarmLight{parent=alarm_panel,x=6,label="Reactor Waste Leak",c1=ind_bkg,c2=ind_red.fgd,c3=ind_grn.fgd,flash=true,period=period.BLINK_250_MS}
|
||||
local a_rwh = AlarmLight{parent=alarm_panel,x=6,label="Reactor Waste High",c1=ind_bkg,c2=ind_yel.fgd,c3=ind_grn.fgd,flash=true,period=period.BLINK_500_MS}
|
||||
alarm_panel.line_break()
|
||||
local a_rps = AlarmLight{parent=alarm_panel,x=6,label="RPS Transient",c1=colors.gray,c2=colors.yellow,c3=colors.green,flash=true,period=period.BLINK_500_MS}
|
||||
local a_clt = AlarmLight{parent=alarm_panel,x=6,label="RCS Transient",c1=colors.gray,c2=colors.yellow,c3=colors.green,flash=true,period=period.BLINK_500_MS}
|
||||
local a_tbt = AlarmLight{parent=alarm_panel,x=6,label="Turbine Trip",c1=colors.gray,c2=colors.red,c3=colors.green,flash=true,period=period.BLINK_250_MS}
|
||||
local a_rps = AlarmLight{parent=alarm_panel,x=6,label="RPS Transient",c1=ind_bkg,c2=ind_yel.fgd,c3=ind_grn.fgd,flash=true,period=period.BLINK_500_MS}
|
||||
local a_clt = AlarmLight{parent=alarm_panel,x=6,label="RCS Transient",c1=ind_bkg,c2=ind_yel.fgd,c3=ind_grn.fgd,flash=true,period=period.BLINK_500_MS}
|
||||
local a_tbt = AlarmLight{parent=alarm_panel,x=6,label="Turbine Trip",c1=ind_bkg,c2=ind_red.fgd,c3=ind_grn.fgd,flash=true,period=period.BLINK_250_MS}
|
||||
|
||||
a_brc.register(u_ps, "Alarm_1", a_brc.update)
|
||||
a_rad.register(u_ps, "Alarm_2", a_rad.update)
|
||||
@@ -465,9 +474,9 @@ local function init(parent, id)
|
||||
|
||||
-- color tags
|
||||
|
||||
TextBox{parent=alarm_panel,x=5,y=13,text="\x95",width=1,height=1,fg_bg=cpair(colors.white,colors.cyan)}
|
||||
TextBox{parent=alarm_panel,x=5,text="\x95",width=1,height=1,fg_bg=cpair(colors.white,colors.blue)}
|
||||
TextBox{parent=alarm_panel,x=5,text="\x95",width=1,height=1,fg_bg=cpair(colors.white,colors.blue)}
|
||||
TextBox{parent=alarm_panel,x=5,y=13,text="\x95",width=1,height=1,fg_bg=cpair(s_hi_bright.bkg,colors.cyan)}
|
||||
TextBox{parent=alarm_panel,x=5,text="\x95",width=1,height=1,fg_bg=cpair(s_hi_bright.bkg,colors.blue)}
|
||||
TextBox{parent=alarm_panel,x=5,text="\x95",width=1,height=1,fg_bg=cpair(s_hi_bright.bkg,colors.blue)}
|
||||
|
||||
--------------------------------
|
||||
-- automatic control settings --
|
||||
@@ -479,7 +488,7 @@ local function init(parent, id)
|
||||
|
||||
local ctl_opts = { "Manual", "Primary", "Secondary", "Tertiary", "Backup" }
|
||||
|
||||
local group = RadioButton{parent=auto_div,options=ctl_opts,callback=function()end,radio_colors=cpair(colors.gray,colors.white),select_color=colors.purple}
|
||||
local group = RadioButton{parent=auto_div,options=ctl_opts,callback=function()end,radio_colors=cpair(style.theme.accent_dark,style.theme.accent_light),select_color=colors.purple}
|
||||
|
||||
group.register(u_ps, "auto_group_id", function (gid) group.set_value(gid + 1) end)
|
||||
|
||||
@@ -491,7 +500,7 @@ local function init(parent, id)
|
||||
auto_div.line_break()
|
||||
|
||||
TextBox{parent=auto_div,text="Prio. Group",height=1,width=11,fg_bg=style.label}
|
||||
local auto_grp = TextBox{parent=auto_div,text="Manual",height=1,width=11,fg_bg=bw_fg_bg}
|
||||
local auto_grp = TextBox{parent=auto_div,text="Manual",height=1,width=11,fg_bg=s_field}
|
||||
|
||||
auto_grp.register(u_ps, "auto_group", auto_grp.set_value)
|
||||
|
||||
|
||||
@@ -24,17 +24,12 @@ local ALIGN = core.ALIGN
|
||||
local sprintf = util.sprintf
|
||||
|
||||
local border = core.border
|
||||
local cpair = core.cpair
|
||||
local pipe = core.pipe
|
||||
|
||||
local wh_gray = style.wh_gray
|
||||
local bw_fg_bg = style.bw_fg_bg
|
||||
local text_c = style.text_colors
|
||||
local lu_c = style.lu_colors
|
||||
local lg_gray = style.lg_gray
|
||||
|
||||
local ind_grn = style.ind_grn
|
||||
local ind_wht = style.ind_wht
|
||||
|
||||
-- make a new unit flow window
|
||||
---@param parent graphics_element parent
|
||||
---@param x integer top left x
|
||||
@@ -42,6 +37,15 @@ local ind_wht = style.ind_wht
|
||||
---@param wide boolean whether to render wide version
|
||||
---@param unit ioctl_unit unit database entry
|
||||
local function make(parent, x, y, wide, unit)
|
||||
local s_field = style.theme.field_box
|
||||
|
||||
local text_c = style.text_colors
|
||||
local lu_c = style.lu_colors
|
||||
local lu_c_d = style.lu_colors_dark
|
||||
|
||||
local ind_grn = style.ind_grn
|
||||
local ind_wht = style.ind_wht
|
||||
|
||||
local height = 16
|
||||
|
||||
local v_start = 1 + ((unit.unit_id - 1) * 5)
|
||||
@@ -99,35 +103,35 @@ local function make(parent, x, y, wide, unit)
|
||||
table.insert(rc_pipes, pipe(_wide(92, 78), py, _wide(104, 83), py, colors.white, true))
|
||||
end
|
||||
|
||||
PipeNetwork{parent=root,x=20,y=1,pipes=rc_pipes,bg=colors.lightGray}
|
||||
PipeNetwork{parent=root,x=20,y=1,pipes=rc_pipes,bg=style.theme.bg}
|
||||
|
||||
if unit.num_boilers > 0 then
|
||||
local cc_rate = DataIndicator{parent=root,x=_wide(25,22),y=3,lu_colors=lu_c,label="",unit="mB/t",format="%11.0f",value=0,commas=true,width=16,fg_bg=bw_fg_bg}
|
||||
local hc_rate = DataIndicator{parent=root,x=_wide(25,22),y=5,lu_colors=lu_c,label="",unit="mB/t",format="%11.0f",value=0,commas=true,width=16,fg_bg=bw_fg_bg}
|
||||
local cc_rate = DataIndicator{parent=root,x=_wide(25,22),y=3,lu_colors=lu_c,label="",unit="mB/t",format="%11.0f",value=0,commas=true,width=16,fg_bg=s_field}
|
||||
local hc_rate = DataIndicator{parent=root,x=_wide(25,22),y=5,lu_colors=lu_c,label="",unit="mB/t",format="%11.0f",value=0,commas=true,width=16,fg_bg=s_field}
|
||||
|
||||
cc_rate.register(unit.unit_ps, "boiler_boil_sum", function (sum) cc_rate.update(sum * 10) end)
|
||||
hc_rate.register(unit.unit_ps, "heating_rate", hc_rate.update)
|
||||
|
||||
local boiler = Rectangle{parent=root,x=_wide(47,40),y=1,border=border(1, colors.gray, true),width=19,height=5,fg_bg=wh_gray}
|
||||
local boiler = Rectangle{parent=root,x=_wide(47,40),y=1,border=border(1,colors.gray,true),width=19,height=5,fg_bg=wh_gray}
|
||||
TextBox{parent=boiler,y=1,text="THERMO-ELECTRIC",alignment=ALIGN.CENTER,height=1}
|
||||
TextBox{parent=boiler,y=3,text=util.trinary(unit.num_boilers>1,"BOILERS","BOILER"),alignment=ALIGN.CENTER,height=1}
|
||||
TextBox{parent=root,x=_wide(47,40),y=2,text="\x1b \x80 \x1a",width=1,height=3,fg_bg=lg_gray}
|
||||
TextBox{parent=root,x=_wide(65,58),y=2,text="\x1b \x80 \x1a",width=1,height=3,fg_bg=lg_gray}
|
||||
|
||||
local wt_rate = DataIndicator{parent=root,x=_wide(71,61),y=3,lu_colors=lu_c,label="",unit="mB/t",format="%11.0f",value=0,commas=true,width=16,fg_bg=bw_fg_bg}
|
||||
local st_rate = DataIndicator{parent=root,x=_wide(71,61),y=5,lu_colors=lu_c,label="",unit="mB/t",format="%11.0f",value=0,commas=true,width=16,fg_bg=bw_fg_bg}
|
||||
local wt_rate = DataIndicator{parent=root,x=_wide(71,61),y=3,lu_colors=lu_c,label="",unit="mB/t",format="%11.0f",value=0,commas=true,width=16,fg_bg=s_field}
|
||||
local st_rate = DataIndicator{parent=root,x=_wide(71,61),y=5,lu_colors=lu_c,label="",unit="mB/t",format="%11.0f",value=0,commas=true,width=16,fg_bg=s_field}
|
||||
|
||||
wt_rate.register(unit.unit_ps, "turbine_flow_sum", wt_rate.update)
|
||||
st_rate.register(unit.unit_ps, "boiler_boil_sum", st_rate.update)
|
||||
else
|
||||
local wt_rate = DataIndicator{parent=root,x=28,y=3,lu_colors=lu_c,label="",unit="mB/t",format="%11.0f",value=0,commas=true,width=16,fg_bg=bw_fg_bg}
|
||||
local st_rate = DataIndicator{parent=root,x=28,y=5,lu_colors=lu_c,label="",unit="mB/t",format="%11.0f",value=0,commas=true,width=16,fg_bg=bw_fg_bg}
|
||||
local wt_rate = DataIndicator{parent=root,x=28,y=3,lu_colors=lu_c,label="",unit="mB/t",format="%11.0f",value=0,commas=true,width=16,fg_bg=s_field}
|
||||
local st_rate = DataIndicator{parent=root,x=28,y=5,lu_colors=lu_c,label="",unit="mB/t",format="%11.0f",value=0,commas=true,width=16,fg_bg=s_field}
|
||||
|
||||
wt_rate.register(unit.unit_ps, "turbine_flow_sum", wt_rate.update)
|
||||
st_rate.register(unit.unit_ps, "heating_rate", st_rate.update)
|
||||
end
|
||||
|
||||
local turbine = Rectangle{parent=root,x=_wide(93,79),y=1,border=border(1, colors.gray, true),width=19,height=5,fg_bg=wh_gray}
|
||||
local turbine = Rectangle{parent=root,x=_wide(93,79),y=1,border=border(1,colors.gray,true),width=19,height=5,fg_bg=wh_gray}
|
||||
TextBox{parent=turbine,y=1,text="STEAM TURBINE",alignment=ALIGN.CENTER,height=1}
|
||||
TextBox{parent=turbine,y=3,text=util.trinary(unit.num_turbines>1,"GENERATORS","GENERATOR"),alignment=ALIGN.CENTER,height=1}
|
||||
TextBox{parent=root,x=_wide(93,79),y=2,text="\x1b \x80 \x1a",width=1,height=3,fg_bg=lg_gray}
|
||||
@@ -135,7 +139,7 @@ local function make(parent, x, y, wide, unit)
|
||||
for i = 1, unit.num_turbines do
|
||||
local ry = 1 + (2 * (i - 1)) + prv_yo
|
||||
TextBox{parent=root,x=_wide(125,103),y=ry,text="\x10\x11\x7f",fg_bg=text_c,width=3,height=1}
|
||||
local state = TriIndicatorLight{parent=root,x=_wide(129,107),y=ry,label=v_names[i+4],c1=colors.gray,c2=colors.yellow,c3=colors.red}
|
||||
local state = TriIndicatorLight{parent=root,x=_wide(129,107),y=ry,label=v_names[i+4],c1=style.ind_bkg,c2=style.ind_yel.fgd,c3=style.ind_red.fgd}
|
||||
state.register(unit.turbine_ps_tbl[i], "SteamDumpOpen", state.update)
|
||||
end
|
||||
|
||||
@@ -145,6 +149,8 @@ local function make(parent, x, y, wide, unit)
|
||||
|
||||
local waste = Div{parent=root,x=3,y=6}
|
||||
|
||||
local waste_c = style.theme.fuel_color
|
||||
|
||||
local waste_pipes = {
|
||||
pipe(0, 0, _wide(19, 16), 1, colors.brown, true),
|
||||
pipe(_wide(14, 13), 1, _wide(19, 17), 5, colors.brown, true),
|
||||
@@ -158,12 +164,12 @@ local function make(parent, x, y, wide, unit)
|
||||
pipe(_wide(74, 63), 4, _wide(95, 81), 4, colors.cyan, true),
|
||||
pipe(_wide(74, 63), 8, _wide(133, 111), 8, colors.cyan, true),
|
||||
|
||||
pipe(_wide(108, 94), 1, _wide(132, 110), 6, colors.black, true, true),
|
||||
pipe(_wide(108, 94), 4, _wide(111, 95), 1, colors.black, true, true),
|
||||
pipe(_wide(132, 110), 6, _wide(130, 108), 6, colors.black, true, true)
|
||||
pipe(_wide(108, 94), 1, _wide(132, 110), 6, waste_c, true, true),
|
||||
pipe(_wide(108, 94), 4, _wide(111, 95), 1, waste_c, true, true),
|
||||
pipe(_wide(132, 110), 6, _wide(130, 108), 6, waste_c, true, true)
|
||||
}
|
||||
|
||||
PipeNetwork{parent=waste,x=1,y=1,pipes=waste_pipes,bg=colors.lightGray}
|
||||
PipeNetwork{parent=waste,x=1,y=1,pipes=waste_pipes,bg=style.theme.bg}
|
||||
|
||||
local function _valve(vx, vy, n)
|
||||
TextBox{parent=waste,x=vx,y=vy,text="\x10\x11",fg_bg=text_c,width=2,height=1}
|
||||
@@ -175,16 +181,16 @@ local function make(parent, x, y, wide, unit)
|
||||
|
||||
local function _machine(mx, my, name)
|
||||
local l = string.len(name) + 2
|
||||
TextBox{parent=waste,x=mx,y=my,text=string.rep("\x8f",l),alignment=ALIGN.CENTER,fg_bg=lg_gray,width=l,height=1}
|
||||
TextBox{parent=waste,x=mx,y=my+1,text=name,alignment=ALIGN.CENTER,fg_bg=wh_gray,width=l,height=1}
|
||||
TextBox{parent=waste,x=mx,y=my,text=string.rep("\x8f",l),alignment=ALIGN.CENTER,fg_bg=cpair(style.theme.bg,style.theme.header.bkg),width=l,height=1}
|
||||
TextBox{parent=waste,x=mx,y=my+1,text=name,alignment=ALIGN.CENTER,fg_bg=style.theme.header,width=l,height=1}
|
||||
end
|
||||
|
||||
local waste_rate = DataIndicator{parent=waste,x=1,y=3,lu_colors=lu_c,label="",unit="mB/t",format="%7.2f",value=0,width=12,fg_bg=bw_fg_bg}
|
||||
local pu_rate = DataIndicator{parent=waste,x=_wide(82,70),y=3,lu_colors=lu_c,label="",unit="mB/t",format="%7.3f",value=0,width=12,fg_bg=bw_fg_bg}
|
||||
local po_rate = DataIndicator{parent=waste,x=_wide(52,45),y=6,lu_colors=lu_c,label="",unit="mB/t",format="%7.2f",value=0,width=12,fg_bg=bw_fg_bg}
|
||||
local popl_rate = DataIndicator{parent=waste,x=_wide(82,70),y=6,lu_colors=lu_c,label="",unit="mB/t",format="%7.2f",value=0,width=12,fg_bg=bw_fg_bg}
|
||||
local poam_rate = DataIndicator{parent=waste,x=_wide(82,70),y=10,lu_colors=lu_c,label="",unit="mB/t",format="%7.2f",value=0,width=12,fg_bg=bw_fg_bg}
|
||||
local spent_rate = DataIndicator{parent=waste,x=_wide(117,98),y=3,lu_colors=lu_c,label="",unit="mB/t",format="%8.3f",value=0,width=13,fg_bg=bw_fg_bg}
|
||||
local waste_rate = DataIndicator{parent=waste,x=1,y=3,lu_colors=lu_c,label="",unit="mB/t",format="%7.2f",value=0,width=12,fg_bg=s_field}
|
||||
local pu_rate = DataIndicator{parent=waste,x=_wide(82,70),y=3,lu_colors=lu_c,label="",unit="mB/t",format="%7.3f",value=0,width=12,fg_bg=s_field}
|
||||
local po_rate = DataIndicator{parent=waste,x=_wide(52,45),y=6,lu_colors=lu_c,label="",unit="mB/t",format="%7.2f",value=0,width=12,fg_bg=s_field}
|
||||
local popl_rate = DataIndicator{parent=waste,x=_wide(82,70),y=6,lu_colors=lu_c,label="",unit="mB/t",format="%7.2f",value=0,width=12,fg_bg=s_field}
|
||||
local poam_rate = DataIndicator{parent=waste,x=_wide(82,70),y=10,lu_colors=lu_c,label="",unit="mB/t",format="%7.2f",value=0,width=12,fg_bg=s_field}
|
||||
local spent_rate = DataIndicator{parent=waste,x=_wide(117,98),y=3,lu_colors=lu_c,label="",unit="mB/t",format="%8.3f",value=0,width=13,fg_bg=s_field}
|
||||
|
||||
waste_rate.register(unit.unit_ps, "act_burn_rate", waste_rate.update)
|
||||
pu_rate.register(unit.unit_ps, "pu_rate", pu_rate.update)
|
||||
@@ -204,12 +210,12 @@ local function make(parent, x, y, wide, unit)
|
||||
_machine(_wide(116, 94), 6, "SPENT WASTE \x1b")
|
||||
|
||||
TextBox{parent=waste,x=_wide(30,25),y=3,text="SNAs [Po]",alignment=ALIGN.CENTER,width=19,height=1,fg_bg=wh_gray}
|
||||
local sna_po = Rectangle{parent=waste,x=_wide(30,25),y=4,border=border(1,colors.gray,true),width=19,height=7,thin=true,fg_bg=bw_fg_bg}
|
||||
local sna_po = Rectangle{parent=waste,x=_wide(30,25),y=4,border=border(1,colors.gray,true),width=19,height=7,thin=true,fg_bg=style.theme.highlight_box_bright}
|
||||
local sna_act = IndicatorLight{parent=sna_po,label="ACTIVE",colors=ind_grn}
|
||||
local sna_cnt = DataIndicator{parent=sna_po,x=12,y=1,lu_colors=lu_c,label="CNT",unit="",format="%2d",value=0,width=7}
|
||||
local sna_pk = DataIndicator{parent=sna_po,y=3,lu_colors=lu_c,label="PEAK",unit="mB/t",format="%7.2f",value=0,width=17}
|
||||
local sna_max = DataIndicator{parent=sna_po,lu_colors=lu_c,label="MAX",unit="mB/t",format="%8.2f",value=0,width=17}
|
||||
local sna_in = DataIndicator{parent=sna_po,lu_colors=lu_c,label="IN",unit="mB/t",format="%9.2f",value=0,width=17}
|
||||
local sna_cnt = DataIndicator{parent=sna_po,x=12,y=1,lu_colors=lu_c_d,label="CNT",unit="",format="%2d",value=0,width=7}
|
||||
local sna_pk = DataIndicator{parent=sna_po,y=3,lu_colors=lu_c_d,label="PEAK",unit="mB/t",format="%7.2f",value=0,width=17}
|
||||
local sna_max = DataIndicator{parent=sna_po,lu_colors=lu_c_d,label="MAX",unit="mB/t",format="%8.2f",value=0,width=17}
|
||||
local sna_in = DataIndicator{parent=sna_po,lu_colors=lu_c_d,label="IN",unit="mB/t",format="%9.2f",value=0,width=17}
|
||||
|
||||
sna_act.register(unit.unit_ps, "po_rate", function (r) sna_act.update(r > 0) end)
|
||||
sna_cnt.register(unit.unit_ps, "sna_count", sna_cnt.update)
|
||||
|
||||
@@ -44,7 +44,7 @@ local function make(parent, x, y, unit)
|
||||
local root = Div{parent=parent,x=x,y=y,width=80,height=height}
|
||||
|
||||
-- unit header message
|
||||
TextBox{parent=root,text="Unit #"..unit.unit_id,alignment=ALIGN.CENTER,height=1,fg_bg=style.header}
|
||||
TextBox{parent=root,text="Unit #"..unit.unit_id,alignment=ALIGN.CENTER,height=1,fg_bg=style.theme.header}
|
||||
|
||||
-------------
|
||||
-- REACTOR --
|
||||
@@ -66,7 +66,7 @@ local function make(parent, x, y, unit)
|
||||
table.insert(coolant_pipes, pipe(2, 0, 11, 11, colors.orange))
|
||||
end
|
||||
|
||||
PipeNetwork{parent=root,x=4,y=10,pipes=coolant_pipes,bg=colors.lightGray}
|
||||
PipeNetwork{parent=root,x=4,y=10,pipes=coolant_pipes,bg=style.theme.bg}
|
||||
end
|
||||
|
||||
-------------
|
||||
@@ -164,10 +164,10 @@ local function make(parent, x, y, unit)
|
||||
table.insert(steam_pipes_b, pipe(0, 18, 2, 18, colors.blue, false, true)) -- water boiler 2 to turbine 2 junction
|
||||
end
|
||||
|
||||
PipeNetwork{parent=root,x=47,y=11,pipes=steam_pipes_a,bg=colors.lightGray}
|
||||
PipeNetwork{parent=root,x=47,y=11,pipes=steam_pipes_a,bg=style.theme.bg}
|
||||
end
|
||||
|
||||
PipeNetwork{parent=root,x=54,y=3,pipes=steam_pipes_b,bg=colors.lightGray}
|
||||
PipeNetwork{parent=root,x=54,y=3,pipes=steam_pipes_b,bg=style.theme.bg}
|
||||
|
||||
return root
|
||||
end
|
||||
|
||||
@@ -32,13 +32,16 @@ local border = core.border
|
||||
local pipe = core.pipe
|
||||
|
||||
local wh_gray = style.wh_gray
|
||||
local bw_fg_bg = style.bw_fg_bg
|
||||
local text_col = style.text_colors
|
||||
local lu_col = style.lu_colors
|
||||
|
||||
-- create new flow view
|
||||
---@param main graphics_element main displaybox
|
||||
local function init(main)
|
||||
local s_hi_bright = style.theme.highlight_box_bright
|
||||
local s_field = style.theme.field_box
|
||||
local text_col = style.text_colors
|
||||
local lu_col = style.lu_colors
|
||||
local lu_c_d = style.lu_colors_dark
|
||||
|
||||
local facility = iocontrol.get_db().facility
|
||||
local units = iocontrol.get_db().units
|
||||
|
||||
@@ -46,9 +49,9 @@ local function init(main)
|
||||
local tank_list = facility.tank_list
|
||||
|
||||
-- window header message
|
||||
local header = TextBox{parent=main,y=1,text="Facility Coolant and Waste Flow Monitor",alignment=ALIGN.CENTER,height=1,fg_bg=style.header}
|
||||
local header = TextBox{parent=main,y=1,text="Facility Coolant and Waste Flow Monitor",alignment=ALIGN.CENTER,height=1,fg_bg=style.theme.header}
|
||||
-- max length example: "01:23:45 AM - Wednesday, September 28 2022"
|
||||
local datetime = TextBox{parent=main,x=(header.get_width()-42),y=1,text="",alignment=ALIGN.RIGHT,width=42,height=1,fg_bg=style.header}
|
||||
local datetime = TextBox{parent=main,x=(header.get_width()-42),y=1,text="",alignment=ALIGN.RIGHT,width=42,height=1,fg_bg=style.theme.header}
|
||||
|
||||
datetime.register(facility.ps, "date_time", datetime.set_value)
|
||||
|
||||
@@ -240,16 +243,17 @@ local function init(main)
|
||||
local flow_x = 3
|
||||
if #water_pipes > 0 then
|
||||
flow_x = 25
|
||||
PipeNetwork{parent=main,x=2,y=3,pipes=water_pipes,bg=colors.lightGray}
|
||||
PipeNetwork{parent=main,x=2,y=3,pipes=water_pipes,bg=style.theme.bg}
|
||||
end
|
||||
|
||||
for i = 1, facility.num_units do
|
||||
local y_offset = y_ofs(i)
|
||||
unit_flow(main, flow_x, 5 + y_offset, #water_pipes == 0, units[i])
|
||||
table.insert(po_pipes, pipe(0, 3 + y_offset, 4, 0, colors.cyan, true, true))
|
||||
util.nop()
|
||||
end
|
||||
|
||||
PipeNetwork{parent=main,x=139,y=15,pipes=po_pipes,bg=colors.lightGray}
|
||||
PipeNetwork{parent=main,x=139,y=15,pipes=po_pipes,bg=style.theme.bg}
|
||||
|
||||
-----------------
|
||||
-- tank valves --
|
||||
@@ -297,7 +301,7 @@ local function init(main)
|
||||
|
||||
TextBox{parent=tank_box,x=2,y=3,text="Fill",height=1,width=10,fg_bg=style.label}
|
||||
local tank_pcnt = DataIndicator{parent=tank_box,x=10,y=3,label="",format="%5.2f",value=100,unit="%",lu_colors=lu_col,width=8,fg_bg=text_col}
|
||||
local tank_amnt = DataIndicator{parent=tank_box,x=2,label="",format="%13d",value=0,commas=true,unit="mB",lu_colors=lu_col,width=16,fg_bg=bw_fg_bg}
|
||||
local tank_amnt = DataIndicator{parent=tank_box,x=2,label="",format="%13d",value=0,commas=true,unit="mB",lu_colors=lu_col,width=16,fg_bg=s_field}
|
||||
|
||||
TextBox{parent=tank_box,x=2,y=6,text="Water Level",height=1,width=11,fg_bg=style.label}
|
||||
local level = HorizontalBar{parent=tank_box,x=2,y=7,bar_fg_bg=cpair(colors.blue,colors.gray),height=1,width=16}
|
||||
@@ -332,6 +336,8 @@ local function init(main)
|
||||
end
|
||||
end
|
||||
|
||||
util.nop()
|
||||
|
||||
---------
|
||||
-- SPS --
|
||||
---------
|
||||
@@ -348,12 +354,12 @@ local function init(main)
|
||||
status.register(facility.sps_ps_tbl[1], "computed_status", status.update)
|
||||
|
||||
TextBox{parent=sps_box,x=2,y=3,text="Input Rate",height=1,width=10,fg_bg=style.label}
|
||||
local sps_in = DataIndicator{parent=sps_box,x=2,label="",format="%15.2f",value=0,unit="mB/t",lu_colors=lu_col,width=20,fg_bg=bw_fg_bg}
|
||||
local sps_in = DataIndicator{parent=sps_box,x=2,label="",format="%15.2f",value=0,unit="mB/t",lu_colors=lu_col,width=20,fg_bg=s_field}
|
||||
|
||||
sps_in.register(facility.ps, "po_am_rate", sps_in.update)
|
||||
|
||||
TextBox{parent=sps_box,x=2,y=6,text="Production Rate",height=1,width=15,fg_bg=style.label}
|
||||
local sps_rate = DataIndicator{parent=sps_box,x=2,label="",format="%15d",value=0,unit="\xb5B/t",lu_colors=lu_col,width=20,fg_bg=bw_fg_bg}
|
||||
local sps_rate = DataIndicator{parent=sps_box,x=2,label="",format="%15d",value=0,unit="\xb5B/t",lu_colors=lu_col,width=20,fg_bg=s_field}
|
||||
|
||||
sps_rate.register(facility.sps_ps_tbl[1], "process_rate", function (r) sps_rate.update(r * 1000) end)
|
||||
|
||||
@@ -362,24 +368,24 @@ local function init(main)
|
||||
----------------
|
||||
|
||||
TextBox{parent=main,x=145,y=16,text="RAW WASTE",alignment=ALIGN.CENTER,width=19,height=1,fg_bg=wh_gray}
|
||||
local raw_waste = Rectangle{parent=main,x=145,y=17,border=border(1,colors.gray,true),width=19,height=3,thin=true,fg_bg=bw_fg_bg}
|
||||
local sum_raw_waste = DataIndicator{parent=raw_waste,lu_colors=lu_col,label="SUM",unit="mB/t",format="%8.2f",value=0,width=17}
|
||||
local raw_waste = Rectangle{parent=main,x=145,y=17,border=border(1,colors.gray,true),width=19,height=3,thin=true,fg_bg=s_hi_bright}
|
||||
local sum_raw_waste = DataIndicator{parent=raw_waste,lu_colors=lu_c_d,label="SUM",unit="mB/t",format="%8.2f",value=0,width=17}
|
||||
|
||||
sum_raw_waste.register(facility.ps, "burn_sum", sum_raw_waste.update)
|
||||
|
||||
TextBox{parent=main,x=145,y=21,text="PROC. WASTE",alignment=ALIGN.CENTER,width=19,height=1,fg_bg=wh_gray}
|
||||
local pr_waste = Rectangle{parent=main,x=145,y=22,border=border(1,colors.gray,true),width=19,height=5,thin=true,fg_bg=bw_fg_bg}
|
||||
local pu = DataIndicator{parent=pr_waste,lu_colors=lu_col,label="Pu",unit="mB/t",format="%9.3f",value=0,width=17}
|
||||
local po = DataIndicator{parent=pr_waste,lu_colors=lu_col,label="Po",unit="mB/t",format="%9.2f",value=0,width=17}
|
||||
local popl = DataIndicator{parent=pr_waste,lu_colors=lu_col,label="PoPl",unit="mB/t",format="%7.2f",value=0,width=17}
|
||||
local pr_waste = Rectangle{parent=main,x=145,y=22,border=border(1,colors.gray,true),width=19,height=5,thin=true,fg_bg=s_hi_bright}
|
||||
local pu = DataIndicator{parent=pr_waste,lu_colors=lu_c_d,label="Pu",unit="mB/t",format="%9.3f",value=0,width=17}
|
||||
local po = DataIndicator{parent=pr_waste,lu_colors=lu_c_d,label="Po",unit="mB/t",format="%9.2f",value=0,width=17}
|
||||
local popl = DataIndicator{parent=pr_waste,lu_colors=lu_c_d,label="PoPl",unit="mB/t",format="%7.2f",value=0,width=17}
|
||||
|
||||
pu.register(facility.ps, "pu_rate", pu.update)
|
||||
po.register(facility.ps, "po_rate", po.update)
|
||||
popl.register(facility.ps, "po_pl_rate", popl.update)
|
||||
|
||||
TextBox{parent=main,x=145,y=28,text="SPENT WASTE",alignment=ALIGN.CENTER,width=19,height=1,fg_bg=wh_gray}
|
||||
local sp_waste = Rectangle{parent=main,x=145,y=29,border=border(1,colors.gray,true),width=19,height=3,thin=true,fg_bg=bw_fg_bg}
|
||||
local sum_sp_waste = DataIndicator{parent=sp_waste,lu_colors=lu_col,label="SUM",unit="mB/t",format="%8.3f",value=0,width=17}
|
||||
local sp_waste = Rectangle{parent=main,x=145,y=29,border=border(1,colors.gray,true),width=19,height=3,thin=true,fg_bg=s_hi_bright}
|
||||
local sum_sp_waste = DataIndicator{parent=sp_waste,lu_colors=lu_c_d,label="SUM",unit="mB/t",format="%8.3f",value=0,width=17}
|
||||
|
||||
sum_sp_waste.register(facility.ps, "spent_waste_rate", sum_sp_waste.update)
|
||||
end
|
||||
|
||||
@@ -22,8 +22,11 @@ local TextBox = require("graphics.elements.textbox")
|
||||
local TabBar = require("graphics.elements.controls.tabbar")
|
||||
|
||||
local LED = require("graphics.elements.indicators.led")
|
||||
local LEDPair = require("graphics.elements.indicators.ledpair")
|
||||
local RGBLED = require("graphics.elements.indicators.ledrgb")
|
||||
|
||||
local LINK_STATE = types.PANEL_LINK_STATE
|
||||
|
||||
local ALIGN = core.ALIGN
|
||||
|
||||
local cpair = core.cpair
|
||||
@@ -36,7 +39,7 @@ local led_grn = style.led_grn
|
||||
local function init(panel, num_units)
|
||||
local ps = iocontrol.get_db().fp.ps
|
||||
|
||||
TextBox{parent=panel,y=1,text="SCADA COORDINATOR",alignment=ALIGN.CENTER,height=1,fg_bg=style.fp.header}
|
||||
TextBox{parent=panel,y=1,text="SCADA COORDINATOR",alignment=ALIGN.CENTER,height=1,fg_bg=style.fp_theme.header}
|
||||
|
||||
local page_div = Div{parent=panel,x=1,y=3}
|
||||
|
||||
@@ -56,19 +59,58 @@ local function init(panel, num_units)
|
||||
heartbeat.register(ps, "heartbeat", heartbeat.update)
|
||||
|
||||
local modem = LED{parent=system,label="MODEM",colors=led_grn}
|
||||
local network = RGBLED{parent=system,label="NETWORK",colors={colors.green,colors.red,colors.orange,colors.yellow,colors.gray}}
|
||||
network.update(types.PANEL_LINK_STATE.DISCONNECTED)
|
||||
|
||||
if not style.colorblind then
|
||||
local network = RGBLED{parent=system,label="NETWORK",colors={colors.green,colors.red,colors.orange,colors.yellow,style.fp_ind_bkg}}
|
||||
network.update(types.PANEL_LINK_STATE.DISCONNECTED)
|
||||
network.register(ps, "link_state", network.update)
|
||||
else
|
||||
local nt_lnk = LEDPair{parent=system,label="NT LINKED",off=style.fp_ind_bkg,c1=colors.red,c2=colors.green}
|
||||
local nt_ver = LEDPair{parent=system,label="NT VERSION",off=style.fp_ind_bkg,c1=colors.red,c2=colors.green}
|
||||
|
||||
nt_lnk.register(ps, "link_state", function (state)
|
||||
local value = 2
|
||||
|
||||
if state == LINK_STATE.DISCONNECTED then
|
||||
value = 1
|
||||
elseif state == LINK_STATE.LINKED then
|
||||
value = 3
|
||||
end
|
||||
|
||||
nt_lnk.update(value)
|
||||
end)
|
||||
|
||||
nt_ver.register(ps, "link_state", function (state)
|
||||
local value = 3
|
||||
|
||||
if state == LINK_STATE.BAD_VERSION then
|
||||
value = 2
|
||||
elseif state == LINK_STATE.DISCONNECTED then
|
||||
value = 1
|
||||
end
|
||||
|
||||
nt_ver.update(value)
|
||||
end)
|
||||
end
|
||||
|
||||
system.line_break()
|
||||
|
||||
modem.register(ps, "has_modem", modem.update)
|
||||
network.register(ps, "link_state", network.update)
|
||||
|
||||
local speaker = LED{parent=system,label="SPEAKER",colors=led_grn}
|
||||
speaker.register(ps, "has_speaker", speaker.update)
|
||||
|
||||
system.line_break()
|
||||
|
||||
local rt_main = LED{parent=system,label="RT MAIN",colors=led_grn}
|
||||
local rt_render = LED{parent=system,label="RT RENDER",colors=led_grn}
|
||||
|
||||
rt_main.register(ps, "routine__main", rt_main.update)
|
||||
rt_render.register(ps, "routine__render", rt_render.update)
|
||||
|
||||
---@diagnostic disable-next-line: undefined-field
|
||||
local comp_id = util.sprintf("(%d)", os.getComputerID())
|
||||
TextBox{parent=system,x=9,y=4,width=6,height=1,text=comp_id,fg_bg=style.fp_label}
|
||||
TextBox{parent=system,x=9,y=4,width=6,height=1,text=comp_id,fg_bg=style.fp.disabled_fg}
|
||||
|
||||
local monitors = Div{parent=main_page,width=16,height=17,x=18,y=2}
|
||||
|
||||
@@ -89,7 +131,7 @@ local function init(panel, num_units)
|
||||
-- about footer
|
||||
--
|
||||
|
||||
local about = Div{parent=main_page,width=15,height=3,x=1,y=16,fg_bg=style.fp_label}
|
||||
local about = Div{parent=main_page,width=15,height=3,x=1,y=16,fg_bg=style.fp.disabled_fg}
|
||||
local fw_v = TextBox{parent=about,x=1,y=1,text="FW: v00.00.00",alignment=ALIGN.LEFT,height=1}
|
||||
local comms_v = TextBox{parent=about,x=1,y=2,text="NT: v00.00.00",alignment=ALIGN.LEFT,height=1}
|
||||
|
||||
@@ -103,7 +145,7 @@ local function init(panel, num_units)
|
||||
-- API page
|
||||
|
||||
local api_page = Div{parent=page_div,x=1,y=1,hidden=true}
|
||||
local api_list = ListBox{parent=api_page,x=1,y=1,height=17,width=51,scroll_height=1000,fg_bg=style.fp_text,nav_fg_bg=cpair(colors.gray,colors.lightGray),nav_active=cpair(colors.black,colors.gray)}
|
||||
local api_list = ListBox{parent=api_page,x=1,y=1,height=17,width=51,scroll_height=1000,fg_bg=style.fp.text_fg,nav_fg_bg=cpair(colors.gray,colors.lightGray),nav_active=cpair(colors.black,colors.gray)}
|
||||
local _ = Div{parent=api_list,height=1,hidden=true} -- padding
|
||||
|
||||
-- assemble page panes
|
||||
@@ -113,11 +155,11 @@ local function init(panel, num_units)
|
||||
local page_pane = MultiPane{parent=page_div,x=1,y=1,panes=panes}
|
||||
|
||||
local tabs = {
|
||||
{ name = "CRD", color = style.fp_text },
|
||||
{ name = "API", color = style.fp_text },
|
||||
{ name = "CRD", color = style.fp.text },
|
||||
{ name = "API", color = style.fp.text },
|
||||
}
|
||||
|
||||
TabBar{parent=panel,y=2,tabs=tabs,min_width=9,callback=page_pane.set_value,fg_bg=style.bw_fg_bg}
|
||||
TabBar{parent=panel,y=2,tabs=tabs,min_width=9,callback=page_pane.set_value,fg_bg=style.fp_theme.highlight_box_bright}
|
||||
|
||||
-- link pocket API list management to PGI
|
||||
pgi.link_elements(api_list, pkt_entry)
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
-- Main SCADA Coordinator GUI
|
||||
--
|
||||
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local iocontrol = require("coordinator.iocontrol")
|
||||
|
||||
local style = require("coordinator.ui.style")
|
||||
@@ -21,14 +23,16 @@ local ALIGN = core.ALIGN
|
||||
-- create new main view
|
||||
---@param main graphics_element main displaybox
|
||||
local function init(main)
|
||||
local s_header = style.theme.header
|
||||
|
||||
local facility = iocontrol.get_db().facility
|
||||
local units = iocontrol.get_db().units
|
||||
|
||||
-- window header message
|
||||
local header = TextBox{parent=main,y=1,text="Nuclear Generation Facility SCADA Coordinator",alignment=ALIGN.CENTER,height=1,fg_bg=style.header}
|
||||
local ping = DataIndicator{parent=main,x=1,y=1,label="SVTT",format="%d",value=0,unit="ms",lu_colors=style.lg_white,width=12,fg_bg=style.header}
|
||||
local header = TextBox{parent=main,y=1,text="Nuclear Generation Facility SCADA Coordinator",alignment=ALIGN.CENTER,height=1,fg_bg=s_header}
|
||||
local ping = DataIndicator{parent=main,x=1,y=1,label="SVTT",format="%d",value=0,unit="ms",lu_colors=style.lg_white,width=12,fg_bg=s_header}
|
||||
-- max length example: "01:23:45 AM - Wednesday, September 28 2022"
|
||||
local datetime = TextBox{parent=main,x=(header.get_width()-42),y=1,text="",alignment=ALIGN.RIGHT,width=42,height=1,fg_bg=style.header}
|
||||
local datetime = TextBox{parent=main,x=(header.get_width()-42),y=1,text="",alignment=ALIGN.RIGHT,width=42,height=1,fg_bg=s_header}
|
||||
|
||||
ping.register(facility.ps, "sv_ping", ping.update)
|
||||
datetime.register(facility.ps, "date_time", datetime.set_value)
|
||||
@@ -51,6 +55,8 @@ local function init(main)
|
||||
|
||||
cnc_y_start = cnc_y_start + row_1_height + 1
|
||||
|
||||
util.nop()
|
||||
|
||||
if facility.num_units >= 3 then
|
||||
-- base offset 3, spacing 1, max height of units 1 and 2
|
||||
local row_2_offset = cnc_y_start
|
||||
@@ -62,12 +68,12 @@ local function init(main)
|
||||
uo_4 = unit_overview(main, 84, row_2_offset, units[4])
|
||||
cnc_y_start = math.max(cnc_y_start, row_2_offset + uo_4.get_height() + 1)
|
||||
end
|
||||
|
||||
util.nop()
|
||||
end
|
||||
|
||||
-- command & control
|
||||
|
||||
cnc_y_start = cnc_y_start
|
||||
|
||||
-- induction matrix and process control interfaces are 24 tall + space needed for divider
|
||||
local cnc_bottom_align_start = main.get_height() - 26
|
||||
|
||||
@@ -79,6 +85,8 @@ local function init(main)
|
||||
|
||||
process_ctl(main, 2, cnc_bottom_align_start)
|
||||
|
||||
util.nop()
|
||||
|
||||
imatrix(main, 131, cnc_bottom_align_start, facility.induction_data_tbl[1], facility.induction_ps_tbl[1])
|
||||
end
|
||||
|
||||
|
||||
@@ -2,79 +2,141 @@
|
||||
-- Graphics Style Options
|
||||
--
|
||||
|
||||
local core = require("graphics.core")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local core = require("graphics.core")
|
||||
local themes = require("graphics.themes")
|
||||
|
||||
---@class crd_style
|
||||
local style = {}
|
||||
|
||||
local cpair = core.cpair
|
||||
|
||||
-- GLOBAL --
|
||||
|
||||
-- add color mappings for front panel
|
||||
colors.ivory = colors.pink
|
||||
colors.yellow_hc = colors.purple
|
||||
colors.red_off = colors.brown
|
||||
colors.yellow_off = colors.magenta
|
||||
colors.green_off = colors.lime
|
||||
|
||||
-- front panel styling
|
||||
|
||||
style.fp = {}
|
||||
style.fp_theme = themes.sandstone
|
||||
style.fp = themes.get_fp_style(style.fp_theme)
|
||||
|
||||
style.fp.root = cpair(colors.black, colors.ivory)
|
||||
style.fp.header = cpair(colors.black, colors.lightGray)
|
||||
|
||||
style.fp.colors = {
|
||||
{ c = colors.red, hex = 0xdf4949 }, -- RED ON
|
||||
{ c = colors.orange, hex = 0xffb659 },
|
||||
{ c = colors.yellow, hex = 0xf9fb53 }, -- YELLOW ON
|
||||
{ c = colors.lime, hex = 0x16665a }, -- GREEN OFF
|
||||
{ c = colors.green, hex = 0x6be551 }, -- GREEN ON
|
||||
{ c = colors.cyan, hex = 0x34bac8 },
|
||||
{ c = colors.lightBlue, hex = 0x6cc0f2 },
|
||||
{ c = colors.blue, hex = 0x0096ff },
|
||||
{ c = colors.purple, hex = 0xb156ee }, -- YELLOW HIGH CONTRAST
|
||||
{ c = colors.pink, hex = 0xdcd9ca }, -- IVORY
|
||||
{ c = colors.magenta, hex = 0x85862c }, -- YELLOW OFF
|
||||
-- { c = colors.white, hex = 0xdcd9ca },
|
||||
{ c = colors.lightGray, hex = 0xb1b8b3 },
|
||||
{ c = colors.gray, hex = 0x575757 },
|
||||
-- { c = colors.black, hex = 0x191919 },
|
||||
{ c = colors.brown, hex = 0x672223 } -- RED OFF
|
||||
}
|
||||
style.led_grn = cpair(colors.green, colors.green_off)
|
||||
|
||||
-- main GUI styling
|
||||
|
||||
style.root = cpair(colors.black, colors.lightGray)
|
||||
style.header = cpair(colors.white, colors.gray)
|
||||
style.label = cpair(colors.gray, colors.lightGray)
|
||||
---@class theme
|
||||
local smooth_stone = {
|
||||
text = colors.black,
|
||||
text_inv = colors.white,
|
||||
label = colors.gray,
|
||||
label_dark = colors.gray,
|
||||
disabled = colors.lightGray,
|
||||
bg = colors.lightGray,
|
||||
checkbox_bg = colors.black,
|
||||
accent_light = colors.white,
|
||||
accent_dark = colors.gray,
|
||||
|
||||
style.colors = {
|
||||
{ c = colors.red, hex = 0xdf4949 },
|
||||
{ c = colors.orange, hex = 0xffb659 },
|
||||
{ c = colors.yellow, hex = 0xfffc79 },
|
||||
{ c = colors.lime, hex = 0x80ff80 },
|
||||
{ c = colors.green, hex = 0x4aee8a },
|
||||
{ c = colors.cyan, hex = 0x34bac8 },
|
||||
{ c = colors.lightBlue, hex = 0x6cc0f2 },
|
||||
{ c = colors.blue, hex = 0x0096ff },
|
||||
{ c = colors.purple, hex = 0xb156ee },
|
||||
{ c = colors.pink, hex = 0xf26ba2 },
|
||||
{ c = colors.magenta, hex = 0xf9488a },
|
||||
-- { c = colors.white, hex = 0xf0f0f0 },
|
||||
{ c = colors.lightGray, hex = 0xcacaca },
|
||||
{ c = colors.gray, hex = 0x575757 },
|
||||
-- { c = colors.black, hex = 0x191919 },
|
||||
-- { c = colors.brown, hex = 0x7f664c }
|
||||
fuel_color = colors.black,
|
||||
|
||||
header = cpair(colors.white, colors.gray),
|
||||
|
||||
text_fg = cpair(colors.black, colors._INHERIT),
|
||||
label_fg = cpair(colors.gray, colors._INHERIT),
|
||||
disabled_fg = cpair(colors.lightGray, colors._INHERIT),
|
||||
|
||||
highlight_box = cpair(colors.black, colors.white),
|
||||
highlight_box_bright = cpair(colors.black, colors.white),
|
||||
field_box = cpair(colors.black, colors.white),
|
||||
|
||||
colors = themes.smooth_stone.colors,
|
||||
|
||||
-- color re-mappings for assistive modes
|
||||
color_modes = themes.smooth_stone.color_modes
|
||||
}
|
||||
|
||||
---@type theme
|
||||
local deepslate = {
|
||||
text = colors.white,
|
||||
text_inv = colors.black,
|
||||
label = colors.lightGray,
|
||||
label_dark = colors.gray,
|
||||
disabled = colors.gray,
|
||||
bg = colors.black,
|
||||
checkbox_bg = colors.gray,
|
||||
accent_light = colors.gray,
|
||||
accent_dark = colors.lightGray,
|
||||
|
||||
fuel_color = colors.lightGray,
|
||||
|
||||
header = cpair(colors.white, colors.gray),
|
||||
|
||||
text_fg = cpair(colors.white, colors._INHERIT),
|
||||
label_fg = cpair(colors.lightGray, colors._INHERIT),
|
||||
disabled_fg = cpair(colors.gray, colors._INHERIT),
|
||||
|
||||
highlight_box = cpair(colors.white, colors.gray),
|
||||
highlight_box_bright = cpair(colors.black, colors.lightGray),
|
||||
field_box = cpair(colors.white, colors.gray),
|
||||
|
||||
colors = themes.deepslate.colors,
|
||||
|
||||
-- color re-mappings for assistive modes
|
||||
color_modes = themes.deepslate.color_modes
|
||||
}
|
||||
|
||||
style.theme = smooth_stone
|
||||
|
||||
-- set themes per configurations
|
||||
---@param main UI_THEME main UI theme
|
||||
---@param fp FP_THEME front panel theme
|
||||
---@param color_mode COLOR_MODE the color mode to use
|
||||
function style.set_themes(main, fp, color_mode)
|
||||
local colorblind = color_mode ~= themes.COLOR_MODE.STANDARD and color_mode ~= themes.COLOR_MODE.STD_ON_BLACK
|
||||
local gray_ind_off = color_mode == themes.COLOR_MODE.STANDARD or color_mode == themes.COLOR_MODE.BLUE_IND
|
||||
|
||||
style.ind_bkg = colors.gray
|
||||
style.fp_ind_bkg = util.trinary(gray_ind_off, colors.gray, colors.black)
|
||||
style.ind_hi_box_bg = util.trinary(gray_ind_off, colors.gray, colors.black)
|
||||
|
||||
if main == themes.UI_THEME.SMOOTH_STONE then
|
||||
style.theme = smooth_stone
|
||||
style.ind_bkg = util.trinary(gray_ind_off, colors.gray, colors.black)
|
||||
elseif main == themes.UI_THEME.DEEPSLATE then
|
||||
style.theme = deepslate
|
||||
style.ind_hi_box_bg = util.trinary(gray_ind_off, colors.lightGray, colors.black)
|
||||
end
|
||||
|
||||
style.colorblind = colorblind
|
||||
|
||||
style.root = cpair(style.theme.text, style.theme.bg)
|
||||
style.label = cpair(style.theme.label, style.theme.bg)
|
||||
|
||||
-- high contrast text (also tags)
|
||||
style.hc_text = cpair(style.theme.text, style.theme.text_inv)
|
||||
-- text on default background
|
||||
style.text_colors = cpair(style.theme.text, style.theme.bg)
|
||||
-- label & unit colors
|
||||
style.lu_colors = cpair(style.theme.label, style.theme.label)
|
||||
-- label & unit colors (darker if set)
|
||||
style.lu_colors_dark = cpair(style.theme.label_dark, style.theme.label_dark)
|
||||
|
||||
style.ind_grn = cpair(util.trinary(colorblind, colors.blue, colors.green), style.ind_bkg)
|
||||
style.ind_yel = cpair(colors.yellow, style.ind_bkg)
|
||||
style.ind_red = cpair(colors.red, style.ind_bkg)
|
||||
style.ind_wht = cpair(colors.white, style.ind_bkg)
|
||||
|
||||
if fp == themes.FP_THEME.SANDSTONE then
|
||||
style.fp_theme = themes.sandstone
|
||||
elseif fp == themes.FP_THEME.BASALT then
|
||||
style.fp_theme = themes.basalt
|
||||
end
|
||||
|
||||
style.fp = themes.get_fp_style(style.fp_theme)
|
||||
end
|
||||
|
||||
-- COMMON COLOR PAIRS --
|
||||
|
||||
style.wh_gray = cpair(colors.white, colors.gray)
|
||||
|
||||
style.bw_fg_bg = cpair(colors.black, colors.white)
|
||||
style.text_colors = cpair(colors.black, colors.lightGray)
|
||||
style.lu_colors = cpair(colors.gray, colors.gray)
|
||||
|
||||
style.hzd_fg_bg = style.wh_gray
|
||||
style.dis_colors = cpair(colors.white, colors.lightGray)
|
||||
|
||||
@@ -82,15 +144,6 @@ style.lg_gray = cpair(colors.lightGray, colors.gray)
|
||||
style.lg_white = cpair(colors.lightGray, colors.white)
|
||||
style.gray_white = cpair(colors.gray, colors.white)
|
||||
|
||||
style.ind_grn = cpair(colors.green, colors.gray)
|
||||
style.ind_yel = cpair(colors.yellow, colors.gray)
|
||||
style.ind_red = cpair(colors.red, colors.gray)
|
||||
style.ind_wht = style.wh_gray
|
||||
|
||||
style.fp_text = cpair(colors.black, colors.ivory)
|
||||
style.fp_label = cpair(colors.lightGray, colors.ivory)
|
||||
style.led_grn = cpair(colors.green, colors.green_off)
|
||||
|
||||
-- UI COMPONENTS --
|
||||
|
||||
style.reactor = {
|
||||
|
||||
@@ -7,7 +7,7 @@ local flasher = require("graphics.flasher")
|
||||
|
||||
local core = {}
|
||||
|
||||
core.version = "2.1.1"
|
||||
core.version = "2.2.3"
|
||||
|
||||
core.flasher = flasher
|
||||
core.events = events
|
||||
@@ -61,6 +61,9 @@ end
|
||||
---@field blit_fgd string
|
||||
---@field blit_bkg string
|
||||
|
||||
-- add inherited flag, 3 isn't a pure color so it wouldn't be used
|
||||
colors._INHERIT = 3
|
||||
|
||||
-- create a new color pair definition
|
||||
---@nodiscard
|
||||
---@param a color
|
||||
|
||||
@@ -49,9 +49,11 @@ local element = {}
|
||||
---|indicator_light_args
|
||||
---|power_indicator_args
|
||||
---|rad_indicator_args
|
||||
---|signal_bar_args
|
||||
---|state_indicator_args
|
||||
---|tristate_indicator_light_args
|
||||
---|vbar_args
|
||||
---|app_multipane_args
|
||||
---|colormap_args
|
||||
---|displaybox_args
|
||||
---|div_args
|
||||
@@ -234,11 +236,24 @@ function element.new(args, child_offset_x, child_offset_y)
|
||||
|
||||
-- init colors
|
||||
if args.fg_bg ~= nil then
|
||||
protected.fg_bg = args.fg_bg
|
||||
elseif args.parent ~= nil then
|
||||
protected.fg_bg = args.parent.get_fg_bg()
|
||||
protected.fg_bg = core.cpair(args.fg_bg.fgd, args.fg_bg.bkg)
|
||||
end
|
||||
|
||||
if args.parent ~= nil then
|
||||
local p_fg_bg = args.parent.get_fg_bg()
|
||||
|
||||
if args.fg_bg == nil then
|
||||
protected.fg_bg = core.cpair(p_fg_bg.fgd, p_fg_bg.bkg)
|
||||
else
|
||||
if protected.fg_bg.fgd == colors._INHERIT then protected.fg_bg = core.cpair(p_fg_bg.fgd, protected.fg_bg.bkg) end
|
||||
if protected.fg_bg.bkg == colors._INHERIT then protected.fg_bg = core.cpair(protected.fg_bg.fgd, p_fg_bg.bkg) end
|
||||
end
|
||||
end
|
||||
|
||||
-- check colors
|
||||
element.assert(protected.fg_bg.fgd ~= colors._INHERIT, "could not determine foreground color to inherit")
|
||||
element.assert(protected.fg_bg.bkg ~= colors._INHERIT, "could not determine background color to inherit")
|
||||
|
||||
-- set colors
|
||||
protected.window.setBackgroundColor(protected.fg_bg.bkg)
|
||||
protected.window.setTextColor(protected.fg_bg.fgd)
|
||||
|
||||
109
graphics/elements/appmultipane.lua
Normal file
109
graphics/elements/appmultipane.lua
Normal file
@@ -0,0 +1,109 @@
|
||||
-- App Page Multi-Pane Display Graphics Element
|
||||
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local core = require("graphics.core")
|
||||
local element = require("graphics.element")
|
||||
local events = require("graphics.events")
|
||||
|
||||
local MOUSE_CLICK = core.events.MOUSE_CLICK
|
||||
|
||||
---@class app_multipane_args
|
||||
---@field panes table panes to swap between
|
||||
---@field nav_colors cpair on/off colors (a/b respectively) for page navigator
|
||||
---@field scroll_nav boolean? true to allow scrolling to change the active pane
|
||||
---@field drag_nav boolean? true to allow mouse dragging to change the active pane (on mouse up)
|
||||
---@field callback function? function to call when pane is changed by mouse interaction
|
||||
---@field parent graphics_element
|
||||
---@field id? string element id
|
||||
---@field x? integer 1 if omitted
|
||||
---@field y? integer auto incremented if omitted
|
||||
---@field width? integer parent width if omitted
|
||||
---@field height? integer parent height if omitted
|
||||
---@field gframe? graphics_frame frame instead of x/y/width/height
|
||||
---@field fg_bg? cpair foreground/background colors
|
||||
---@field hidden? boolean true to hide on initial draw
|
||||
|
||||
-- new app multipane element
|
||||
---@nodiscard
|
||||
---@param args app_multipane_args
|
||||
---@return graphics_element element, element_id id
|
||||
local function multipane(args)
|
||||
element.assert(type(args.panes) == "table", "panes is a required field")
|
||||
|
||||
-- create new graphics element base object
|
||||
local e = element.new(args)
|
||||
|
||||
e.value = 1
|
||||
|
||||
local nav_x_start = math.floor((e.frame.w / 2) - (#args.panes / 2)) + 1
|
||||
local nav_x_end = math.floor((e.frame.w / 2) - (#args.panes / 2)) + #args.panes
|
||||
|
||||
-- show the selected pane
|
||||
function e.redraw()
|
||||
for i = 1, #args.panes do args.panes[i].hide() end
|
||||
args.panes[e.value].show()
|
||||
|
||||
-- draw page indicator dots
|
||||
for i = 1, #args.panes do
|
||||
e.w_set_cur(nav_x_start + (i - 1), e.frame.h)
|
||||
e.w_set_fgd(util.trinary(i == e.value, args.nav_colors.color_a, args.nav_colors.color_b))
|
||||
e.w_write("\x07")
|
||||
end
|
||||
end
|
||||
|
||||
-- handle mouse interaction
|
||||
---@param event mouse_interaction mouse event
|
||||
function e.handle_mouse(event)
|
||||
local initial = e.value
|
||||
|
||||
if e.enabled then
|
||||
if event.current.y == e.frame.h and event.current.x >= nav_x_start and event.current.x <= nav_x_end then
|
||||
local id = event.current.x - nav_x_start + 1
|
||||
|
||||
if event.type == MOUSE_CLICK.TAP then
|
||||
e.set_value(id)
|
||||
elseif event.type == MOUSE_CLICK.UP then
|
||||
e.set_value(id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if args.scroll_nav then
|
||||
if event.type == events.MOUSE_CLICK.SCROLL_DOWN then
|
||||
e.set_value(e.value + 1)
|
||||
elseif event.type == events.MOUSE_CLICK.SCROLL_UP then
|
||||
e.set_value(e.value - 1)
|
||||
end
|
||||
end
|
||||
|
||||
if args.drag_nav then
|
||||
local x1, x2 = event.initial.x, event.current.x
|
||||
if event.type == events.MOUSE_CLICK.UP and e.in_frame_bounds(x1, event.initial.y) and e.in_frame_bounds(x1, event.current.y) then
|
||||
if x2 > x1 then
|
||||
e.set_value(e.value - 1)
|
||||
elseif x2 < x1 then
|
||||
e.set_value(e.value + 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if e.value ~= initial and type(args.callback) == "function" then args.callback(e.value) end
|
||||
end
|
||||
|
||||
-- select which pane is shown
|
||||
---@param value integer pane to show
|
||||
function e.set_value(value)
|
||||
if (e.value ~= value) and (value > 0) and (value <= #args.panes) then
|
||||
e.value = value
|
||||
e.redraw()
|
||||
end
|
||||
end
|
||||
|
||||
-- initial draw
|
||||
e.redraw()
|
||||
|
||||
return e.complete()
|
||||
end
|
||||
|
||||
return multipane
|
||||
@@ -138,23 +138,21 @@ local function hazard_button(args)
|
||||
-- handle mouse interaction
|
||||
---@param event mouse_interaction mouse event
|
||||
function e.handle_mouse(event)
|
||||
if e.enabled then
|
||||
if core.events.was_clicked(event.type) then
|
||||
-- change text color to indicate clicked
|
||||
e.w_set_fgd(args.accent)
|
||||
e.w_set_cur(3, 2)
|
||||
e.w_write(args.text)
|
||||
if e.enabled and core.events.was_clicked(event.type) and e.in_frame_bounds(event.current.x, event.current.y) then
|
||||
-- change text color to indicate clicked
|
||||
e.w_set_fgd(args.accent)
|
||||
e.w_set_cur(3, 2)
|
||||
e.w_write(args.text)
|
||||
|
||||
-- abort any other callbacks
|
||||
tcd.abort(on_timeout)
|
||||
tcd.abort(on_success)
|
||||
tcd.abort(on_failure)
|
||||
-- abort any other callbacks
|
||||
tcd.abort(on_timeout)
|
||||
tcd.abort(on_success)
|
||||
tcd.abort(on_failure)
|
||||
|
||||
-- 1.5 second timeout
|
||||
tcd.dispatch(1.5, on_timeout)
|
||||
-- 1.5 second timeout
|
||||
tcd.dispatch(1.5, on_timeout)
|
||||
|
||||
args.callback()
|
||||
end
|
||||
args.callback()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -90,7 +90,8 @@ local function radio_button(args)
|
||||
-- handle mouse interaction
|
||||
---@param event mouse_interaction mouse event
|
||||
function e.handle_mouse(event)
|
||||
if e.enabled and core.events.was_clicked(event.type) and (event.initial.y == event.current.y) then
|
||||
if e.enabled and core.events.was_clicked(event.type) and
|
||||
(event.initial.y == event.current.y) and e.in_frame_bounds(event.current.x, event.current.y) then
|
||||
-- determine what was pressed
|
||||
if args.options[event.current.y] ~= nil then
|
||||
e.value = event.current.y
|
||||
|
||||
@@ -127,20 +127,19 @@ local function spinbox(args)
|
||||
---@param event mouse_interaction mouse event
|
||||
function e.handle_mouse(event)
|
||||
-- only handle if on an increment or decrement arrow
|
||||
if e.enabled and core.events.was_clicked(event.type) and
|
||||
(event.current.x ~= dec_point_x) and (event.current.y ~= 2) then
|
||||
if event.current.x == event.initial.x and event.current.y == event.initial.y then
|
||||
local idx = util.trinary(event.current.x > dec_point_x, event.current.x - 1, event.current.x)
|
||||
if digits[idx] ~= nil then
|
||||
if event.current.y == 1 then
|
||||
digits[idx] = digits[idx] + 1
|
||||
elseif event.current.y == 3 then
|
||||
digits[idx] = digits[idx] - 1
|
||||
end
|
||||
|
||||
update_value()
|
||||
show_num()
|
||||
if e.enabled and core.events.was_clicked(event.type) and e.in_frame_bounds(event.current.x, event.current.y) and
|
||||
(event.current.x ~= dec_point_x) and (event.current.y ~= 2) and
|
||||
(event.current.x == event.initial.x) and (event.current.y == event.initial.y) then
|
||||
local idx = util.trinary(event.current.x > dec_point_x, event.current.x - 1, event.current.x)
|
||||
if digits[idx] ~= nil then
|
||||
if event.current.y == 1 then
|
||||
digits[idx] = digits[idx] + 1
|
||||
elseif event.current.y == 3 then
|
||||
digits[idx] = digits[idx] - 1
|
||||
end
|
||||
|
||||
update_value()
|
||||
show_num()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -58,14 +58,14 @@ local function switch_button(args)
|
||||
-- handle mouse interaction
|
||||
---@param event mouse_interaction mouse event
|
||||
function e.handle_mouse(event)
|
||||
if e.enabled and core.events.was_clicked(event.type) then
|
||||
if e.enabled and core.events.was_clicked(event.type) and e.in_frame_bounds(event.current.x, event.current.y) then
|
||||
e.value = not e.value
|
||||
e.redraw()
|
||||
args.callback(e.value)
|
||||
end
|
||||
end
|
||||
|
||||
-- set the value
|
||||
-- set the value (does not call the callback)
|
||||
---@param val boolean new value
|
||||
function e.set_value(val)
|
||||
e.value = val
|
||||
|
||||
@@ -98,7 +98,7 @@ local function tabbar(args)
|
||||
---@param event mouse_interaction mouse event
|
||||
function e.handle_mouse(event)
|
||||
-- determine what was pressed
|
||||
if e.enabled and core.events.was_clicked(event.type) then
|
||||
if e.enabled and core.events.was_clicked(event.type) and e.in_frame_bounds(event.current.x, event.current.y) then
|
||||
-- a button may have been pressed, which one was it?
|
||||
local tab_ini = which_tab(event.initial.x)
|
||||
local tab_cur = which_tab(event.current.x)
|
||||
|
||||
@@ -53,11 +53,11 @@ local function number_field(args)
|
||||
---@param event mouse_interaction mouse event
|
||||
function e.handle_mouse(event)
|
||||
-- only handle if on an increment or decrement arrow
|
||||
if e.enabled then
|
||||
if e.enabled and e.in_frame_bounds(event.current.x, event.current.y) then
|
||||
if core.events.was_clicked(event.type) then
|
||||
e.take_focus()
|
||||
|
||||
if event.type == MOUSE_CLICK.UP and e.in_frame_bounds(event.current.x, event.current.y) then
|
||||
if event.type == MOUSE_CLICK.UP then
|
||||
ifield.move_cursor(event.current.x)
|
||||
end
|
||||
elseif event.type == MOUSE_CLICK.DOUBLE_CLICK then
|
||||
|
||||
@@ -41,11 +41,11 @@ local function text_field(args)
|
||||
---@param event mouse_interaction mouse event
|
||||
function e.handle_mouse(event)
|
||||
-- only handle if on an increment or decrement arrow
|
||||
if e.enabled then
|
||||
if e.enabled and e.in_frame_bounds(event.current.x, event.current.y) then
|
||||
if core.events.was_clicked(event.type) then
|
||||
e.take_focus()
|
||||
|
||||
if event.type == MOUSE_CLICK.UP and e.in_frame_bounds(event.current.x, event.current.y) then
|
||||
if event.type == MOUSE_CLICK.UP then
|
||||
ifield.move_cursor(event.current.x)
|
||||
end
|
||||
elseif event.type == MOUSE_CLICK.DOUBLE_CLICK then
|
||||
|
||||
85
graphics/elements/indicators/signal.lua
Normal file
85
graphics/elements/indicators/signal.lua
Normal file
@@ -0,0 +1,85 @@
|
||||
-- Signal Bars Graphics Element
|
||||
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local element = require("graphics.element")
|
||||
|
||||
---@class signal_bar_args
|
||||
---@field compact? boolean true to use a single character (works better against edges that extend out colors)
|
||||
---@field colors_low_med? cpair color a for low signal quality, color b for medium signal quality
|
||||
---@field disconnect_color? color color for the 'x' on disconnect
|
||||
---@field parent graphics_element
|
||||
---@field id? string element id
|
||||
---@field x? integer 1 if omitted
|
||||
---@field y? integer auto incremented if omitted
|
||||
---@field fg_bg? cpair foreground/background colors (foreground is used for high signal quality)
|
||||
---@field hidden? boolean true to hide on initial draw
|
||||
|
||||
-- new signal bar
|
||||
---@nodiscard
|
||||
---@param args signal_bar_args
|
||||
---@return graphics_element element, element_id id
|
||||
local function signal_bar(args)
|
||||
args.height = 1
|
||||
args.width = util.trinary(args.compact, 1, 2)
|
||||
|
||||
-- create new graphics element base object
|
||||
local e = element.new(args)
|
||||
|
||||
e.value = 0
|
||||
|
||||
local blit_bkg = args.fg_bg.blit_bkg
|
||||
local blit_0, blit_1, blit_2, blit_3 = args.fg_bg.blit_fgd, args.fg_bg.blit_fgd, args.fg_bg.blit_fgd, args.fg_bg.blit_fgd
|
||||
|
||||
if type(args.colors_low_med) == "table" then
|
||||
blit_1 = args.colors_low_med.blit_a or blit_1
|
||||
blit_2 = args.colors_low_med.blit_b or blit_2
|
||||
end
|
||||
|
||||
if util.is_int(args.disconnect_color) then blit_0 = colors.toBlit(args.disconnect_color) end
|
||||
|
||||
-- on state change (0 = offline, 1 through 3 = low to high signal)
|
||||
---@param new_state integer signal state
|
||||
function e.on_update(new_state)
|
||||
e.value = new_state
|
||||
e.redraw()
|
||||
end
|
||||
|
||||
-- set signal state (0 = offline, 1 through 3 = low to high signal)
|
||||
---@param val integer signal state
|
||||
function e.set_value(val) e.on_update(val) end
|
||||
|
||||
-- draw label and signal bar
|
||||
function e.redraw()
|
||||
e.w_set_cur(1, 1)
|
||||
|
||||
if args.compact then
|
||||
if e.value == 1 then
|
||||
e.w_blit("\x90", blit_1, blit_bkg)
|
||||
elseif e.value == 2 then
|
||||
e.w_blit("\x94", blit_2, blit_bkg)
|
||||
elseif e.value == 3 then
|
||||
e.w_blit("\x95", blit_3, blit_bkg)
|
||||
else
|
||||
e.w_blit("x", blit_0, blit_bkg)
|
||||
end
|
||||
else
|
||||
if e.value == 1 then
|
||||
e.w_blit("\x9f ", blit_bkg .. blit_bkg, blit_1 .. blit_bkg)
|
||||
elseif e.value == 2 then
|
||||
e.w_blit("\x9f\x94", blit_bkg .. blit_2, blit_2 .. blit_bkg)
|
||||
elseif e.value == 3 then
|
||||
e.w_blit("\x9f\x81", blit_bkg .. blit_bkg, blit_3 .. blit_3)
|
||||
else
|
||||
e.w_blit(" x", blit_0 .. blit_0, blit_bkg .. blit_bkg)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- initial draw
|
||||
e.redraw()
|
||||
|
||||
return e.complete()
|
||||
end
|
||||
|
||||
return signal_bar
|
||||
418
graphics/themes.lua
Normal file
418
graphics/themes.lua
Normal file
@@ -0,0 +1,418 @@
|
||||
--
|
||||
-- Graphics Themes
|
||||
--
|
||||
|
||||
local core = require("graphics.core")
|
||||
|
||||
local cpair = core.cpair
|
||||
|
||||
---@class graphics_themes
|
||||
local themes = {}
|
||||
|
||||
-- add color mappings for front panels
|
||||
colors.ivory = colors.pink
|
||||
colors.green_hc = colors.cyan
|
||||
colors.yellow_hc = colors.purple
|
||||
colors.red_off = colors.brown
|
||||
colors.yellow_off = colors.magenta
|
||||
colors.green_off = colors.lime
|
||||
|
||||
--#region Types
|
||||
|
||||
---@enum UI_THEME
|
||||
themes.UI_THEME = { SMOOTH_STONE = 1, DEEPSLATE = 2 }
|
||||
themes.UI_THEME_NAMES = { "Smooth Stone", "Deepslate" }
|
||||
|
||||
-- attempts to get the string name of a main ui theme
|
||||
---@nodiscard
|
||||
---@param id any
|
||||
---@return string|nil
|
||||
function themes.ui_theme_name(id)
|
||||
if id == themes.UI_THEME.SMOOTH_STONE or
|
||||
id == themes.UI_THEME.DEEPSLATE then
|
||||
return themes.UI_THEME_NAMES[id]
|
||||
else return nil end
|
||||
end
|
||||
|
||||
---@enum FP_THEME
|
||||
themes.FP_THEME = { SANDSTONE = 1, BASALT = 2 }
|
||||
themes.FP_THEME_NAMES = { "Sandstone", "Basalt" }
|
||||
|
||||
-- attempts to get the string name of a front panel theme
|
||||
---@nodiscard
|
||||
---@param id any
|
||||
---@return string|nil
|
||||
function themes.fp_theme_name(id)
|
||||
if id == themes.FP_THEME.SANDSTONE or
|
||||
id == themes.FP_THEME.BASALT then
|
||||
return themes.FP_THEME_NAMES[id]
|
||||
else return nil end
|
||||
end
|
||||
|
||||
---@enum COLOR_MODE
|
||||
themes.COLOR_MODE = {
|
||||
STANDARD = 1,
|
||||
DEUTERANOPIA = 2,
|
||||
PROTANOPIA = 3,
|
||||
TRITANOPIA = 4,
|
||||
BLUE_IND = 5,
|
||||
STD_ON_BLACK = 6,
|
||||
BLUE_ON_BLACK = 7,
|
||||
NUM_MODES = 8
|
||||
}
|
||||
|
||||
themes.COLOR_MODE_NAMES = {
|
||||
"Standard",
|
||||
"Deuteranopia",
|
||||
"Protanopia",
|
||||
"Tritanopia",
|
||||
"Blue for 'Good'",
|
||||
"Standard + Black",
|
||||
"Blue + Black"
|
||||
}
|
||||
|
||||
-- attempts to get the string name of a color mode
|
||||
---@nodiscard
|
||||
---@param id any
|
||||
---@return string|nil
|
||||
function themes.color_mode_name(id)
|
||||
if id == themes.COLOR_MODE.STANDARD or
|
||||
id == themes.COLOR_MODE.DEUTERANOPIA or
|
||||
id == themes.COLOR_MODE.PROTANOPIA or
|
||||
id == themes.COLOR_MODE.TRITANOPIA or
|
||||
id == themes.COLOR_MODE.BLUE_IND or
|
||||
id == themes.COLOR_MODE.STD_ON_BLACK or
|
||||
id == themes.COLOR_MODE.BLUE_ON_BLACK then
|
||||
return themes.COLOR_MODE_NAMES[id]
|
||||
else return nil end
|
||||
end
|
||||
|
||||
--#endregion
|
||||
|
||||
--#region Front Panel Themes
|
||||
|
||||
---@class fp_theme
|
||||
themes.sandstone = {
|
||||
text = colors.black,
|
||||
label = colors.lightGray,
|
||||
label_dark = colors.gray,
|
||||
disabled = colors.lightGray,
|
||||
bg = colors.ivory,
|
||||
|
||||
header = cpair(colors.black, colors.lightGray),
|
||||
|
||||
highlight_box = cpair(colors.black, colors.lightGray),
|
||||
highlight_box_bright = cpair(colors.black, colors.white),
|
||||
field_box = cpair(colors.gray, colors.white),
|
||||
|
||||
colors = {
|
||||
{ c = colors.red, hex = 0xdf4949 },
|
||||
{ c = colors.orange, hex = 0xffb659 },
|
||||
{ c = colors.yellow, hex = 0xf9fb53 },
|
||||
{ c = colors.green_off, hex = 0x16665a },
|
||||
{ c = colors.green, hex = 0x6be551 },
|
||||
{ c = colors.green_hc, hex = 0x6be551 },
|
||||
{ c = colors.lightBlue, hex = 0x6cc0f2 },
|
||||
{ c = colors.blue, hex = 0x0096ff },
|
||||
{ c = colors.yellow_hc, hex = 0xe3bc2a },
|
||||
{ c = colors.ivory, hex = 0xdcd9ca },
|
||||
{ c = colors.yellow_off, hex = 0x85862c },
|
||||
{ c = colors.white, hex = 0xf0f0f0 },
|
||||
{ c = colors.lightGray, hex = 0xb1b8b3 },
|
||||
{ c = colors.gray, hex = 0x575757 },
|
||||
{ c = colors.black, hex = 0x191919 },
|
||||
{ c = colors.red_off, hex = 0x672223 }
|
||||
},
|
||||
|
||||
-- color re-mappings for assistive modes
|
||||
color_modes = {
|
||||
-- standard
|
||||
{},
|
||||
-- deuteranopia
|
||||
{
|
||||
{ c = colors.green, hex = 0x1081ff },
|
||||
{ c = colors.green_hc, hex = 0x1081ff },
|
||||
{ c = colors.green_off, hex = 0x141414 },
|
||||
{ c = colors.yellow, hex = 0xf7c311 },
|
||||
{ c = colors.yellow_off, hex = 0x141414 },
|
||||
{ c = colors.red, hex = 0xfb5615 },
|
||||
{ c = colors.red_off, hex = 0x141414 }
|
||||
},
|
||||
-- protanopia
|
||||
{
|
||||
{ c = colors.green, hex = 0x1081ff },
|
||||
{ c = colors.green_hc, hex = 0x1081ff },
|
||||
{ c = colors.green_off, hex = 0x141414 },
|
||||
{ c = colors.yellow, hex = 0xf5e633 },
|
||||
{ c = colors.yellow_off, hex = 0x141414 },
|
||||
{ c = colors.red, hex = 0xff521a },
|
||||
{ c = colors.red_off, hex = 0x141414 }
|
||||
},
|
||||
-- tritanopia
|
||||
{
|
||||
{ c = colors.green, hex = 0x40cbd7 },
|
||||
{ c = colors.green_hc, hex = 0x40cbd7 },
|
||||
{ c = colors.green_off, hex = 0x141414 },
|
||||
{ c = colors.yellow, hex = 0xffbc00 },
|
||||
{ c = colors.yellow_off, hex = 0x141414 },
|
||||
{ c = colors.red, hex = 0xff0000 },
|
||||
{ c = colors.red_off, hex = 0x141414 }
|
||||
},
|
||||
-- blue indicators
|
||||
{
|
||||
{ c = colors.green, hex = 0x1081ff },
|
||||
{ c = colors.green_hc, hex = 0x1081ff },
|
||||
{ c = colors.green_off, hex = 0x053466 },
|
||||
},
|
||||
-- standard, black backgrounds
|
||||
{
|
||||
{ c = colors.green_off, hex = 0x141414 },
|
||||
{ c = colors.yellow_off, hex = 0x141414 },
|
||||
{ c = colors.red_off, hex = 0x141414 }
|
||||
},
|
||||
-- blue indicators, black backgrounds
|
||||
{
|
||||
{ c = colors.green, hex = 0x1081ff },
|
||||
{ c = colors.green_hc, hex = 0x1081ff },
|
||||
{ c = colors.green_off, hex = 0x141414 },
|
||||
{ c = colors.yellow_off, hex = 0x141414 },
|
||||
{ c = colors.red_off, hex = 0x141414 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
---@type fp_theme
|
||||
themes.basalt = {
|
||||
text = colors.white,
|
||||
label = colors.gray,
|
||||
label_dark = colors.ivory,
|
||||
disabled = colors.lightGray,
|
||||
bg = colors.ivory,
|
||||
|
||||
header = cpair(colors.white, colors.gray),
|
||||
|
||||
highlight_box = cpair(colors.white, colors.gray),
|
||||
highlight_box_bright = cpair(colors.black, colors.lightGray),
|
||||
field_box = cpair(colors.white, colors.gray),
|
||||
|
||||
colors = {
|
||||
{ c = colors.red, hex = 0xf18486 },
|
||||
{ c = colors.orange, hex = 0xffb659 },
|
||||
{ c = colors.yellow, hex = 0xefe37c },
|
||||
{ c = colors.green_off, hex = 0x436b41 },
|
||||
{ c = colors.green, hex = 0x7ae175 },
|
||||
{ c = colors.green_hc, hex = 0x7ae175 },
|
||||
{ c = colors.lightBlue, hex = 0x7dc6f2 },
|
||||
{ c = colors.blue, hex = 0x56aae6 },
|
||||
{ c = colors.yellow_hc, hex = 0xe9cd68 },
|
||||
{ c = colors.ivory, hex = 0x4d4e52 },
|
||||
{ c = colors.yellow_off, hex = 0x757040 },
|
||||
{ c = colors.white, hex = 0xbfbfbf },
|
||||
{ c = colors.lightGray, hex = 0x848794 },
|
||||
{ c = colors.gray, hex = 0x5c5f68 },
|
||||
{ c = colors.black, hex = 0x333333 },
|
||||
{ c = colors.red_off, hex = 0x512d2d }
|
||||
},
|
||||
|
||||
color_modes = {
|
||||
-- standard
|
||||
{},
|
||||
-- deuteranopia
|
||||
{
|
||||
{ c = colors.green, hex = 0x65aeff },
|
||||
{ c = colors.green_hc, hex = 0x99c9ff },
|
||||
{ c = colors.green_off, hex = 0x333333 },
|
||||
{ c = colors.yellow, hex = 0xf7c311 },
|
||||
{ c = colors.yellow_off, hex = 0x333333 },
|
||||
{ c = colors.red, hex = 0xf18486 },
|
||||
{ c = colors.red_off, hex = 0x333333 }
|
||||
},
|
||||
-- protanopia
|
||||
{
|
||||
{ c = colors.green, hex = 0x65aeff },
|
||||
{ c = colors.green_hc, hex = 0x99c9ff },
|
||||
{ c = colors.green_off, hex = 0x333333 },
|
||||
{ c = colors.yellow, hex = 0xf5e633 },
|
||||
{ c = colors.yellow_off, hex = 0x333333 },
|
||||
{ c = colors.red, hex = 0xff8058 },
|
||||
{ c = colors.red_off, hex = 0x333333 }
|
||||
},
|
||||
-- tritanopia
|
||||
{
|
||||
{ c = colors.green, hex = 0x00ecff },
|
||||
{ c = colors.green_hc, hex = 0x00ecff },
|
||||
{ c = colors.green_off, hex = 0x333333 },
|
||||
{ c = colors.yellow, hex = 0xffbc00 },
|
||||
{ c = colors.yellow_off, hex = 0x333333 },
|
||||
{ c = colors.red, hex = 0xdf4949 },
|
||||
{ c = colors.red_off, hex = 0x333333 }
|
||||
},
|
||||
-- blue indicators
|
||||
{
|
||||
{ c = colors.green, hex = 0x65aeff },
|
||||
{ c = colors.green_hc, hex = 0x99c9ff },
|
||||
{ c = colors.green_off, hex = 0x365e8a },
|
||||
},
|
||||
-- standard, black backgrounds
|
||||
{
|
||||
{ c = colors.green_off, hex = 0x333333 },
|
||||
{ c = colors.yellow_off, hex = 0x333333 },
|
||||
{ c = colors.red_off, hex = 0x333333 }
|
||||
},
|
||||
-- blue indicators, black backgrounds
|
||||
{
|
||||
{ c = colors.green, hex = 0x65aeff },
|
||||
{ c = colors.green_hc, hex = 0x99c9ff },
|
||||
{ c = colors.green_off, hex = 0x333333 },
|
||||
{ c = colors.yellow_off, hex = 0x333333 },
|
||||
{ c = colors.red_off, hex = 0x333333 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-- get style fields for a front panel based on the provided theme
|
||||
---@param theme fp_theme
|
||||
function themes.get_fp_style(theme)
|
||||
---@class fp_style
|
||||
local style = {
|
||||
root = cpair(theme.text, theme.bg),
|
||||
|
||||
text = cpair(theme.text, theme.bg),
|
||||
text_fg = cpair(theme.text, colors._INHERIT),
|
||||
|
||||
label_fg = cpair(theme.label, colors._INHERIT),
|
||||
label_d_fg = cpair(theme.label_dark, colors._INHERIT),
|
||||
|
||||
disabled_fg = cpair(theme.disabled, colors._INHERIT)
|
||||
}
|
||||
|
||||
return style
|
||||
end
|
||||
|
||||
--#endregion
|
||||
|
||||
--#region Main UI Color Palettes
|
||||
|
||||
---@class ui_palette
|
||||
themes.smooth_stone = {
|
||||
colors = {
|
||||
{ c = colors.red, hex = 0xdf4949 },
|
||||
{ c = colors.orange, hex = 0xffb659 },
|
||||
{ c = colors.yellow, hex = 0xfffc79 },
|
||||
{ c = colors.lime, hex = 0x80ff80 },
|
||||
{ c = colors.green, hex = 0x4aee8a },
|
||||
{ c = colors.cyan, hex = 0x34bac8 },
|
||||
{ c = colors.lightBlue, hex = 0x6cc0f2 },
|
||||
{ c = colors.blue, hex = 0x0096ff },
|
||||
{ c = colors.purple, hex = 0xb156ee },
|
||||
{ c = colors.pink, hex = 0xf26ba2 },
|
||||
{ c = colors.magenta, hex = 0xf9488a },
|
||||
{ c = colors.white, hex = 0xf0f0f0 },
|
||||
{ c = colors.lightGray, hex = 0xcacaca },
|
||||
{ c = colors.gray, hex = 0x575757 },
|
||||
{ c = colors.black, hex = 0x191919 },
|
||||
{ c = colors.brown, hex = 0x7f664c }
|
||||
},
|
||||
|
||||
-- color re-mappings for assistive modes
|
||||
color_modes = {
|
||||
-- standard
|
||||
{},
|
||||
-- deuteranopia
|
||||
{
|
||||
{ c = colors.blue, hex = 0x1081ff },
|
||||
{ c = colors.yellow, hex = 0xf7c311 },
|
||||
{ c = colors.red, hex = 0xfb5615 }
|
||||
},
|
||||
-- protanopia
|
||||
{
|
||||
{ c = colors.blue, hex = 0x1081ff },
|
||||
{ c = colors.yellow, hex = 0xf5e633 },
|
||||
{ c = colors.red, hex = 0xff521a }
|
||||
},
|
||||
-- tritanopia
|
||||
{
|
||||
{ c = colors.blue, hex = 0x40cbd7 },
|
||||
{ c = colors.yellow, hex = 0xffbc00 },
|
||||
{ c = colors.red, hex = 0xff0000 }
|
||||
},
|
||||
-- blue indicators
|
||||
{
|
||||
{ c = colors.blue, hex = 0x1081ff },
|
||||
{ c = colors.yellow, hex = 0xfffc79 },
|
||||
{ c = colors.red, hex = 0xdf4949 }
|
||||
},
|
||||
-- standard, black backgrounds
|
||||
{},
|
||||
-- blue indicators, black backgrounds
|
||||
{
|
||||
{ c = colors.blue, hex = 0x1081ff },
|
||||
{ c = colors.yellow, hex = 0xfffc79 },
|
||||
{ c = colors.red, hex = 0xdf4949 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
---@type ui_palette
|
||||
themes.deepslate = {
|
||||
colors = {
|
||||
{ c = colors.red, hex = 0xeb6a6c },
|
||||
{ c = colors.orange, hex = 0xf2b86c },
|
||||
{ c = colors.yellow, hex = 0xd9cf81 },
|
||||
{ c = colors.lime, hex = 0x80ff80 },
|
||||
{ c = colors.green, hex = 0x70e19b },
|
||||
{ c = colors.cyan, hex = 0x7ccdd0 },
|
||||
{ c = colors.lightBlue, hex = 0x99ceef },
|
||||
{ c = colors.blue, hex = 0x60bcff },
|
||||
{ c = colors.purple, hex = 0xc38aea },
|
||||
{ c = colors.pink, hex = 0xff7fb8 },
|
||||
{ c = colors.magenta, hex = 0xf980dd },
|
||||
{ c = colors.white, hex = 0xd9d9d9 },
|
||||
{ c = colors.lightGray, hex = 0x949494 },
|
||||
{ c = colors.gray, hex = 0x575757 },
|
||||
{ c = colors.black, hex = 0x262626 },
|
||||
{ c = colors.brown, hex = 0xb18f6a }
|
||||
},
|
||||
|
||||
-- color re-mappings for assistive modes
|
||||
color_modes = {
|
||||
-- standard
|
||||
{},
|
||||
-- deuteranopia
|
||||
{
|
||||
{ c = colors.blue, hex = 0x65aeff },
|
||||
{ c = colors.yellow, hex = 0xf7c311 },
|
||||
{ c = colors.red, hex = 0xfb5615 }
|
||||
},
|
||||
-- protanopia
|
||||
{
|
||||
{ c = colors.blue, hex = 0x65aeff },
|
||||
{ c = colors.yellow, hex = 0xf5e633 },
|
||||
{ c = colors.red, hex = 0xff8058 }
|
||||
},
|
||||
-- tritanopia
|
||||
{
|
||||
{ c = colors.blue, hex = 0x00ecff },
|
||||
{ c = colors.yellow, hex = 0xffbc00 },
|
||||
{ c = colors.red, hex = 0xdf4949 }
|
||||
},
|
||||
-- blue indicators
|
||||
{
|
||||
{ c = colors.blue, hex = 0x65aeff },
|
||||
{ c = colors.yellow, hex = 0xd9cf81 },
|
||||
{ c = colors.red, hex = 0xeb6a6c }
|
||||
},
|
||||
-- standard, black backgrounds
|
||||
{},
|
||||
-- blue indicators, black backgrounds
|
||||
{
|
||||
{ c = colors.blue, hex = 0x65aeff },
|
||||
{ c = colors.yellow, hex = 0xd9cf81 },
|
||||
{ c = colors.red, hex = 0xeb6a6c }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
--#endregion
|
||||
|
||||
return themes
|
||||
@@ -7,6 +7,7 @@ local tcd = require("scada-common.tcd")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local core = require("graphics.core")
|
||||
local themes = require("graphics.themes")
|
||||
|
||||
local DisplayBox = require("graphics.elements.displaybox")
|
||||
local Div = require("graphics.elements.div")
|
||||
@@ -41,21 +42,7 @@ local style = {}
|
||||
style.root = cpair(colors.black, colors.lightGray)
|
||||
style.header = cpair(colors.white, colors.gray)
|
||||
|
||||
style.colors = {
|
||||
{ c = colors.red, hex = 0xdf4949 },
|
||||
{ c = colors.orange, hex = 0xffb659 },
|
||||
{ c = colors.yellow, hex = 0xfffc79 },
|
||||
{ c = colors.lime, hex = 0x80ff80 },
|
||||
{ c = colors.green, hex = 0x4aee8a },
|
||||
{ c = colors.cyan, hex = 0x34bac8 },
|
||||
{ c = colors.lightBlue, hex = 0x6cc0f2 },
|
||||
{ c = colors.blue, hex = 0x0096ff },
|
||||
{ c = colors.purple, hex = 0xb156ee },
|
||||
{ c = colors.pink, hex = 0xf26ba2 },
|
||||
{ c = colors.magenta, hex = 0xf9488a },
|
||||
{ c = colors.lightGray, hex = 0xcacaca },
|
||||
{ c = colors.gray, hex = 0x575757 }
|
||||
}
|
||||
style.colors = themes.smooth_stone.colors
|
||||
|
||||
local bw_fg_bg = cpair(colors.black, colors.white)
|
||||
local g_lg_fg_bg = cpair(colors.gray, colors.lightGray)
|
||||
@@ -112,7 +99,7 @@ local fields = {
|
||||
{ "AuthKey", "Facility Auth Key" , ""},
|
||||
{ "LogMode", "Log Mode", log.MODE.APPEND },
|
||||
{ "LogPath", "Log Path", "/log.txt" },
|
||||
{ "LogDebug","Log Debug Messages", false }
|
||||
{ "LogDebug", "Log Debug Messages", false }
|
||||
}
|
||||
|
||||
-- load data from the settings file
|
||||
|
||||
@@ -2,18 +2,17 @@
|
||||
-- I/O Control for Pocket Integration with Supervisor & Coordinator
|
||||
--
|
||||
|
||||
local psil = require("scada-common.psil")
|
||||
|
||||
local log = require("scada-common.log")
|
||||
local psil = require("scada-common.psil")
|
||||
local types = require("scada-common.types")
|
||||
|
||||
local ALARM = types.ALARM
|
||||
|
||||
local iocontrol = {}
|
||||
---@todo nominal trip time is ping (0ms to 10ms usually)
|
||||
local WARN_TT = 40
|
||||
local HIGH_TT = 80
|
||||
|
||||
---@class pocket_ioctl
|
||||
local io = {
|
||||
ps = psil.create()
|
||||
}
|
||||
local iocontrol = {}
|
||||
|
||||
---@enum POCKET_LINK_STATE
|
||||
local LINK_STATE = {
|
||||
@@ -23,23 +22,175 @@ local LINK_STATE = {
|
||||
LINKED = 3
|
||||
}
|
||||
|
||||
---@enum NAV_PAGE
|
||||
local NAV_PAGE = {
|
||||
HOME = 1,
|
||||
iocontrol.LINK_STATE = LINK_STATE
|
||||
|
||||
---@enum POCKET_APP_ID
|
||||
local APP_ID = {
|
||||
ROOT = 1,
|
||||
-- main app page
|
||||
UNITS = 2,
|
||||
REACTORS = 3,
|
||||
BOILERS = 4,
|
||||
TURBINES = 5,
|
||||
DIAG = 6,
|
||||
D_ALARMS = 7
|
||||
ABOUT = 3,
|
||||
-- diag app page
|
||||
ALARMS = 4,
|
||||
-- other
|
||||
DUMMY = 5,
|
||||
NUM_APPS = 5
|
||||
}
|
||||
|
||||
iocontrol.LINK_STATE = LINK_STATE
|
||||
iocontrol.NAV_PAGE = NAV_PAGE
|
||||
iocontrol.APP_ID = APP_ID
|
||||
|
||||
---@class pocket_ioctl
|
||||
local io = {
|
||||
version = "unknown",
|
||||
ps = psil.create()
|
||||
}
|
||||
|
||||
---@class nav_tree_page
|
||||
---@field _p nav_tree_page|nil page's parent
|
||||
---@field _c table page's children
|
||||
---@field nav_to function function to navigate to this page
|
||||
---@field switcher function|nil function to switch between children
|
||||
---@field tasks table tasks to run while viewing this page
|
||||
|
||||
-- allocate the page navigation system
|
||||
function iocontrol.alloc_nav()
|
||||
local self = {
|
||||
pane = nil, ---@type graphics_element
|
||||
apps = {},
|
||||
containers = {},
|
||||
cur_app = APP_ID.ROOT
|
||||
}
|
||||
|
||||
self.cur_page = self.root
|
||||
|
||||
---@class pocket_nav
|
||||
io.nav = {}
|
||||
|
||||
-- set the root pane element to switch between apps with
|
||||
---@param root_pane graphics_element
|
||||
function io.nav.set_pane(root_pane)
|
||||
self.pane = root_pane
|
||||
end
|
||||
|
||||
-- register an app
|
||||
---@param app_id POCKET_APP_ID app ID
|
||||
---@param container graphics_element element that contains this app (usually a Div)
|
||||
---@param pane graphics_element? multipane if this is a simple paned app, then nav_to must be a number
|
||||
function io.nav.register_app(app_id, container, pane)
|
||||
---@class pocket_app
|
||||
local app = {
|
||||
root = { _p = nil, _c = {}, nav_to = function () end, tasks = {} }, ---@type nav_tree_page
|
||||
cur_page = nil, ---@type nav_tree_page
|
||||
pane = pane,
|
||||
paned_pages = {}
|
||||
}
|
||||
|
||||
-- delayed set of the pane if it wasn't ready at the start
|
||||
---@param root_pane graphics_element multipane
|
||||
function app.set_root_pane(root_pane)
|
||||
app.pane = root_pane
|
||||
end
|
||||
|
||||
-- if a pane was provided, this will switch between numbered pages
|
||||
---@param idx integer page index
|
||||
function app.switcher(idx)
|
||||
if app.paned_pages[idx] then
|
||||
app.paned_pages[idx].nav_to()
|
||||
end
|
||||
end
|
||||
|
||||
-- create a new page entry in the app's page navigation tree
|
||||
---@param parent nav_tree_page? a parent page or nil to set this as the root
|
||||
---@param nav_to function|integer function to navigate to this page or pane index
|
||||
---@return nav_tree_page new_page this new page
|
||||
function app.new_page(parent, nav_to)
|
||||
---@type nav_tree_page
|
||||
local page = { _p = parent, _c = {}, nav_to = function () end, switcher = function () end, tasks = {} }
|
||||
|
||||
if parent == nil then
|
||||
app.root = page
|
||||
if app.cur_page == nil then app.cur_page = page end
|
||||
end
|
||||
|
||||
if type(nav_to) == "number" then
|
||||
app.paned_pages[nav_to] = page
|
||||
|
||||
function page.nav_to()
|
||||
app.cur_page = page
|
||||
if app.pane then app.pane.set_value(nav_to) end
|
||||
end
|
||||
else
|
||||
function page.nav_to()
|
||||
app.cur_page = page
|
||||
nav_to()
|
||||
end
|
||||
end
|
||||
|
||||
-- switch between children
|
||||
---@param id integer child ID
|
||||
function page.switcher(id) if page._c[id] then page._c[id].nav_to() end end
|
||||
|
||||
if parent ~= nil then
|
||||
table.insert(page._p._c, page)
|
||||
end
|
||||
|
||||
return page
|
||||
end
|
||||
|
||||
-- get the currently active page
|
||||
function app.get_current_page() return app.cur_page end
|
||||
|
||||
-- attempt to navigate up the tree
|
||||
---@return boolean success true if successfully navigated up
|
||||
function app.nav_up()
|
||||
local parent = app.cur_page._p
|
||||
if parent then parent.nav_to() end
|
||||
return parent ~= nil
|
||||
end
|
||||
|
||||
self.apps[app_id] = app
|
||||
self.containers[app_id] = container
|
||||
|
||||
return app
|
||||
end
|
||||
|
||||
-- get a list of the app containers (usually Div elements)
|
||||
function io.nav.get_containers() return self.containers end
|
||||
|
||||
-- open a given app
|
||||
---@param app_id POCKET_APP_ID
|
||||
function io.nav.open_app(app_id)
|
||||
if self.apps[app_id] then
|
||||
self.cur_app = app_id
|
||||
self.pane.set_value(app_id)
|
||||
else
|
||||
log.debug("tried to open unknown app")
|
||||
end
|
||||
end
|
||||
|
||||
-- get the currently active page
|
||||
---@return nav_tree_page
|
||||
function io.nav.get_current_page()
|
||||
return self.apps[self.cur_app].get_current_page()
|
||||
end
|
||||
|
||||
-- attempt to navigate up
|
||||
function io.nav.nav_up()
|
||||
local app = self.apps[self.cur_app] ---@type pocket_app
|
||||
log.debug("attempting app nav up for app " .. self.cur_app)
|
||||
|
||||
if not app.nav_up() then
|
||||
log.debug("internal app nav up failed, going to home screen")
|
||||
io.nav.open_app(APP_ID.ROOT)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- initialize facility-independent components of pocket iocontrol
|
||||
---@param comms pocket_comms
|
||||
function iocontrol.init_core(comms)
|
||||
iocontrol.alloc_nav()
|
||||
|
||||
---@class pocket_ioctl_diag
|
||||
io.diag = {}
|
||||
|
||||
@@ -76,29 +227,135 @@ function iocontrol.init_core(comms)
|
||||
alarm_buttons = {},
|
||||
tone_indicators = {} -- indicators to update from supervisor tone states
|
||||
}
|
||||
|
||||
---@class pocket_nav
|
||||
io.nav = {
|
||||
page = NAV_PAGE.HOME, ---@type NAV_PAGE
|
||||
sub_pages = { NAV_PAGE.HOME, NAV_PAGE.UNITS, NAV_PAGE.REACTORS, NAV_PAGE.BOILERS, NAV_PAGE.TURBINES, NAV_PAGE.DIAG },
|
||||
tasks = {}
|
||||
}
|
||||
|
||||
-- add a task to be performed periodically while on a given page
|
||||
---@param page NAV_PAGE page to add task to
|
||||
---@param task function function to execute
|
||||
function io.nav.register_task(page, task)
|
||||
if io.nav.tasks[page] == nil then io.nav.tasks[page] = {} end
|
||||
table.insert(io.nav.tasks[page], task)
|
||||
end
|
||||
end
|
||||
|
||||
-- initialize facility-dependent components of pocket iocontrol
|
||||
function iocontrol.init_fac() end
|
||||
---@param conf facility_conf configuration
|
||||
---@param temp_scale 1|2|3|4 temperature unit (1 = K, 2 = C, 3 = F, 4 = R)
|
||||
function iocontrol.init_fac(conf, temp_scale)
|
||||
-- temperature unit label and conversion function (from Kelvin)
|
||||
if temp_scale == 2 then
|
||||
io.temp_label = "\xb0C"
|
||||
io.temp_convert = function (t) return t - 273.15 end
|
||||
elseif temp_scale == 3 then
|
||||
io.temp_label = "\xb0F"
|
||||
io.temp_convert = function (t) return (1.8 * (t - 273.15)) + 32 end
|
||||
elseif temp_scale == 4 then
|
||||
io.temp_label = "\xb0R"
|
||||
io.temp_convert = function (t) return 1.8 * t end
|
||||
else
|
||||
io.temp_label = "K"
|
||||
io.temp_convert = function (t) return t end
|
||||
end
|
||||
|
||||
-- facility data structure
|
||||
---@class pioctl_facility
|
||||
io.facility = {
|
||||
num_units = conf.num_units,
|
||||
tank_mode = conf.cooling.fac_tank_mode,
|
||||
tank_defs = conf.cooling.fac_tank_defs,
|
||||
all_sys_ok = false,
|
||||
rtu_count = 0,
|
||||
|
||||
auto_ready = false,
|
||||
auto_active = false,
|
||||
auto_ramping = false,
|
||||
auto_saturated = false,
|
||||
|
||||
---@type WASTE_PRODUCT
|
||||
auto_current_waste_product = types.WASTE_PRODUCT.PLUTONIUM,
|
||||
auto_pu_fallback_active = false,
|
||||
|
||||
radiation = types.new_zero_radiation_reading(),
|
||||
|
||||
ps = psil.create(),
|
||||
|
||||
induction_ps_tbl = {},
|
||||
induction_data_tbl = {},
|
||||
|
||||
sps_ps_tbl = {},
|
||||
sps_data_tbl = {},
|
||||
|
||||
tank_ps_tbl = {},
|
||||
tank_data_tbl = {},
|
||||
|
||||
env_d_ps = psil.create(),
|
||||
env_d_data = {}
|
||||
}
|
||||
end
|
||||
|
||||
-- set network link state
|
||||
---@param state POCKET_LINK_STATE
|
||||
function iocontrol.report_link_state(state) io.ps.publish("link_state", state) end
|
||||
function iocontrol.report_link_state(state)
|
||||
io.ps.publish("link_state", state)
|
||||
|
||||
if state == LINK_STATE.API_LINK_ONLY or state == LINK_STATE.UNLINKED then
|
||||
io.ps.publish("svr_conn_quality", 0)
|
||||
end
|
||||
|
||||
if state == LINK_STATE.SV_LINK_ONLY or state == LINK_STATE.UNLINKED then
|
||||
io.ps.publish("crd_conn_quality", 0)
|
||||
end
|
||||
end
|
||||
|
||||
-- determine supervisor connection quality (trip time)
|
||||
---@param trip_time integer
|
||||
function iocontrol.report_svr_tt(trip_time)
|
||||
local state = 3
|
||||
if trip_time > HIGH_TT then
|
||||
state = 1
|
||||
elseif trip_time > WARN_TT then
|
||||
state = 2
|
||||
end
|
||||
|
||||
io.ps.publish("svr_conn_quality", state)
|
||||
end
|
||||
|
||||
-- determine coordinator connection quality (trip time)
|
||||
---@param trip_time integer
|
||||
function iocontrol.report_crd_tt(trip_time)
|
||||
local state = 3
|
||||
if trip_time > HIGH_TT then
|
||||
state = 1
|
||||
elseif trip_time > WARN_TT then
|
||||
state = 2
|
||||
end
|
||||
|
||||
io.ps.publish("crd_conn_quality", state)
|
||||
end
|
||||
|
||||
-- populate facility data from API_GET_FAC
|
||||
---@param data table
|
||||
---@return boolean valid
|
||||
function iocontrol.record_facility_data(data)
|
||||
local valid = true
|
||||
|
||||
local fac = io.facility
|
||||
|
||||
fac.all_sys_ok = data[1]
|
||||
fac.rtu_count = data[2]
|
||||
fac.radiation = data[3]
|
||||
|
||||
-- auto control
|
||||
if type(data[4]) == "table" and #data[4] == 4 then
|
||||
fac.auto_ready = data[4][1]
|
||||
fac.auto_active = data[4][2]
|
||||
fac.auto_ramping = data[4][3]
|
||||
fac.auto_saturated = data[4][4]
|
||||
end
|
||||
|
||||
-- waste
|
||||
if type(data[5]) == "table" and #data[5] == 2 then
|
||||
fac.auto_current_waste_product = data[5][1]
|
||||
fac.auto_pu_fallback_active = data[5][2]
|
||||
end
|
||||
|
||||
fac.num_tanks = data[6]
|
||||
fac.has_imatrix = data[7]
|
||||
fac.has_sps = data[8]
|
||||
|
||||
return valid
|
||||
end
|
||||
|
||||
-- get the IO controller database
|
||||
function iocontrol.get_db() return io end
|
||||
|
||||
@@ -8,6 +8,7 @@ local PROTOCOL = comms.PROTOCOL
|
||||
local DEVICE_TYPE = comms.DEVICE_TYPE
|
||||
local ESTABLISH_ACK = comms.ESTABLISH_ACK
|
||||
local MGMT_TYPE = comms.MGMT_TYPE
|
||||
local CRDN_TYPE = comms.CRDN_TYPE
|
||||
|
||||
local LINK_STATE = iocontrol.LINK_STATE
|
||||
|
||||
@@ -125,7 +126,7 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
|
||||
|
||||
-- attempt coordinator API connection establishment
|
||||
local function _send_api_establish()
|
||||
_send_crd(MGMT_TYPE.ESTABLISH, { comms.version, version, DEVICE_TYPE.PKT })
|
||||
_send_crd(MGMT_TYPE.ESTABLISH, { comms.version, version, DEVICE_TYPE.PKT, comms.api_version })
|
||||
end
|
||||
|
||||
-- keep alive ack to supervisor
|
||||
@@ -246,6 +247,25 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
|
||||
return pkt
|
||||
end
|
||||
|
||||
---@param packet mgmt_frame|crdn_frame
|
||||
---@param length integer
|
||||
---@param max integer?
|
||||
---@return boolean
|
||||
local function _check_length(packet, length, max)
|
||||
local ok = util.trinary(max == nil, packet.length == length, packet.length >= length and packet.length <= (max or 0))
|
||||
if not ok then
|
||||
local fmt = "[comms] RX_PACKET{r_chan=%d,proto=%d,type=%d}: packet length mismatch -> expect %d != actual %d"
|
||||
log.debug(util.sprintf(fmt, packet.scada_frame.remote_channel(), packet.scada_frame.protocol(), packet.type))
|
||||
end
|
||||
return ok
|
||||
end
|
||||
|
||||
---@param packet mgmt_frame|crdn_frame
|
||||
local function _fail_type(packet)
|
||||
local fmt = "[comms] RX_PACKET{r_chan=%d,proto=%d,type=%d}: unrecognized packet type"
|
||||
log.debug(util.sprintf(fmt, packet.scada_frame.remote_channel(), packet.scada_frame.protocol(), packet.type))
|
||||
end
|
||||
|
||||
-- handle a packet
|
||||
---@param packet mgmt_frame|crdn_frame|nil
|
||||
function public.handle_packet(packet)
|
||||
@@ -268,7 +288,7 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
|
||||
return
|
||||
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 ..
|
||||
"); channel in use by another system?")
|
||||
"); channel in use by another system?")
|
||||
return
|
||||
else
|
||||
self.api.r_seq_num = packet.scada_frame.seq_num()
|
||||
@@ -277,12 +297,24 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
|
||||
-- feed watchdog on valid sequence number
|
||||
api_watchdog.feed()
|
||||
|
||||
if protocol == PROTOCOL.SCADA_MGMT then
|
||||
if protocol == PROTOCOL.SCADA_CRDN then
|
||||
---@cast packet crdn_frame
|
||||
if self.api.linked then
|
||||
if packet.type == CRDN_TYPE.API_GET_FAC then
|
||||
if _check_length(packet, 11) then
|
||||
iocontrol.record_facility_data(packet.data)
|
||||
end
|
||||
elseif packet.type == CRDN_TYPE.API_GET_UNITS then
|
||||
else _fail_type(packet) end
|
||||
else
|
||||
log.debug("discarding coordinator SCADA_CRDN packet before linked")
|
||||
end
|
||||
elseif protocol == PROTOCOL.SCADA_MGMT then
|
||||
---@cast packet mgmt_frame
|
||||
if self.api.linked then
|
||||
if packet.type == MGMT_TYPE.KEEP_ALIVE then
|
||||
-- keep alive request received, echo back
|
||||
if packet.length == 1 then
|
||||
if _check_length(packet, 1) then
|
||||
local timestamp = packet.data[1]
|
||||
local trip_time = util.time() - timestamp
|
||||
|
||||
@@ -290,11 +322,11 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
|
||||
log.warning("pocket coordinator KEEP_ALIVE trip time > 750ms (" .. trip_time .. "ms)")
|
||||
end
|
||||
|
||||
-- log.debug("pocket coordinator RTT = " .. trip_time .. "ms")
|
||||
-- log.debug("pocket coordinator TT = " .. trip_time .. "ms")
|
||||
|
||||
_send_api_keep_alive_ack(timestamp)
|
||||
else
|
||||
log.debug("coordinator SCADA keep alive packet length mismatch")
|
||||
|
||||
iocontrol.report_crd_tt(trip_time)
|
||||
end
|
||||
elseif packet.type == MGMT_TYPE.CLOSE then
|
||||
-- handle session close
|
||||
@@ -303,24 +335,38 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
|
||||
self.api.r_seq_num = nil
|
||||
self.api.addr = comms.BROADCAST
|
||||
log.info("coordinator server connection closed by remote host")
|
||||
else
|
||||
log.debug("received unknown SCADA_MGMT packet type " .. packet.type .. " from coordinator")
|
||||
end
|
||||
else _fail_type(packet) end
|
||||
elseif packet.type == MGMT_TYPE.ESTABLISH then
|
||||
-- connection with coordinator established
|
||||
if packet.length == 1 then
|
||||
if _check_length(packet, 1, 2) then
|
||||
local est_ack = packet.data[1]
|
||||
|
||||
if est_ack == ESTABLISH_ACK.ALLOW then
|
||||
log.info("coordinator connection established")
|
||||
self.establish_delay_counter = 0
|
||||
self.api.linked = true
|
||||
self.api.addr = src_addr
|
||||
if packet.length == 2 then
|
||||
local fac_config = packet.data[2]
|
||||
|
||||
if self.sv.linked then
|
||||
iocontrol.report_link_state(LINK_STATE.LINKED)
|
||||
if type(fac_config) == "table" and #fac_config == 2 then
|
||||
-- get configuration
|
||||
local conf = { num_units = fac_config[1], cooling = fac_config[2] }
|
||||
|
||||
---@todo unit options
|
||||
iocontrol.init_fac(conf, 1)
|
||||
|
||||
log.info("coordinator connection established")
|
||||
self.establish_delay_counter = 0
|
||||
self.api.linked = true
|
||||
self.api.addr = src_addr
|
||||
|
||||
if self.sv.linked then
|
||||
iocontrol.report_link_state(LINK_STATE.LINKED)
|
||||
else
|
||||
iocontrol.report_link_state(LINK_STATE.API_LINK_ONLY)
|
||||
end
|
||||
else
|
||||
log.debug("invalid facility configuration table received from coordinator, establish failed")
|
||||
end
|
||||
else
|
||||
iocontrol.report_link_state(LINK_STATE.API_LINK_ONLY)
|
||||
log.debug("received coordinator establish allow without facility configuration")
|
||||
end
|
||||
elseif est_ack == ESTABLISH_ACK.DENY then
|
||||
if self.api.last_est_ack ~= est_ack then
|
||||
@@ -334,13 +380,15 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
|
||||
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
|
||||
log.debug("coordinator SCADA_MGMT establish packet reply unsupported")
|
||||
end
|
||||
|
||||
self.api.last_est_ack = est_ack
|
||||
else
|
||||
log.debug("coordinator SCADA_MGMT establish packet length mismatch")
|
||||
end
|
||||
else
|
||||
log.debug("discarding coordinator non-link SCADA_MGMT packet before linked")
|
||||
@@ -372,7 +420,7 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
|
||||
if self.sv.linked then
|
||||
if packet.type == MGMT_TYPE.KEEP_ALIVE then
|
||||
-- keep alive request received, echo back
|
||||
if packet.length == 1 then
|
||||
if _check_length(packet, 1) then
|
||||
local timestamp = packet.data[1]
|
||||
local trip_time = util.time() - timestamp
|
||||
|
||||
@@ -380,11 +428,11 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
|
||||
log.warning("pocket supervisor KEEP_ALIVE trip time > 750ms (" .. trip_time .. "ms)")
|
||||
end
|
||||
|
||||
-- log.debug("pocket supervisor RTT = " .. trip_time .. "ms")
|
||||
-- log.debug("pocket supervisor TT = " .. trip_time .. "ms")
|
||||
|
||||
_send_sv_keep_alive_ack(timestamp)
|
||||
else
|
||||
log.debug("supervisor SCADA keep alive packet length mismatch")
|
||||
|
||||
iocontrol.report_svr_tt(trip_time)
|
||||
end
|
||||
elseif packet.type == MGMT_TYPE.CLOSE then
|
||||
-- handle session close
|
||||
@@ -394,12 +442,10 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
|
||||
self.sv.addr = comms.BROADCAST
|
||||
log.info("supervisor server connection closed by remote host")
|
||||
elseif packet.type == MGMT_TYPE.DIAG_TONE_GET then
|
||||
if packet.length == 8 then
|
||||
if _check_length(packet, 8) then
|
||||
for i = 1, #packet.data do
|
||||
diag.tone_test.tone_indicators[i].update(packet.data[i] == true)
|
||||
end
|
||||
else
|
||||
log.debug("supervisor SCADA diag alarm states packet length mismatch")
|
||||
end
|
||||
elseif packet.type == MGMT_TYPE.DIAG_TONE_SET then
|
||||
if packet.length == 1 and packet.data[1] == false then
|
||||
@@ -438,12 +484,10 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
|
||||
else
|
||||
log.debug("supervisor SCADA diag alarm set packet length/type mismatch")
|
||||
end
|
||||
else
|
||||
log.debug("received unknown SCADA_MGMT packet type " .. packet.type .. " from supervisor")
|
||||
end
|
||||
else _fail_type(packet) end
|
||||
elseif packet.type == MGMT_TYPE.ESTABLISH then
|
||||
-- connection with supervisor established
|
||||
if packet.length == 1 then
|
||||
if _check_length(packet, 1) then
|
||||
local est_ack = packet.data[1]
|
||||
|
||||
if est_ack == ESTABLISH_ACK.ALLOW then
|
||||
@@ -474,15 +518,11 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
|
||||
end
|
||||
|
||||
self.sv.last_est_ack = est_ack
|
||||
else
|
||||
log.debug("supervisor SCADA_MGMT establish packet length mismatch")
|
||||
end
|
||||
else
|
||||
log.debug("discarding supervisor non-link SCADA_MGMT packet before linked")
|
||||
end
|
||||
else
|
||||
log.debug("illegal packet type " .. protocol .. " from supervisor", true)
|
||||
end
|
||||
else _fail_type(packet) end
|
||||
else
|
||||
log.debug("received packet from unconfigured channel " .. r_chan, true)
|
||||
end
|
||||
@@ -500,5 +540,4 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
|
||||
return public
|
||||
end
|
||||
|
||||
|
||||
return pocket
|
||||
|
||||
@@ -18,7 +18,7 @@ local iocontrol = require("pocket.iocontrol")
|
||||
local pocket = require("pocket.pocket")
|
||||
local renderer = require("pocket.renderer")
|
||||
|
||||
local POCKET_VERSION = "v0.7.1-alpha"
|
||||
local POCKET_VERSION = "v0.8.0-alpha"
|
||||
|
||||
local println = util.println
|
||||
local println_ts = util.println_ts
|
||||
@@ -54,6 +54,7 @@ log.info("BOOTING pocket.startup " .. POCKET_VERSION)
|
||||
log.info("========================================")
|
||||
|
||||
crash.set_env("pocket", POCKET_VERSION)
|
||||
crash.dbg_log_env()
|
||||
|
||||
----------------------------------------
|
||||
-- main application
|
||||
@@ -67,6 +68,9 @@ local function main()
|
||||
-- mount connected devices
|
||||
ppm.mount_all()
|
||||
|
||||
-- record version for GUI
|
||||
iocontrol.get_db().version = POCKET_VERSION
|
||||
|
||||
----------------------------------------
|
||||
-- setup communications & clocks
|
||||
----------------------------------------
|
||||
@@ -130,7 +134,7 @@ local function main()
|
||||
-- start connection watchdogs
|
||||
conn_wd.sv.feed()
|
||||
conn_wd.api.feed()
|
||||
log.debug("startup> conn watchdog started")
|
||||
log.debug("startup> conn watchdogs started")
|
||||
|
||||
local io_db = iocontrol.get_db()
|
||||
local nav = io_db.nav
|
||||
@@ -148,11 +152,8 @@ local function main()
|
||||
pocket_comms.link_update()
|
||||
|
||||
-- update any tasks for the active page
|
||||
if (type(nav.tasks[nav.page]) == "table") then
|
||||
for i = 1, #nav.tasks[nav.page] do
|
||||
nav.tasks[nav.page][i]()
|
||||
end
|
||||
end
|
||||
local page_tasks = nav.get_current_page().tasks
|
||||
for i = 1, #page_tasks do page_tasks[i]() end
|
||||
|
||||
loop_clock.start()
|
||||
elseif conn_wd.sv.is_timer(param1) then
|
||||
|
||||
@@ -1,58 +1,39 @@
|
||||
--
|
||||
-- Diagnostic Apps
|
||||
--
|
||||
|
||||
local iocontrol = require("pocket.iocontrol")
|
||||
|
||||
local core = require("graphics.core")
|
||||
|
||||
local Div = require("graphics.elements.div")
|
||||
local MultiPane = require("graphics.elements.multipane")
|
||||
local TextBox = require("graphics.elements.textbox")
|
||||
|
||||
local IndicatorLight = require("graphics.elements.indicators.light")
|
||||
|
||||
local App = require("graphics.elements.controls.app")
|
||||
local Checkbox = require("graphics.elements.controls.checkbox")
|
||||
local PushButton = require("graphics.elements.controls.push_button")
|
||||
local SwitchButton = require("graphics.elements.controls.switch_button")
|
||||
|
||||
local cpair = core.cpair
|
||||
|
||||
local NAV_PAGE = iocontrol.NAV_PAGE
|
||||
|
||||
local ALIGN = core.ALIGN
|
||||
|
||||
-- new diagnostics page view
|
||||
-- create diagnostic app pages
|
||||
---@param root graphics_element parent
|
||||
local function new_view(root)
|
||||
local function create_pages(root)
|
||||
local db = iocontrol.get_db()
|
||||
|
||||
local main = Div{parent=root,x=1,y=1}
|
||||
|
||||
local diag_home = Div{parent=main,x=1,y=1}
|
||||
|
||||
TextBox{parent=diag_home,text="Diagnostic Apps",x=1,y=2,height=1,alignment=ALIGN.CENTER}
|
||||
|
||||
local alarm_test = Div{parent=main,x=1,y=1}
|
||||
|
||||
local panes = { diag_home, alarm_test }
|
||||
|
||||
local page_pane = MultiPane{parent=main,x=1,y=1,panes=panes}
|
||||
|
||||
local function navigate_diag()
|
||||
page_pane.set_value(1)
|
||||
db.nav.page = NAV_PAGE.DIAG
|
||||
db.nav.sub_pages[NAV_PAGE.DIAG] = NAV_PAGE.DIAG
|
||||
end
|
||||
|
||||
local function navigate_alarm()
|
||||
page_pane.set_value(2)
|
||||
db.nav.page = NAV_PAGE.D_ALARMS
|
||||
db.nav.sub_pages[NAV_PAGE.DIAG] = NAV_PAGE.D_ALARMS
|
||||
end
|
||||
|
||||
------------------------
|
||||
-- Alarm Testing Page --
|
||||
------------------------
|
||||
|
||||
db.nav.register_task(NAV_PAGE.D_ALARMS, db.diag.tone_test.get_tone_states)
|
||||
local alarm_test = Div{parent=root,x=1,y=1}
|
||||
|
||||
local alarm_app = db.nav.register_app(iocontrol.APP_ID.ALARMS, alarm_test)
|
||||
|
||||
local page = alarm_app.new_page(nil, function () end)
|
||||
page.tasks = { db.diag.tone_test.get_tone_states }
|
||||
|
||||
local ttest = db.diag.tone_test
|
||||
|
||||
@@ -67,8 +48,6 @@ local function new_view(root)
|
||||
|
||||
ttest.ready_warn = TextBox{parent=audio,y=2,text="",height=1,alignment=ALIGN.CENTER,fg_bg=cpair(colors.yellow,colors.black)}
|
||||
|
||||
PushButton{parent=audio,x=13,y=18,text="\x11 BACK",min_width=8,fg_bg=cpair(colors.black,colors.lightGray),active_fg_bg=c_wht_gray,callback=navigate_diag}
|
||||
|
||||
local tones = Div{parent=audio,x=2,y=3,height=10,width=8,fg_bg=cpair(colors.black,colors.yellow)}
|
||||
|
||||
TextBox{parent=tones,text="Tones",height=1,alignment=ALIGN.CENTER,fg_bg=audio.get_fg_bg()}
|
||||
@@ -132,16 +111,6 @@ local function new_view(root)
|
||||
local t_8 = IndicatorLight{parent=states,x=6,label="8",colors=c_blue_gray}
|
||||
|
||||
ttest.tone_indicators = { t_1, t_2, t_3, t_4, t_5, t_6, t_7, t_8 }
|
||||
|
||||
--------------
|
||||
-- App List --
|
||||
--------------
|
||||
|
||||
App{parent=diag_home,x=3,y=4,text="\x0f",title="Alarm",callback=navigate_alarm,app_fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray)}
|
||||
App{parent=diag_home,x=10,y=4,text="\x1e",title="LoopT",callback=function()end,app_fg_bg=cpair(colors.black,colors.cyan)}
|
||||
App{parent=diag_home,x=17,y=4,text="@",title="Comps",callback=function()end,app_fg_bg=cpair(colors.black,colors.orange)}
|
||||
|
||||
return main
|
||||
end
|
||||
|
||||
return new_view
|
||||
return create_pages
|
||||
24
pocket/ui/apps/dummy_app.lua
Normal file
24
pocket/ui/apps/dummy_app.lua
Normal file
@@ -0,0 +1,24 @@
|
||||
--
|
||||
-- Placeholder App
|
||||
--
|
||||
|
||||
local iocontrol = require("pocket.iocontrol")
|
||||
|
||||
local core = require("graphics.core")
|
||||
|
||||
local Div = require("graphics.elements.div")
|
||||
local TextBox = require("graphics.elements.textbox")
|
||||
|
||||
-- create placeholder app page
|
||||
---@param root graphics_element parent
|
||||
local function create_pages(root)
|
||||
local db = iocontrol.get_db()
|
||||
|
||||
local main = Div{parent=root,x=1,y=1}
|
||||
|
||||
db.nav.register_app(iocontrol.APP_ID.DUMMY, main).new_page(nil, function () end)
|
||||
|
||||
TextBox{parent=main,text="This app is not implemented yet.",x=1,y=2,alignment=core.ALIGN.CENTER}
|
||||
end
|
||||
|
||||
return create_pages
|
||||
102
pocket/ui/apps/sys_apps.lua
Normal file
102
pocket/ui/apps/sys_apps.lua
Normal file
@@ -0,0 +1,102 @@
|
||||
--
|
||||
-- System Apps
|
||||
--
|
||||
|
||||
local comms = require("scada-common.comms")
|
||||
local lockbox = require("lockbox")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local iocontrol = require("pocket.iocontrol")
|
||||
|
||||
local core = require("graphics.core")
|
||||
|
||||
local Div = require("graphics.elements.div")
|
||||
local ListBox = require("graphics.elements.listbox")
|
||||
local MultiPane = require("graphics.elements.multipane")
|
||||
local TextBox = require("graphics.elements.textbox")
|
||||
|
||||
local PushButton = require("graphics.elements.controls.push_button")
|
||||
|
||||
local cpair = core.cpair
|
||||
|
||||
local ALIGN = core.ALIGN
|
||||
|
||||
-- create system app pages
|
||||
---@param root graphics_element parent
|
||||
local function create_pages(root)
|
||||
local db = iocontrol.get_db()
|
||||
|
||||
----------------
|
||||
-- About Page --
|
||||
----------------
|
||||
|
||||
local about_root = Div{parent=root,x=1,y=1}
|
||||
|
||||
local about_app = db.nav.register_app(iocontrol.APP_ID.ABOUT, about_root)
|
||||
|
||||
local about_page = about_app.new_page(nil, 1)
|
||||
local fw_page = about_app.new_page(about_page, 2)
|
||||
local hw_page = about_app.new_page(about_page, 3)
|
||||
|
||||
local about = Div{parent=about_root,x=1,y=2}
|
||||
|
||||
TextBox{parent=about,y=1,text="System Information",height=1,alignment=ALIGN.CENTER}
|
||||
|
||||
local btn_fg_bg = cpair(colors.lightBlue, colors.black)
|
||||
local btn_active = cpair(colors.white, colors.black)
|
||||
local label = cpair(colors.lightGray, colors.black)
|
||||
|
||||
PushButton{parent=about,x=2,y=3,text="Firmware >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=fw_page.nav_to}
|
||||
PushButton{parent=about,x=2,y=4,text="Host Details >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=hw_page.nav_to}
|
||||
|
||||
local fw_div = Div{parent=about_root,x=1,y=2}
|
||||
TextBox{parent=fw_div,y=1,text="Firmware Versions",height=1,alignment=ALIGN.CENTER}
|
||||
|
||||
PushButton{parent=fw_div,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=about_page.nav_to}
|
||||
|
||||
local fw_list_box = ListBox{parent=fw_div,x=1,y=3,scroll_height=100,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)}
|
||||
|
||||
local fw_list = Div{parent=fw_list_box,x=1,y=2,height=18}
|
||||
|
||||
TextBox{parent=fw_list,x=2,text="Pocket Version",height=1,alignment=ALIGN.LEFT,fg_bg=label}
|
||||
TextBox{parent=fw_list,x=2,text=db.version,height=1,alignment=ALIGN.LEFT}
|
||||
|
||||
fw_list.line_break()
|
||||
TextBox{parent=fw_list,x=2,text="Comms Version",height=1,alignment=ALIGN.LEFT,fg_bg=label}
|
||||
TextBox{parent=fw_list,x=2,text=comms.version,height=1,alignment=ALIGN.LEFT}
|
||||
|
||||
fw_list.line_break()
|
||||
TextBox{parent=fw_list,x=2,text="API Version",height=1,alignment=ALIGN.LEFT,fg_bg=label}
|
||||
TextBox{parent=fw_list,x=2,text=comms.api_version,height=1,alignment=ALIGN.LEFT}
|
||||
|
||||
fw_list.line_break()
|
||||
TextBox{parent=fw_list,x=2,text="Common Lib Version",height=1,alignment=ALIGN.LEFT,fg_bg=label}
|
||||
TextBox{parent=fw_list,x=2,text=util.version,height=1,alignment=ALIGN.LEFT}
|
||||
|
||||
fw_list.line_break()
|
||||
TextBox{parent=fw_list,x=2,text="Graphics Version",height=1,alignment=ALIGN.LEFT,fg_bg=label}
|
||||
TextBox{parent=fw_list,x=2,text=core.version,height=1,alignment=ALIGN.LEFT}
|
||||
|
||||
fw_list.line_break()
|
||||
TextBox{parent=fw_list,x=2,text="Lockbox Version",height=1,alignment=ALIGN.LEFT,fg_bg=label}
|
||||
TextBox{parent=fw_list,x=2,text=lockbox.version,height=1,alignment=ALIGN.LEFT}
|
||||
|
||||
local hw_div = Div{parent=about_root,x=1,y=2}
|
||||
TextBox{parent=hw_div,y=1,text="Host Versions",height=1,alignment=ALIGN.CENTER}
|
||||
|
||||
PushButton{parent=hw_div,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=about_page.nav_to}
|
||||
|
||||
hw_div.line_break()
|
||||
TextBox{parent=hw_div,x=2,text="Lua Version",height=1,alignment=ALIGN.LEFT,fg_bg=label}
|
||||
TextBox{parent=hw_div,x=2,text=_VERSION,height=1,alignment=ALIGN.LEFT}
|
||||
|
||||
hw_div.line_break()
|
||||
TextBox{parent=hw_div,x=2,text="Environment",height=1,alignment=ALIGN.LEFT,fg_bg=label}
|
||||
TextBox{parent=hw_div,x=2,text=_HOST,height=6,alignment=ALIGN.LEFT}
|
||||
|
||||
local root_pane = MultiPane{parent=about_root,x=1,y=1,panes={about,fw_div,hw_div}}
|
||||
|
||||
about_app.set_root_pane(root_pane)
|
||||
end
|
||||
|
||||
return create_pages
|
||||
@@ -4,27 +4,29 @@
|
||||
|
||||
local iocontrol = require("pocket.iocontrol")
|
||||
|
||||
local style = require("pocket.ui.style")
|
||||
local diag_apps = require("pocket.ui.apps.diag_apps")
|
||||
local dummy_app = require("pocket.ui.apps.dummy_app")
|
||||
local sys_apps = require("pocket.ui.apps.sys_apps")
|
||||
|
||||
local conn_waiting = require("pocket.ui.components.conn_waiting")
|
||||
|
||||
local boiler_page = require("pocket.ui.pages.boiler_page")
|
||||
local diag_page = require("pocket.ui.pages.diag_page")
|
||||
local home_page = require("pocket.ui.pages.home_page")
|
||||
local reactor_page = require("pocket.ui.pages.reactor_page")
|
||||
local turbine_page = require("pocket.ui.pages.turbine_page")
|
||||
local unit_page = require("pocket.ui.pages.unit_page")
|
||||
|
||||
local style = require("pocket.ui.style")
|
||||
|
||||
local core = require("graphics.core")
|
||||
|
||||
local Div = require("graphics.elements.div")
|
||||
local MultiPane = require("graphics.elements.multipane")
|
||||
local TextBox = require("graphics.elements.textbox")
|
||||
|
||||
local PushButton = require("graphics.elements.controls.push_button")
|
||||
local Sidebar = require("graphics.elements.controls.sidebar")
|
||||
|
||||
local SignalBar = require("graphics.elements.indicators.signal")
|
||||
|
||||
local LINK_STATE = iocontrol.LINK_STATE
|
||||
local NAV_PAGE = iocontrol.NAV_PAGE
|
||||
|
||||
local ALIGN = core.ALIGN
|
||||
|
||||
@@ -33,26 +35,27 @@ local cpair = core.cpair
|
||||
-- create new main view
|
||||
---@param main graphics_element main displaybox
|
||||
local function init(main)
|
||||
local nav = iocontrol.get_db().nav
|
||||
local ps = iocontrol.get_db().ps
|
||||
local db = iocontrol.get_db()
|
||||
|
||||
-- window header message
|
||||
TextBox{parent=main,y=1,text="",alignment=ALIGN.LEFT,height=1,fg_bg=style.header}
|
||||
TextBox{parent=main,y=1,text="DEV ALPHA APP S C ",alignment=ALIGN.LEFT,height=1,fg_bg=style.header}
|
||||
local svr_conn = SignalBar{parent=main,y=1,x=22,compact=true,colors_low_med=cpair(colors.red,colors.yellow),disconnect_color=colors.lightGray,fg_bg=cpair(colors.green,colors.gray)}
|
||||
local crd_conn = SignalBar{parent=main,y=1,x=26,compact=true,colors_low_med=cpair(colors.red,colors.yellow),disconnect_color=colors.lightGray,fg_bg=cpair(colors.green,colors.gray)}
|
||||
|
||||
--
|
||||
-- root panel panes (connection screens + main screen)
|
||||
--
|
||||
db.ps.subscribe("svr_conn_quality", svr_conn.set_value)
|
||||
db.ps.subscribe("crd_conn_quality", crd_conn.set_value)
|
||||
|
||||
--#region root panel panes (connection screens + main screen)
|
||||
|
||||
local root_pane_div = Div{parent=main,x=1,y=2}
|
||||
|
||||
local conn_sv_wait = conn_waiting(root_pane_div, 6, false)
|
||||
local conn_api_wait = conn_waiting(root_pane_div, 6, true)
|
||||
local main_pane = Div{parent=main,x=1,y=2}
|
||||
local root_panes = { conn_sv_wait, conn_api_wait, main_pane }
|
||||
|
||||
local root_pane = MultiPane{parent=root_pane_div,x=1,y=1,panes=root_panes}
|
||||
local root_pane = MultiPane{parent=root_pane_div,x=1,y=1,panes={conn_sv_wait,conn_api_wait,main_pane}}
|
||||
|
||||
root_pane.register(ps, "link_state", function (state)
|
||||
root_pane.register(db.ps, "link_state", function (state)
|
||||
if state == LINK_STATE.UNLINKED or state == LINK_STATE.API_LINK_ONLY then
|
||||
root_pane.set_value(1)
|
||||
elseif state == LINK_STATE.SV_LINK_ONLY then
|
||||
@@ -62,62 +65,33 @@ local function init(main)
|
||||
end
|
||||
end)
|
||||
|
||||
--
|
||||
-- main page panel panes & sidebar
|
||||
--
|
||||
--#endregion
|
||||
|
||||
--#region main page panel panes & sidebar
|
||||
|
||||
local page_div = Div{parent=main_pane,x=4,y=1}
|
||||
|
||||
local sidebar_tabs = {
|
||||
{
|
||||
char = "#",
|
||||
color = cpair(colors.black,colors.green)
|
||||
},
|
||||
{
|
||||
char = "U",
|
||||
color = cpair(colors.black,colors.yellow)
|
||||
},
|
||||
{
|
||||
char = "R",
|
||||
color = cpair(colors.black,colors.cyan)
|
||||
},
|
||||
{
|
||||
char = "B",
|
||||
color = cpair(colors.black,colors.lightGray)
|
||||
},
|
||||
{
|
||||
char = "T",
|
||||
color = cpair(colors.black,colors.white)
|
||||
},
|
||||
{
|
||||
char = "D",
|
||||
color = cpair(colors.black,colors.orange)
|
||||
}
|
||||
{ char = "#", color = cpair(colors.black, colors.green) }
|
||||
}
|
||||
|
||||
local panes = { home_page(page_div), unit_page(page_div), reactor_page(page_div), boiler_page(page_div), turbine_page(page_div), diag_page(page_div) }
|
||||
home_page(page_div)
|
||||
unit_page(page_div)
|
||||
|
||||
local page_pane = MultiPane{parent=page_div,x=1,y=1,panes=panes}
|
||||
diag_apps(page_div)
|
||||
sys_apps(page_div)
|
||||
dummy_app(page_div)
|
||||
|
||||
local function navigate_sidebar(page)
|
||||
if page == 1 then
|
||||
nav.page = nav.sub_pages[NAV_PAGE.HOME]
|
||||
elseif page == 2 then
|
||||
nav.page = nav.sub_pages[NAV_PAGE.UNITS]
|
||||
elseif page == 3 then
|
||||
nav.page = nav.sub_pages[NAV_PAGE.REACTORS]
|
||||
elseif page == 4 then
|
||||
nav.page = nav.sub_pages[NAV_PAGE.BOILERS]
|
||||
elseif page == 5 then
|
||||
nav.page = nav.sub_pages[NAV_PAGE.TURBINES]
|
||||
elseif page == 6 then
|
||||
nav.page = nav.sub_pages[NAV_PAGE.DIAG]
|
||||
end
|
||||
assert(#db.nav.get_containers() == iocontrol.APP_ID.NUM_APPS, "app IDs were not sequential or some apps weren't registered")
|
||||
|
||||
page_pane.set_value(page)
|
||||
end
|
||||
local page_pane = MultiPane{parent=page_div,x=1,y=1,panes=db.nav.get_containers()}
|
||||
db.nav.set_pane(page_pane)
|
||||
|
||||
Sidebar{parent=main_pane,x=1,y=1,tabs=sidebar_tabs,fg_bg=cpair(colors.white,colors.gray),callback=navigate_sidebar}
|
||||
Sidebar{parent=main_pane,x=1,y=1,tabs=sidebar_tabs,fg_bg=cpair(colors.white,colors.gray),callback=db.nav.open_app}
|
||||
|
||||
PushButton{parent=main_pane,x=1,y=19,text="\x1b",min_width=3,fg_bg=cpair(colors.white,colors.gray),active_fg_bg=cpair(colors.gray,colors.black),callback=db.nav.nav_up}
|
||||
|
||||
--#endregion
|
||||
end
|
||||
|
||||
return init
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
-- local style = require("pocket.ui.style")
|
||||
|
||||
local core = require("graphics.core")
|
||||
|
||||
local Div = require("graphics.elements.div")
|
||||
local TextBox = require("graphics.elements.textbox")
|
||||
|
||||
-- local cpair = core.cpair
|
||||
|
||||
local ALIGN = core.ALIGN
|
||||
|
||||
-- new boiler page view
|
||||
---@param root graphics_element parent
|
||||
local function new_view(root)
|
||||
local main = Div{parent=root,x=1,y=1}
|
||||
|
||||
TextBox{parent=main,text="BOILERS",x=1,y=1,height=1,alignment=ALIGN.CENTER}
|
||||
|
||||
return main
|
||||
end
|
||||
|
||||
return new_view
|
||||
@@ -1,21 +1,59 @@
|
||||
local core = require("graphics.core")
|
||||
--
|
||||
-- Main Home Page
|
||||
--
|
||||
|
||||
local Div = require("graphics.elements.div")
|
||||
local iocontrol = require("pocket.iocontrol")
|
||||
|
||||
local App = require("graphics.elements.controls.app")
|
||||
local core = require("graphics.core")
|
||||
|
||||
local AppMultiPane = require("graphics.elements.appmultipane")
|
||||
local Div = require("graphics.elements.div")
|
||||
local TextBox = require("graphics.elements.textbox")
|
||||
|
||||
local App = require("graphics.elements.controls.app")
|
||||
|
||||
local cpair = core.cpair
|
||||
|
||||
local APP_ID = iocontrol.APP_ID
|
||||
|
||||
local ALIGN = core.ALIGN
|
||||
|
||||
-- new home page view
|
||||
---@param root graphics_element parent
|
||||
local function new_view(root)
|
||||
local main = Div{parent=root,x=1,y=1}
|
||||
local db = iocontrol.get_db()
|
||||
|
||||
App{parent=main,x=3,y=2,text="\x17",title="PRC",callback=function()end,app_fg_bg=cpair(colors.black,colors.purple)}
|
||||
App{parent=main,x=10,y=2,text="\x15",title="CTL",callback=function()end,app_fg_bg=cpair(colors.black,colors.green)}
|
||||
App{parent=main,x=17,y=2,text="\x08",title="DEV",callback=function()end,app_fg_bg=cpair(colors.black,colors.lightGray)}
|
||||
App{parent=main,x=3,y=7,text="\x7f",title="Waste",callback=function()end,app_fg_bg=cpair(colors.black,colors.brown)}
|
||||
App{parent=main,x=10,y=7,text="\xb6",title="Guide",callback=function()end,app_fg_bg=cpair(colors.black,colors.cyan)}
|
||||
local main = Div{parent=root,x=1,y=1,height=19}
|
||||
|
||||
local app = db.nav.register_app(iocontrol.APP_ID.ROOT, main)
|
||||
|
||||
local apps_1 = Div{parent=main,x=1,y=1,height=15}
|
||||
local apps_2 = Div{parent=main,x=1,y=1,height=15}
|
||||
|
||||
local panes = { apps_1, apps_2 }
|
||||
|
||||
local app_pane = AppMultiPane{parent=main,x=1,y=1,height=18,panes=panes,active_color=colors.lightGray,nav_colors=cpair(colors.lightGray,colors.gray),scroll_nav=true,drag_nav=true,callback=app.switcher}
|
||||
|
||||
app.set_root_pane(app_pane)
|
||||
app.new_page(app.new_page(nil, 1), 2)
|
||||
|
||||
local function open(id) db.nav.open_app(id) end
|
||||
|
||||
local active_fg_bg = cpair(colors.white,colors.gray)
|
||||
|
||||
App{parent=apps_1,x=3,y=2,text="U",title="Units",callback=function()open(APP_ID.UNITS)end,app_fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=active_fg_bg}
|
||||
App{parent=apps_1,x=10,y=2,text="\x17",title="PRC",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.purple),active_fg_bg=active_fg_bg}
|
||||
App{parent=apps_1,x=17,y=2,text="\x15",title="CTL",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.green),active_fg_bg=active_fg_bg}
|
||||
App{parent=apps_1,x=3,y=7,text="\x08",title="DEV",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.lightGray),active_fg_bg=active_fg_bg}
|
||||
App{parent=apps_1,x=10,y=7,text="\x7f",title="Waste",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.brown),active_fg_bg=active_fg_bg}
|
||||
App{parent=apps_1,x=17,y=7,text="\xb6",title="Guide",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.cyan),active_fg_bg=active_fg_bg}
|
||||
App{parent=apps_1,x=3,y=12,text="?",title="About",callback=function()open(APP_ID.ABOUT)end,app_fg_bg=cpair(colors.black,colors.white),active_fg_bg=active_fg_bg}
|
||||
|
||||
TextBox{parent=apps_2,text="Diagnostic Apps",x=1,y=2,height=1,alignment=ALIGN.CENTER}
|
||||
|
||||
App{parent=apps_2,x=3,y=4,text="\x0f",title="Alarm",callback=function()open(APP_ID.ALARMS)end,app_fg_bg=cpair(colors.black,colors.red),active_fg_bg=active_fg_bg}
|
||||
App{parent=apps_2,x=10,y=4,text="\x1e",title="LoopT",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.cyan),active_fg_bg=active_fg_bg}
|
||||
App{parent=apps_2,x=17,y=4,text="@",title="Comps",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.orange),active_fg_bg=active_fg_bg}
|
||||
|
||||
return main
|
||||
end
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
-- local style = require("pocket.ui.style")
|
||||
|
||||
local core = require("graphics.core")
|
||||
|
||||
local Div = require("graphics.elements.div")
|
||||
local TextBox = require("graphics.elements.textbox")
|
||||
|
||||
-- local cpair = core.cpair
|
||||
|
||||
local ALIGN = core.ALIGN
|
||||
|
||||
-- new reactor page view
|
||||
---@param root graphics_element parent
|
||||
local function new_view(root)
|
||||
local main = Div{parent=root,x=1,y=1}
|
||||
|
||||
TextBox{parent=main,text="REACTOR",x=1,y=1,height=1,alignment=ALIGN.CENTER}
|
||||
|
||||
return main
|
||||
end
|
||||
|
||||
return new_view
|
||||
@@ -1,22 +0,0 @@
|
||||
-- local style = require("pocket.ui.style")
|
||||
|
||||
local core = require("graphics.core")
|
||||
|
||||
local Div = require("graphics.elements.div")
|
||||
local TextBox = require("graphics.elements.textbox")
|
||||
|
||||
-- local cpair = core.cpair
|
||||
|
||||
local ALIGN = core.ALIGN
|
||||
|
||||
-- new turbine page view
|
||||
---@param root graphics_element parent
|
||||
local function new_view(root)
|
||||
local main = Div{parent=root,x=1,y=1}
|
||||
|
||||
TextBox{parent=main,text="TURBINES",x=1,y=1,height=1,alignment=ALIGN.CENTER}
|
||||
|
||||
return main
|
||||
end
|
||||
|
||||
return new_view
|
||||
@@ -1,20 +1,29 @@
|
||||
-- local style = require("pocket.ui.style")
|
||||
--
|
||||
-- Unit Overview Page
|
||||
--
|
||||
|
||||
local core = require("graphics.core")
|
||||
local iocontrol = require("pocket.iocontrol")
|
||||
|
||||
local Div = require("graphics.elements.div")
|
||||
local TextBox = require("graphics.elements.textbox")
|
||||
local core = require("graphics.core")
|
||||
|
||||
-- local cpair = core.cpair
|
||||
local Div = require("graphics.elements.div")
|
||||
local TextBox = require("graphics.elements.textbox")
|
||||
|
||||
local ALIGN = core.ALIGN
|
||||
|
||||
-- new unit page view
|
||||
---@param root graphics_element parent
|
||||
local function new_view(root)
|
||||
local db = iocontrol.get_db()
|
||||
|
||||
local main = Div{parent=root,x=1,y=1}
|
||||
|
||||
TextBox{parent=main,text="UNITS",x=1,y=1,height=1,alignment=ALIGN.CENTER}
|
||||
local app = db.nav.register_app(iocontrol.APP_ID.UNITS, main)
|
||||
app.new_page(nil, function () end)
|
||||
|
||||
TextBox{parent=main,y=2,text="UNITS",height=1,alignment=ALIGN.CENTER}
|
||||
|
||||
TextBox{parent=main,y=4,text="work in progress",height=1,alignment=ALIGN.CENTER}
|
||||
|
||||
return main
|
||||
end
|
||||
|
||||
@@ -8,6 +8,7 @@ local tcd = require("scada-common.tcd")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local core = require("graphics.core")
|
||||
local themes = require("graphics.themes")
|
||||
|
||||
local DisplayBox = require("graphics.elements.displaybox")
|
||||
local Div = require("graphics.elements.div")
|
||||
@@ -23,6 +24,8 @@ 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 println = util.println
|
||||
local tri = util.trinary
|
||||
|
||||
@@ -34,8 +37,10 @@ local RIGHT = core.ALIGN.RIGHT
|
||||
|
||||
-- changes to the config data/format to let the user know
|
||||
local changes = {
|
||||
{"v1.6.2", { "AuthKey minimum length is now 8 (if set)" } },
|
||||
{"v1.6.8", { "ConnTimeout can now have a fractional part" } }
|
||||
{ "v1.6.2", { "AuthKey minimum length is now 8 (if set)" } },
|
||||
{ "v1.6.8", { "ConnTimeout can now have a fractional part" } },
|
||||
{ "v1.6.15", { "Added front panel UI theme", "Added color accessibility modes" } },
|
||||
{ "v1.7.3", { "Added standard with black off state color mode", "Added blue indicator color modes" } }
|
||||
}
|
||||
|
||||
---@class plc_configurator
|
||||
@@ -46,34 +51,25 @@ local style = {}
|
||||
style.root = cpair(colors.black, colors.lightGray)
|
||||
style.header = cpair(colors.white, colors.gray)
|
||||
|
||||
style.colors = {
|
||||
{ c = colors.red, hex = 0xdf4949 },
|
||||
{ c = colors.orange, hex = 0xffb659 },
|
||||
{ c = colors.yellow, hex = 0xfffc79 },
|
||||
{ c = colors.lime, hex = 0x80ff80 },
|
||||
{ c = colors.green, hex = 0x4aee8a },
|
||||
{ c = colors.cyan, hex = 0x34bac8 },
|
||||
{ c = colors.lightBlue, hex = 0x6cc0f2 },
|
||||
{ c = colors.blue, hex = 0x0096ff },
|
||||
{ c = colors.purple, hex = 0xb156ee },
|
||||
{ c = colors.pink, hex = 0xf26ba2 },
|
||||
{ c = colors.magenta, hex = 0xf9488a },
|
||||
{ c = colors.lightGray, hex = 0xcacaca },
|
||||
{ c = colors.gray, hex = 0x575757 }
|
||||
}
|
||||
style.colors = themes.smooth_stone.colors
|
||||
|
||||
local bw_fg_bg = cpair(colors.black, colors.white)
|
||||
local g_lg_fg_bg = cpair(colors.gray, colors.lightGray)
|
||||
local nav_fg_bg = bw_fg_bg
|
||||
local btn_act_fg_bg = cpair(colors.white, colors.gray)
|
||||
|
||||
---@class _plc_cfg_tool_ctl
|
||||
local tool_ctl = {
|
||||
ask_config = false,
|
||||
has_config = false,
|
||||
viewing_config = false,
|
||||
importing_legacy = false,
|
||||
jumped_to_color = false,
|
||||
|
||||
view_cfg = nil, ---@type graphics_element
|
||||
color_cfg = nil, ---@type graphics_element
|
||||
color_next = nil, ---@type graphics_element
|
||||
color_apply = nil, ---@type graphics_element
|
||||
settings_apply = nil, ---@type graphics_element
|
||||
|
||||
set_networked = nil, ---@type function
|
||||
@@ -103,6 +99,8 @@ local tmp_cfg = {
|
||||
LogMode = 0,
|
||||
LogPath = "",
|
||||
LogDebug = false,
|
||||
FrontPanelTheme = 1,
|
||||
ColorMode = 1
|
||||
}
|
||||
|
||||
---@class plc_config
|
||||
@@ -124,7 +122,9 @@ local fields = {
|
||||
{ "AuthKey", "Facility Auth Key" , ""},
|
||||
{ "LogMode", "Log Mode", log.MODE.APPEND },
|
||||
{ "LogPath", "Log Path", "/log.txt" },
|
||||
{ "LogDebug","Log Debug Messages", false }
|
||||
{ "LogDebug", "Log Debug Messages", false },
|
||||
{ "FrontPanelTheme", "Front Panel Theme", themes.FP_THEME.SANDSTONE },
|
||||
{ "ColorMode", "Color Mode", themes.COLOR_MODE.STANDARD }
|
||||
}
|
||||
|
||||
local side_options = { "Top", "Bottom", "Left", "Right", "Front", "Back" }
|
||||
@@ -176,10 +176,11 @@ local function config_view(display)
|
||||
local plc_cfg = Div{parent=root_pane_div,x=1,y=1}
|
||||
local net_cfg = Div{parent=root_pane_div,x=1,y=1}
|
||||
local log_cfg = Div{parent=root_pane_div,x=1,y=1}
|
||||
local clr_cfg = 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 main_pane = MultiPane{parent=root_pane_div,x=1,y=1,panes={main_page,plc_cfg,net_cfg,log_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}}
|
||||
|
||||
-- Main Page
|
||||
|
||||
@@ -196,7 +197,7 @@ local function config_view(display)
|
||||
tool_ctl.viewing_config = true
|
||||
tool_ctl.gen_summary(settings_cfg)
|
||||
tool_ctl.settings_apply.hide(true)
|
||||
main_pane.set_value(5)
|
||||
main_pane.set_value(6)
|
||||
end
|
||||
|
||||
if fs.exists("/reactor-plc/config.lua") then
|
||||
@@ -207,10 +208,21 @@ local function config_view(display)
|
||||
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)}
|
||||
|
||||
if not tool_ctl.has_config then tool_ctl.view_cfg.disable() end
|
||||
local function jump_color()
|
||||
tool_ctl.jumped_to_color = true
|
||||
tool_ctl.color_next.hide(true)
|
||||
tool_ctl.color_apply.show()
|
||||
main_pane.set_value(5)
|
||||
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=39,y=17,min_width=12,text="Change Log",callback=function()main_pane.set_value(6)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
tool_ctl.color_cfg = PushButton{parent=main_page,x=23,y=17,min_width=15,text="Color Options",callback=jump_color,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)}
|
||||
PushButton{parent=main_page,x=39,y=17,min_width=12,text="Change Log",callback=function()main_pane.set_value(7)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
if not tool_ctl.has_config then
|
||||
tool_ctl.view_cfg.disable()
|
||||
tool_ctl.color_cfg.disable()
|
||||
end
|
||||
|
||||
--#region PLC
|
||||
|
||||
@@ -419,10 +431,8 @@ local function config_view(display)
|
||||
tmp_cfg.LogMode = mode.get_value() - 1
|
||||
tmp_cfg.LogPath = path.get_value()
|
||||
tmp_cfg.LogDebug = en_dbg.get_value()
|
||||
tool_ctl.gen_summary(tmp_cfg)
|
||||
tool_ctl.viewing_config = false
|
||||
tool_ctl.importing_legacy = false
|
||||
tool_ctl.settings_apply.show()
|
||||
tool_ctl.color_apply.hide(true)
|
||||
tool_ctl.color_next.show()
|
||||
main_pane.set_value(5)
|
||||
else path_err.show() end
|
||||
end
|
||||
@@ -436,6 +446,116 @@ local function config_view(display)
|
||||
|
||||
--#endregion
|
||||
|
||||
--#region Color Options
|
||||
|
||||
local clr_c_1 = Div{parent=clr_cfg,x=2,y=4,width=49}
|
||||
local clr_c_2 = Div{parent=clr_cfg,x=2,y=4,width=49}
|
||||
local clr_c_3 = Div{parent=clr_cfg,x=2,y=4,width=49}
|
||||
local clr_c_4 = Div{parent=clr_cfg,x=2,y=4,width=49}
|
||||
|
||||
local clr_pane = MultiPane{parent=clr_cfg,x=1,y=4,panes={clr_c_1,clr_c_2,clr_c_3,clr_c_4}}
|
||||
|
||||
TextBox{parent=clr_cfg,x=1,y=2,height=1,text=" Color Configuration",fg_bg=cpair(colors.black,colors.magenta)}
|
||||
|
||||
TextBox{parent=clr_c_1,x=1,y=1,height=2,text="Here you can select the color theme for the front panel."}
|
||||
TextBox{parent=clr_c_1,x=1,y=4,height=2,text="Click 'Accessibility' below to access colorblind assistive options.",fg_bg=g_lg_fg_bg}
|
||||
|
||||
TextBox{parent=clr_c_1,x=1,y=7,height=1,text="Front Panel Theme"}
|
||||
local fp_theme = RadioButton{parent=clr_c_1,x=1,y=8,default=ini_cfg.FrontPanelTheme,options=themes.FP_THEME_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.magenta}
|
||||
|
||||
TextBox{parent=clr_c_2,x=1,y=1,height=6,text="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,height=1,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,height=1,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,height=1,text="Settings saved!"}
|
||||
PushButton{parent=clr_c_3,x=1,y=14,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray)}
|
||||
PushButton{parent=clr_c_3,x=44,y=14,min_width=6,text="Home",callback=c_go_home,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
TextBox{parent=clr_c_4,x=1,y=1,height=5,text="Failed to save the settings file.\n\nThere may not be enough space for the modification or server file permissions may be denying writes."}
|
||||
PushButton{parent=clr_c_4,x=1,y=14,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray)}
|
||||
PushButton{parent=clr_c_4,x=44,y=14,min_width=6,text="Home",callback=c_go_home,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
--#endregion
|
||||
|
||||
--#region Summary and Saving
|
||||
|
||||
local sum_c_1 = Div{parent=summary,x=2,y=4,width=49}
|
||||
@@ -456,7 +576,7 @@ local function config_view(display)
|
||||
tool_ctl.importing_legacy = false
|
||||
tool_ctl.settings_apply.show()
|
||||
else
|
||||
main_pane.set_value(4)
|
||||
main_pane.set_value(5)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -487,6 +607,8 @@ local function config_view(display)
|
||||
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()
|
||||
|
||||
@@ -511,6 +633,7 @@ local function config_view(display)
|
||||
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
|
||||
|
||||
@@ -588,7 +711,7 @@ local function config_view(display)
|
||||
|
||||
tool_ctl.gen_summary(tmp_cfg)
|
||||
sum_pane.set_value(1)
|
||||
main_pane.set_value(5)
|
||||
main_pane.set_value(6)
|
||||
tool_ctl.importing_legacy = true
|
||||
end
|
||||
|
||||
@@ -606,7 +729,7 @@ local function config_view(display)
|
||||
local alternate = false
|
||||
local inner_width = setting_list.get_width() - 1
|
||||
|
||||
tool_ctl.show_key_btn.enable()
|
||||
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
|
||||
@@ -617,9 +740,15 @@ local function config_view(display)
|
||||
local raw = cfg[f[1]]
|
||||
local val = util.strval(raw)
|
||||
|
||||
if f[1] == "AuthKey" then val = string.rep("*", string.len(val)) end
|
||||
if f[1] == "LogMode" then val = util.trinary(raw == log.MODE.APPEND, "append", "replace") end
|
||||
if f[1] == "EmerCoolColor" and raw ~= nil then val = rsio.color_name(raw) end
|
||||
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))
|
||||
|
||||
@@ -23,6 +23,8 @@ local LED = require("graphics.elements.indicators.led")
|
||||
local LEDPair = require("graphics.elements.indicators.ledpair")
|
||||
local RGBLED = require("graphics.elements.indicators.ledrgb")
|
||||
|
||||
local LINK_STATE = types.PANEL_LINK_STATE
|
||||
|
||||
local ALIGN = core.ALIGN
|
||||
|
||||
local cpair = core.cpair
|
||||
@@ -34,8 +36,12 @@ local ind_red = style.ind_red
|
||||
-- create new front panel view
|
||||
---@param panel graphics_element main displaybox
|
||||
local function init(panel)
|
||||
local header = TextBox{parent=panel,y=1,text="REACTOR PLC - UNIT ?",alignment=ALIGN.CENTER,height=1,fg_bg=style.header}
|
||||
header.register(databus.ps, "unit_id", function (id) header.set_value(util.c("REACTOR PLC - UNIT ", id)) end)
|
||||
local s_hi_box = style.theme.highlight_box
|
||||
|
||||
local disabled_fg = style.fp.disabled_fg
|
||||
|
||||
local header = TextBox{parent=panel,y=1,text="FISSION REACTOR PLC - UNIT ?",alignment=ALIGN.CENTER,height=1,fg_bg=style.theme.header}
|
||||
header.register(databus.ps, "unit_id", function (id) header.set_value(util.c("FISSION REACTOR PLC - UNIT ", id)) end)
|
||||
|
||||
--
|
||||
-- system indicators
|
||||
@@ -52,13 +58,47 @@ local function init(panel)
|
||||
|
||||
local reactor = LEDPair{parent=system,label="REACTOR",off=colors.red,c1=colors.yellow,c2=colors.green}
|
||||
local modem = LED{parent=system,label="MODEM",colors=ind_grn}
|
||||
local network = RGBLED{parent=system,label="NETWORK",colors={colors.green,colors.red,colors.orange,colors.yellow,colors.gray}}
|
||||
network.update(types.PANEL_LINK_STATE.DISCONNECTED)
|
||||
|
||||
if not style.colorblind then
|
||||
local network = RGBLED{parent=system,label="NETWORK",colors={colors.green,colors.red,colors.orange,colors.yellow,style.ind_bkg}}
|
||||
network.update(types.PANEL_LINK_STATE.DISCONNECTED)
|
||||
network.register(databus.ps, "link_state", network.update)
|
||||
else
|
||||
local nt_lnk = LEDPair{parent=system,label="NT LINKED",off=style.ind_bkg,c1=colors.red,c2=colors.green}
|
||||
local nt_ver = LEDPair{parent=system,label="NT VERSION",off=style.ind_bkg,c1=colors.red,c2=colors.green}
|
||||
local nt_col = LED{parent=system,label="NT COLLISION",colors=ind_red}
|
||||
|
||||
nt_lnk.register(databus.ps, "link_state", function (state)
|
||||
local value = 2
|
||||
|
||||
if state == LINK_STATE.DISCONNECTED then
|
||||
value = 1
|
||||
elseif state == LINK_STATE.LINKED then
|
||||
value = 3
|
||||
end
|
||||
|
||||
nt_lnk.update(value)
|
||||
end)
|
||||
|
||||
nt_ver.register(databus.ps, "link_state", function (state)
|
||||
local value = 3
|
||||
|
||||
if state == LINK_STATE.BAD_VERSION then
|
||||
value = 2
|
||||
elseif state == LINK_STATE.DISCONNECTED then
|
||||
value = 1
|
||||
end
|
||||
|
||||
nt_ver.update(value)
|
||||
end)
|
||||
|
||||
nt_col.register(databus.ps, "link_state", function (state) nt_col.update(state == LINK_STATE.COLLISION) end)
|
||||
end
|
||||
|
||||
system.line_break()
|
||||
|
||||
reactor.register(databus.ps, "reactor_dev_state", reactor.update)
|
||||
modem.register(databus.ps, "has_modem", modem.update)
|
||||
network.register(databus.ps, "link_state", network.update)
|
||||
|
||||
local rt_main = LED{parent=system,label="RT MAIN",colors=ind_grn}
|
||||
local rt_rps = LED{parent=system,label="RT RPS",colors=ind_grn}
|
||||
@@ -75,7 +115,7 @@ local function init(panel)
|
||||
|
||||
---@diagnostic disable-next-line: undefined-field
|
||||
local comp_id = util.sprintf("(%d)", os.getComputerID())
|
||||
TextBox{parent=system,x=9,y=5,width=6,height=1,text=comp_id,fg_bg=cpair(colors.lightGray,colors.ivory)}
|
||||
TextBox{parent=system,x=9,y=5,width=6,height=1,text=comp_id,fg_bg=disabled_fg}
|
||||
|
||||
--
|
||||
-- status & controls
|
||||
@@ -91,12 +131,12 @@ local function init(panel)
|
||||
emer_cool.register(databus.ps, "emer_cool", emer_cool.update)
|
||||
end
|
||||
|
||||
local status_trip_rct = Rectangle{parent=status,width=20,height=3,x=1,border=border(1,colors.lightGray,true),even_inner=true,fg_bg=cpair(colors.black,colors.ivory)}
|
||||
local status_trip = Div{parent=status_trip_rct,width=18,height=1,fg_bg=cpair(colors.black,colors.lightGray)}
|
||||
local status_trip_rct = Rectangle{parent=status,width=20,height=3,x=1,border=border(1,s_hi_box.bkg,true),even_inner=true}
|
||||
local status_trip = Div{parent=status_trip_rct,width=18,height=1,fg_bg=s_hi_box}
|
||||
local scram = LED{parent=status_trip,width=10,label="RPS TRIP",colors=ind_red,flash=true,period=flasher.PERIOD.BLINK_250_MS}
|
||||
|
||||
local controls_rct = Rectangle{parent=status,width=17,height=3,x=1,border=border(1,colors.white,true),even_inner=true,fg_bg=cpair(colors.black,colors.ivory)}
|
||||
local controls = Div{parent=controls_rct,width=15,height=1,fg_bg=cpair(colors.black,colors.white)}
|
||||
local controls_rct = Rectangle{parent=status,width=17,height=3,x=1,border=border(1,s_hi_box.bkg,true),even_inner=true}
|
||||
local controls = Div{parent=controls_rct,width=15,height=1,fg_bg=s_hi_box}
|
||||
PushButton{parent=controls,x=1,y=1,min_width=7,text="SCRAM",callback=databus.rps_scram,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.black,colors.red_off)}
|
||||
PushButton{parent=controls,x=9,y=1,min_width=7,text="RESET",callback=databus.rps_reset,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.black,colors.yellow_off)}
|
||||
|
||||
@@ -107,9 +147,9 @@ local function init(panel)
|
||||
-- about footer
|
||||
--
|
||||
|
||||
local about = Rectangle{parent=panel,width=32,height=3,x=2,y=16,border=border(1,colors.ivory),thin=true,fg_bg=cpair(colors.black,colors.white)}
|
||||
local fw_v = TextBox{parent=about,x=2,y=1,text="FW: v00.00.00",alignment=ALIGN.LEFT,height=1}
|
||||
local comms_v = TextBox{parent=about,x=17,y=1,text="NT: v00.00.00",alignment=ALIGN.LEFT,height=1}
|
||||
local about = Div{parent=panel,width=15,height=3,x=1,y=18,fg_bg=disabled_fg}
|
||||
local fw_v = TextBox{parent=about,x=1,y=1,text="FW: v00.00.00",alignment=ALIGN.LEFT,height=1}
|
||||
local comms_v = TextBox{parent=about,x=1,y=2,text="NT: v00.00.00",alignment=ALIGN.LEFT,height=1}
|
||||
|
||||
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)
|
||||
@@ -118,7 +158,7 @@ local function init(panel)
|
||||
-- rps list
|
||||
--
|
||||
|
||||
local rps = Rectangle{parent=panel,width=16,height=16,x=36,y=3,border=border(1,colors.lightGray),thin=true,fg_bg=cpair(colors.black,colors.lightGray)}
|
||||
local rps = Rectangle{parent=panel,width=16,height=16,x=36,y=3,border=border(1,s_hi_box.bkg),thin=true,fg_bg=s_hi_box}
|
||||
local rps_man = LED{parent=rps,label="MANUAL",colors=ind_red}
|
||||
local rps_auto = LED{parent=rps,label="AUTOMATIC",colors=ind_red}
|
||||
local rps_tmo = LED{parent=rps,label="TIMEOUT",colors=ind_red}
|
||||
|
||||
@@ -2,46 +2,40 @@
|
||||
-- Graphics Style Options
|
||||
--
|
||||
|
||||
local core = require("graphics.core")
|
||||
local core = require("graphics.core")
|
||||
local themes = require("graphics.themes")
|
||||
|
||||
---@class plc_style
|
||||
local style = {}
|
||||
|
||||
local cpair = core.cpair
|
||||
|
||||
-- GLOBAL --
|
||||
|
||||
-- remap global colors
|
||||
colors.ivory = colors.pink
|
||||
colors.yellow_hc = colors.purple
|
||||
colors.red_off = colors.brown
|
||||
colors.yellow_off = colors.magenta
|
||||
colors.green_off = colors.lime
|
||||
|
||||
style.root = cpair(colors.black, colors.ivory)
|
||||
style.header = cpair(colors.black, colors.lightGray)
|
||||
|
||||
style.colors = {
|
||||
{ c = colors.red, hex = 0xdf4949 }, -- RED ON
|
||||
{ c = colors.orange, hex = 0xffb659 },
|
||||
{ c = colors.yellow, hex = 0xf9fb53 }, -- YELLOW ON
|
||||
{ c = colors.lime, hex = 0x16665a }, -- GREEN OFF
|
||||
{ c = colors.green, hex = 0x6be551 }, -- GREEN ON
|
||||
{ c = colors.cyan, hex = 0x34bac8 },
|
||||
{ c = colors.lightBlue, hex = 0x6cc0f2 },
|
||||
{ c = colors.blue, hex = 0x0096ff },
|
||||
{ c = colors.purple, hex = 0xb156ee }, -- YELLOW HIGH CONTRAST
|
||||
{ c = colors.pink, hex = 0xdcd9ca }, -- IVORY
|
||||
{ c = colors.magenta, hex = 0x85862c }, -- YELLOW OFF
|
||||
-- { c = colors.white, hex = 0xdcd9ca },
|
||||
{ c = colors.lightGray, hex = 0xb1b8b3 },
|
||||
{ c = colors.gray, hex = 0x575757 },
|
||||
-- { c = colors.black, hex = 0x191919 },
|
||||
{ c = colors.brown, hex = 0x672223 } -- RED OFF
|
||||
}
|
||||
|
||||
-- COMMON COLOR PAIRS --
|
||||
style.theme = themes.sandstone
|
||||
style.fp = themes.get_fp_style(style.theme)
|
||||
style.colorblind = false
|
||||
|
||||
style.ind_grn = cpair(colors.green, colors.green_off)
|
||||
style.ind_red = cpair(colors.red, colors.red_off)
|
||||
|
||||
-- set theme per configuration
|
||||
---@param fp FP_THEME front panel theme
|
||||
---@param color_mode COLOR_MODE the color mode to use
|
||||
function style.set_theme(fp, color_mode)
|
||||
if fp == themes.FP_THEME.SANDSTONE then
|
||||
style.theme = themes.sandstone
|
||||
elseif fp == themes.FP_THEME.BASALT then
|
||||
style.theme = themes.basalt
|
||||
end
|
||||
|
||||
style.fp = themes.get_fp_style(style.theme)
|
||||
|
||||
style.colorblind = color_mode ~= themes.COLOR_MODE.STANDARD and color_mode ~= themes.COLOR_MODE.STD_ON_BLACK
|
||||
|
||||
if color_mode == themes.COLOR_MODE.STANDARD or color_mode == themes.COLOR_MODE.BLUE_IND then
|
||||
style.ind_bkg = colors.gray
|
||||
else
|
||||
style.ind_bkg = colors.black
|
||||
end
|
||||
end
|
||||
|
||||
return style
|
||||
|
||||
@@ -6,6 +6,8 @@ local rsio = require("scada-common.rsio")
|
||||
local types = require("scada-common.types")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local themes = require("graphics.themes")
|
||||
|
||||
local databus = require("reactor-plc.databus")
|
||||
|
||||
local plc = {}
|
||||
@@ -52,6 +54,9 @@ function plc.load_config()
|
||||
config.LogPath = settings.get("LogPath")
|
||||
config.LogDebug = settings.get("LogDebug")
|
||||
|
||||
config.FrontPanelTheme = settings.get("FrontPanelTheme")
|
||||
config.ColorMode = settings.get("ColorMode")
|
||||
|
||||
local cfv = util.new_validator()
|
||||
|
||||
cfv.assert_type_bool(config.Networked)
|
||||
@@ -69,7 +74,7 @@ function plc.load_config()
|
||||
|
||||
if type(config.AuthKey) == "string" then
|
||||
local len = string.len(config.AuthKey)
|
||||
cfv.assert_eq(len == 0 or len >= 8, true)
|
||||
cfv.assert(len == 0 or len >= 8)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -78,6 +83,11 @@ function plc.load_config()
|
||||
cfv.assert_type_str(config.LogPath)
|
||||
cfv.assert_type_bool(config.LogDebug)
|
||||
|
||||
cfv.assert_type_int(config.FrontPanelTheme)
|
||||
cfv.assert_range(config.FrontPanelTheme, 1, 2)
|
||||
cfv.assert_type_int(config.ColorMode)
|
||||
cfv.assert_range(config.ColorMode, 1, themes.COLOR_MODE.NUM_MODES)
|
||||
|
||||
-- check emergency coolant configuration if enabled
|
||||
if config.EmerCoolEnable then
|
||||
cfv.assert_eq(rsio.is_valid_side(config.EmerCoolSide), true)
|
||||
@@ -136,9 +146,9 @@ function plc.rps_init(reactor, is_formed)
|
||||
local function _check_and_handle_ppm_call(result)
|
||||
if result == ppm.ACCESS_FAULT then
|
||||
_set_fault()
|
||||
elseif result == ppm.UNDEFINED_FIELD then
|
||||
_set_fault()
|
||||
self.formed = false
|
||||
|
||||
-- if undefined, then the reactor isn't formed
|
||||
if reactor.__p_last_fault() == ppm.UNDEFINED_FIELD then self.formed = false end
|
||||
else return true end
|
||||
|
||||
return false
|
||||
|
||||
@@ -18,11 +18,16 @@ local ui = {
|
||||
}
|
||||
|
||||
-- try to start the UI
|
||||
---@param theme FP_THEME front panel theme
|
||||
---@param color_mode COLOR_MODE color mode
|
||||
---@return boolean success, any error_msg
|
||||
function renderer.try_start_ui()
|
||||
function renderer.try_start_ui(theme, color_mode)
|
||||
local status, msg = true, nil
|
||||
|
||||
if ui.display == nil then
|
||||
-- set theme
|
||||
style.set_theme(theme, color_mode)
|
||||
|
||||
-- reset terminal
|
||||
term.setTextColor(colors.white)
|
||||
term.setBackgroundColor(colors.black)
|
||||
@@ -30,13 +35,19 @@ function renderer.try_start_ui()
|
||||
term.setCursorPos(1, 1)
|
||||
|
||||
-- set overridden colors
|
||||
for i = 1, #style.colors do
|
||||
term.setPaletteColor(style.colors[i].c, style.colors[i].hex)
|
||||
for i = 1, #style.theme.colors do
|
||||
term.setPaletteColor(style.theme.colors[i].c, style.theme.colors[i].hex)
|
||||
end
|
||||
|
||||
-- apply color mode
|
||||
local c_mode_overrides = style.theme.color_modes[color_mode]
|
||||
for i = 1, #c_mode_overrides do
|
||||
term.setPaletteColor(c_mode_overrides[i].c, c_mode_overrides[i].hex)
|
||||
end
|
||||
|
||||
-- init front panel view
|
||||
status, msg = pcall(function ()
|
||||
ui.display = DisplayBox{window=term.current(),fg_bg=style.root}
|
||||
ui.display = DisplayBox{window=term.current(),fg_bg=style.fp.root}
|
||||
panel_view(ui.display)
|
||||
end)
|
||||
|
||||
@@ -64,9 +75,9 @@ function renderer.close_ui()
|
||||
ui.display = nil
|
||||
|
||||
-- restore colors
|
||||
for i = 1, #style.colors do
|
||||
local r, g, b = term.nativePaletteColor(style.colors[i].c)
|
||||
term.setPaletteColor(style.colors[i].c, r, g, b)
|
||||
for i = 1, #style.theme.colors do
|
||||
local r, g, b = term.nativePaletteColor(style.theme.colors[i].c)
|
||||
term.setPaletteColor(style.theme.colors[i].c, r, g, b)
|
||||
end
|
||||
|
||||
-- reset terminal
|
||||
|
||||
@@ -18,7 +18,7 @@ local plc = require("reactor-plc.plc")
|
||||
local renderer = require("reactor-plc.renderer")
|
||||
local threads = require("reactor-plc.threads")
|
||||
|
||||
local R_PLC_VERSION = "v1.6.14"
|
||||
local R_PLC_VERSION = "v1.7.10"
|
||||
|
||||
local println = util.println
|
||||
local println_ts = util.println_ts
|
||||
@@ -55,6 +55,7 @@ log.info("========================================")
|
||||
println(">> Reactor PLC " .. R_PLC_VERSION .. " <<")
|
||||
|
||||
crash.set_env("reactor-plc", R_PLC_VERSION)
|
||||
crash.dbg_log_env()
|
||||
|
||||
----------------------------------------
|
||||
-- main application
|
||||
@@ -144,13 +145,6 @@ local function main()
|
||||
println("init> fission reactor is not formed")
|
||||
log.warning("init> reactor logic adapter present, but reactor is not formed")
|
||||
|
||||
plc_state.degraded = true
|
||||
plc_state.reactor_formed = false
|
||||
elseif smem_dev.reactor.getStatus() == ppm.UNDEFINED_FIELD then
|
||||
-- reactor formed after ppm.mount_all was called
|
||||
println("init> fission reactor was not formed")
|
||||
log.warning("init> reactor reported formed, but multiblock functions are not available")
|
||||
|
||||
plc_state.degraded = true
|
||||
plc_state.reactor_formed = false
|
||||
end
|
||||
@@ -183,8 +177,9 @@ local function main()
|
||||
-- front panel time!
|
||||
if not renderer.ui_ready() then
|
||||
local message
|
||||
plc_state.fp_ok, message = renderer.try_start_ui()
|
||||
plc_state.fp_ok, message = renderer.try_start_ui(config.FrontPanelTheme, config.ColorMode)
|
||||
|
||||
-- ...or not
|
||||
if not plc_state.fp_ok then
|
||||
println_ts(util.c("UI error: ", message))
|
||||
println("init> running without front panel")
|
||||
|
||||
@@ -71,76 +71,49 @@ function threads.thread__main(smem, init)
|
||||
-- blink heartbeat indicator
|
||||
databus.heartbeat()
|
||||
|
||||
-- core clock tick
|
||||
if networked then
|
||||
-- start next clock timer
|
||||
loop_clock.start()
|
||||
-- start next clock timer
|
||||
loop_clock.start()
|
||||
|
||||
-- send updated data
|
||||
if nic.is_connected() then
|
||||
if plc_comms.is_linked() then
|
||||
smem.q.mq_comms_tx.push_command(MQ__COMM_CMD.SEND_STATUS)
|
||||
-- send updated data
|
||||
if networked and nic.is_connected() then
|
||||
if plc_comms.is_linked() then
|
||||
smem.q.mq_comms_tx.push_command(MQ__COMM_CMD.SEND_STATUS)
|
||||
else
|
||||
if ticks_to_update == 0 then
|
||||
plc_comms.send_link_req()
|
||||
ticks_to_update = LINK_TICKS
|
||||
else
|
||||
if ticks_to_update == 0 then
|
||||
plc_comms.send_link_req()
|
||||
ticks_to_update = LINK_TICKS
|
||||
else
|
||||
ticks_to_update = ticks_to_update - 1
|
||||
end
|
||||
ticks_to_update = ticks_to_update - 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- are we now formed after waiting to be formed?
|
||||
-- check for formed state change
|
||||
if (not plc_state.reactor_formed) and rps.is_formed() then
|
||||
-- push a connect event and unmount it from the PPM
|
||||
local iface = ppm.get_iface(plc_dev.reactor)
|
||||
if iface then
|
||||
log.info("unmounting and remounting unformed reactor")
|
||||
ppm.unmount(plc_dev.reactor)
|
||||
-- reactor now formed
|
||||
plc_state.reactor_formed = true
|
||||
|
||||
local type, device = ppm.mount(iface)
|
||||
println_ts("reactor is now formed.")
|
||||
log.info("reactor is now formed")
|
||||
|
||||
if type == "fissionReactorLogicAdapter" and device ~= nil then
|
||||
-- reconnect reactor
|
||||
plc_dev.reactor = device
|
||||
-- SCRAM newly formed reactor
|
||||
smem.q.mq_rps.push_command(MQ__RPS_CMD.SCRAM)
|
||||
|
||||
-- we need to assume formed here as we cannot check in this main loop
|
||||
-- RPS will identify if it isn't and this will get set false later
|
||||
plc_state.reactor_formed = true
|
||||
|
||||
println_ts("reactor reconnected.")
|
||||
log.info("reactor reconnected")
|
||||
|
||||
-- SCRAM newly connected reactor
|
||||
smem.q.mq_rps.push_command(MQ__RPS_CMD.SCRAM)
|
||||
|
||||
-- determine if we are still in a degraded state
|
||||
if (not networked) or nic.is_connected() then
|
||||
plc_state.degraded = false
|
||||
end
|
||||
|
||||
rps.reconnect_reactor(plc_dev.reactor)
|
||||
if networked then
|
||||
plc_comms.reconnect_reactor(plc_dev.reactor)
|
||||
end
|
||||
|
||||
-- partial reset of RPS, specific to becoming formed
|
||||
rps.reset_formed()
|
||||
else
|
||||
-- fully lost the reactor now :(
|
||||
println_ts("reactor lost (failed reconnect)!")
|
||||
log.error("reactor lost (failed reconnect)")
|
||||
|
||||
plc_state.no_reactor = true
|
||||
plc_state.degraded = true
|
||||
end
|
||||
else
|
||||
log.error("failed to get interface of previously connected reactor", true)
|
||||
-- determine if we are still in a degraded state
|
||||
if (not networked) or nic.is_connected() then
|
||||
plc_state.degraded = false
|
||||
end
|
||||
elseif not rps.is_formed() then
|
||||
|
||||
-- partial reset of RPS, specific to becoming formed
|
||||
-- without this, auto control can't resume on chunk load
|
||||
rps.reset_formed()
|
||||
elseif plc_state.reactor_formed and not rps.is_formed() then
|
||||
-- reactor no longer formed
|
||||
println_ts("reactor is no longer formed.")
|
||||
log.info("reactor is no longer formed")
|
||||
|
||||
plc_state.reactor_formed = false
|
||||
plc_state.degraded = true
|
||||
end
|
||||
|
||||
-- update indicators
|
||||
@@ -230,7 +203,8 @@ function threads.thread__main(smem, init)
|
||||
plc_comms.reconnect_reactor(plc_dev.reactor)
|
||||
end
|
||||
|
||||
-- partial reset of RPS, specific to becoming formed
|
||||
-- partial reset of RPS, specific to becoming formed/reconnected
|
||||
-- without this, auto control can't resume on chunk load
|
||||
rps.reset_formed()
|
||||
end
|
||||
elseif networked and type == "modem" then
|
||||
@@ -368,9 +342,9 @@ function threads.thread__rps(smem)
|
||||
end
|
||||
end
|
||||
|
||||
-- if we are in standalone mode, continuously reset RPS
|
||||
-- if we are in standalone mode and the front panel isn't working, continuously reset RPS
|
||||
-- RPS will trip again if there are faults, but if it isn't cleared, the user can't re-enable
|
||||
if not networked then rps.reset(true) end
|
||||
if not (networked or smem.plc_state.fp_ok) then rps.reset(true) end
|
||||
|
||||
-- check safety (SCRAM occurs if tripped)
|
||||
if not plc_state.no_reactor then
|
||||
@@ -662,8 +636,9 @@ function threads.thread__setpoint_control(smem)
|
||||
if (type(cur_burn_rate) == "number") and (setpoints.burn_rate ~= cur_burn_rate) and rps.is_active() then
|
||||
last_burn_sp = setpoints.burn_rate
|
||||
|
||||
-- update without ramp if <= 2.5 mB/t change
|
||||
running = math.abs(setpoints.burn_rate - cur_burn_rate) > 2.5
|
||||
-- update without ramp if <= 2.5 mB/t increase
|
||||
-- no need to ramp down, as the ramp up poses the safety risks
|
||||
running = (setpoints.burn_rate - cur_burn_rate) > 2.5
|
||||
|
||||
if running then
|
||||
log.debug(util.c("SPCTL: starting burn rate ramp from ", cur_burn_rate, " mB/t to ", setpoints.burn_rate, " mB/t"))
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
-- Configuration GUI
|
||||
--
|
||||
|
||||
local constants = require("scada-common.constants")
|
||||
local log = require("scada-common.log")
|
||||
local ppm = require("scada-common.ppm")
|
||||
local rsio = require("scada-common.rsio")
|
||||
@@ -9,6 +10,7 @@ local tcd = require("scada-common.tcd")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local core = require("graphics.core")
|
||||
local themes = require("graphics.themes")
|
||||
|
||||
local DisplayBox = require("graphics.elements.displaybox")
|
||||
local Div = require("graphics.elements.div")
|
||||
@@ -24,56 +26,65 @@ 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 println = util.println
|
||||
local tri = util.trinary
|
||||
|
||||
local cpair = core.cpair
|
||||
|
||||
local IO = rsio.IO
|
||||
local IO_LVL = rsio.IO_LVL
|
||||
local IO_MODE = rsio.IO_MODE
|
||||
|
||||
local LEFT = core.ALIGN.LEFT
|
||||
local CENTER = core.ALIGN.CENTER
|
||||
local RIGHT = core.ALIGN.RIGHT
|
||||
|
||||
-- rsio port descriptions
|
||||
local PORT_DESC = {
|
||||
"Facility SCRAM",
|
||||
"Facility Acknowledge",
|
||||
"Reactor SCRAM",
|
||||
"Reactor RPS Reset",
|
||||
"Reactor Enable",
|
||||
"Unit Acknowledge",
|
||||
"Facility Alarm (high prio)",
|
||||
"Facility Alarm (any)",
|
||||
"Waste Plutonium Valve",
|
||||
"Waste Polonium Valve",
|
||||
"Waste Po Pellets Valve",
|
||||
"Waste Antimatter Valve",
|
||||
"Reactor Active",
|
||||
"Reactor in Auto Control",
|
||||
"RPS Tripped",
|
||||
"RPS Auto SCRAM",
|
||||
"RPS High Damage",
|
||||
"RPS High Temperature",
|
||||
"RPS Low Coolant",
|
||||
"RPS Excess Heated Coolant",
|
||||
"RPS Excess Waste",
|
||||
"RPS Insufficient Fuel",
|
||||
"RPS PLC Fault",
|
||||
"RPS Supervisor Timeout",
|
||||
"Unit Alarm",
|
||||
"Unit Emergency Cool. Valve"
|
||||
local PORT_DESC_MAP = {
|
||||
{ IO.F_SCRAM, "Facility SCRAM" },
|
||||
{ IO.F_ACK, "Facility Acknowledge" },
|
||||
{ IO.R_SCRAM, "Reactor SCRAM" },
|
||||
{ IO.R_RESET, "Reactor RPS Reset" },
|
||||
{ IO.R_ENABLE, "Reactor Enable" },
|
||||
{ IO.U_ACK, "Unit Acknowledge" },
|
||||
{ IO.F_ALARM, "Facility Alarm (high prio)" },
|
||||
{ IO.F_ALARM_ANY, "Facility Alarm (any)" },
|
||||
{ IO.F_MATRIX_LOW, "Induction Matrix < " .. (100 * constants.RS_THRESHOLDS.IMATRIX_CHARGE_LOW) .. "%" },
|
||||
{ IO.F_MATRIX_HIGH, "Induction Matrix > " .. (100 * constants.RS_THRESHOLDS.IMATRIX_CHARGE_HIGH) .. "%" },
|
||||
{ IO.F_MATRIX_CHG, "Induction Matrix Charge %" },
|
||||
{ IO.WASTE_PU, "Waste Plutonium Valve" },
|
||||
{ IO.WASTE_PO, "Waste Polonium Valve" },
|
||||
{ IO.WASTE_POPL, "Waste Po Pellets Valve" },
|
||||
{ IO.WASTE_AM, "Waste Antimatter Valve" },
|
||||
{ IO.R_ACTIVE, "Reactor Active" },
|
||||
{ IO.R_AUTO_CTRL, "Reactor in Auto Control" },
|
||||
{ IO.R_SCRAMMED, "RPS Tripped" },
|
||||
{ IO.R_AUTO_SCRAM, "RPS Auto SCRAM" },
|
||||
{ IO.R_HIGH_DMG, "RPS High Damage" },
|
||||
{ IO.R_HIGH_TEMP, "RPS High Temperature" },
|
||||
{ IO.R_LOW_COOLANT, "RPS Low Coolant" },
|
||||
{ IO.R_EXCESS_HC, "RPS Excess Heated Coolant" },
|
||||
{ IO.R_EXCESS_WS, "RPS Excess Waste" },
|
||||
{ IO.R_INSUFF_FUEL, "RPS Insufficient Fuel" },
|
||||
{ IO.R_PLC_FAULT, "RPS PLC Fault" },
|
||||
{ IO.R_PLC_TIMEOUT, "RPS Supervisor Timeout" },
|
||||
{ IO.U_ALARM, "Unit Alarm" },
|
||||
{ IO.U_EMER_COOL, "Unit Emergency Cool. Valve" }
|
||||
}
|
||||
|
||||
-- designation (0 = facility, 1 = unit)
|
||||
local PORT_DSGN = { [-1] = 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }
|
||||
local PORT_DSGN = { [-1] = 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0 }
|
||||
|
||||
assert(#PORT_DESC == rsio.NUM_PORTS)
|
||||
assert(#PORT_DESC_MAP == rsio.NUM_PORTS)
|
||||
assert(#PORT_DSGN == rsio.NUM_PORTS)
|
||||
|
||||
-- changes to the config data/format to let the user know
|
||||
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.9.2", { "Added standard with black off state color mode", "Added blue indicator color modes" } }
|
||||
}
|
||||
|
||||
---@class rtu_rs_definition
|
||||
@@ -98,34 +109,22 @@ local style = {}
|
||||
style.root = cpair(colors.black, colors.lightGray)
|
||||
style.header = cpair(colors.white, colors.gray)
|
||||
|
||||
style.colors = {
|
||||
{ c = colors.red, hex = 0xdf4949 },
|
||||
{ c = colors.orange, hex = 0xffb659 },
|
||||
{ c = colors.yellow, hex = 0xfffc79 },
|
||||
{ c = colors.lime, hex = 0x80ff80 },
|
||||
{ c = colors.green, hex = 0x4aee8a },
|
||||
{ c = colors.cyan, hex = 0x34bac8 },
|
||||
{ c = colors.lightBlue, hex = 0x6cc0f2 },
|
||||
{ c = colors.blue, hex = 0x0096ff },
|
||||
{ c = colors.purple, hex = 0xb156ee },
|
||||
{ c = colors.pink, hex = 0xf26ba2 },
|
||||
{ c = colors.magenta, hex = 0xf9488a },
|
||||
{ c = colors.lightGray, hex = 0xcacaca },
|
||||
{ c = colors.gray, hex = 0x575757 }
|
||||
}
|
||||
style.colors = themes.smooth_stone.colors
|
||||
|
||||
local bw_fg_bg = cpair(colors.black, colors.white)
|
||||
local g_lg_fg_bg = cpair(colors.gray, colors.lightGray)
|
||||
local nav_fg_bg = bw_fg_bg
|
||||
local btn_act_fg_bg = cpair(colors.white, colors.gray)
|
||||
|
||||
---@class _rtu_cfg_tool_ctl
|
||||
local tool_ctl = {
|
||||
ask_config = false,
|
||||
has_config = false,
|
||||
viewing_config = false,
|
||||
importing_legacy = false,
|
||||
importing_any_dc = false,
|
||||
peri_cfg_editing = false, ---@type string|false
|
||||
jumped_to_color = false,
|
||||
peri_cfg_editing = false, ---@type integer|false
|
||||
peri_cfg_manual = false,
|
||||
rs_cfg_port = IO.F_SCRAM, ---@type IO_PORT
|
||||
rs_cfg_editing = false, ---@type integer|false
|
||||
@@ -133,6 +132,9 @@ local tool_ctl = {
|
||||
view_gw_cfg = nil, ---@type graphics_element
|
||||
dev_cfg = nil, ---@type graphics_element
|
||||
rs_cfg = nil, ---@type graphics_element
|
||||
color_cfg = nil, ---@type graphics_element
|
||||
color_next = nil, ---@type graphics_element
|
||||
color_apply = nil, ---@type graphics_element
|
||||
settings_apply = nil, ---@type graphics_element
|
||||
settings_confirm = nil, ---@type graphics_element
|
||||
|
||||
@@ -181,7 +183,9 @@ local tmp_cfg = {
|
||||
AuthKey = nil, ---@type string|nil
|
||||
LogMode = 0,
|
||||
LogPath = "",
|
||||
LogDebug = false
|
||||
LogDebug = false,
|
||||
FrontPanelTheme = 1,
|
||||
ColorMode = 1
|
||||
}
|
||||
|
||||
---@class rtu_config
|
||||
@@ -198,7 +202,9 @@ local fields = {
|
||||
{ "AuthKey", "Facility Auth Key", "" },
|
||||
{ "LogMode", "Log Mode", log.MODE.APPEND },
|
||||
{ "LogPath", "Log Path", "/log.txt" },
|
||||
{ "LogDebug","Log Debug Messages", false }
|
||||
{ "LogDebug", "Log Debug Messages", false },
|
||||
{ "FrontPanelTheme", "Front Panel Theme", themes.FP_THEME.SANDSTONE },
|
||||
{ "ColorMode", "Color Mode", themes.COLOR_MODE.STANDARD }
|
||||
}
|
||||
|
||||
local side_options = { "Top", "Bottom", "Left", "Right", "Front", "Back" }
|
||||
@@ -266,12 +272,13 @@ local function config_view(display)
|
||||
local spkr_cfg = Div{parent=root_pane_div,x=1,y=1}
|
||||
local net_cfg = Div{parent=root_pane_div,x=1,y=1}
|
||||
local log_cfg = Div{parent=root_pane_div,x=1,y=1}
|
||||
local clr_cfg = 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 peri_cfg = Div{parent=root_pane_div,x=1,y=1}
|
||||
local rs_cfg = Div{parent=root_pane_div,x=1,y=1}
|
||||
|
||||
local main_pane = MultiPane{parent=root_pane_div,x=1,y=1,panes={main_page,spkr_cfg,net_cfg,log_cfg,summary,changelog,peri_cfg,rs_cfg}}
|
||||
local main_pane = MultiPane{parent=root_pane_div,x=1,y=1,panes={main_page,spkr_cfg,net_cfg,log_cfg,clr_cfg,summary,changelog,peri_cfg,rs_cfg}}
|
||||
|
||||
--#region Main Page
|
||||
|
||||
@@ -290,7 +297,7 @@ local function config_view(display)
|
||||
tool_ctl.gen_summary(settings_cfg)
|
||||
tool_ctl.settings_apply.hide(true)
|
||||
tool_ctl.settings_confirm.hide(true)
|
||||
main_pane.set_value(5)
|
||||
main_pane.set_value(6)
|
||||
end
|
||||
|
||||
if fs.exists("/rtu/config.lua") then
|
||||
@@ -300,12 +307,12 @@ local function config_view(display)
|
||||
|
||||
local function show_peri_conns()
|
||||
tool_ctl.gen_peri_summary(ini_cfg)
|
||||
main_pane.set_value(7)
|
||||
main_pane.set_value(8)
|
||||
end
|
||||
|
||||
local function show_rs_conns()
|
||||
tool_ctl.gen_rs_summary(ini_cfg)
|
||||
main_pane.set_value(8)
|
||||
main_pane.set_value(9)
|
||||
end
|
||||
|
||||
PushButton{parent=main_page,x=2,y=y_start,min_width=19,text="Configure Gateway",callback=function()main_pane.set_value(2)end,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg}
|
||||
@@ -313,15 +320,24 @@ local function config_view(display)
|
||||
tool_ctl.dev_cfg = PushButton{parent=main_page,x=2,y=y_start+4,min_width=24,text="Peripheral Connections",callback=show_peri_conns,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=btn_act_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)}
|
||||
tool_ctl.rs_cfg = PushButton{parent=main_page,x=2,y=y_start+6,min_width=22,text="Redstone Connections",callback=show_rs_conns,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=btn_act_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)}
|
||||
|
||||
local function jump_color()
|
||||
tool_ctl.jumped_to_color = true
|
||||
tool_ctl.color_next.hide(true)
|
||||
tool_ctl.color_apply.show()
|
||||
main_pane.set_value(5)
|
||||
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}
|
||||
tool_ctl.color_cfg = PushButton{parent=main_page,x=23,y=17,min_width=15,text="Color Options",callback=jump_color,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)}
|
||||
PushButton{parent=main_page,x=39,y=17,min_width=12,text="Change Log",callback=function()main_pane.set_value(7)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
if not tool_ctl.has_config then
|
||||
tool_ctl.view_gw_cfg.disable()
|
||||
tool_ctl.dev_cfg.disable()
|
||||
tool_ctl.rs_cfg.disable()
|
||||
tool_ctl.color_cfg.disable()
|
||||
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=39,y=17,min_width=12,text="Change Log",callback=function()main_pane.set_value(6)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
--#endregion
|
||||
|
||||
--#region Speakers
|
||||
@@ -432,7 +448,7 @@ local function config_view(display)
|
||||
TextBox{parent=net_c_3,x=1,y=11,height=1,text="Facility Auth Key"}
|
||||
local key, _, censor = TextField{parent=net_c_3,x=1,y=12,max_len=64,value=ini_cfg.AuthKey,width=32,height=1,fg_bg=bw_fg_bg}
|
||||
|
||||
local function censor_key(enable) censor(util.trinary(enable, "*", nil)) end
|
||||
local function censor_key(enable) censor(tri(enable, "*", nil)) end
|
||||
|
||||
local hide_key = CheckBox{parent=net_c_3,x=34,y=12,label="Hide",box_fg_bg=cpair(colors.lightBlue,colors.black),callback=censor_key}
|
||||
|
||||
@@ -480,11 +496,8 @@ local function config_view(display)
|
||||
tmp_cfg.LogMode = mode.get_value() - 1
|
||||
tmp_cfg.LogPath = path.get_value()
|
||||
tmp_cfg.LogDebug = en_dbg.get_value()
|
||||
tool_ctl.gen_summary(tmp_cfg)
|
||||
tool_ctl.viewing_config = false
|
||||
tool_ctl.importing_legacy = false
|
||||
tool_ctl.settings_apply.show()
|
||||
tool_ctl.settings_confirm.hide(true)
|
||||
tool_ctl.color_apply.hide(true)
|
||||
tool_ctl.color_next.show()
|
||||
main_pane.set_value(5)
|
||||
else path_err.show() end
|
||||
end
|
||||
@@ -494,6 +507,112 @@ local function config_view(display)
|
||||
|
||||
--#endregion
|
||||
|
||||
--#region Color Options
|
||||
|
||||
local clr_c_1 = Div{parent=clr_cfg,x=2,y=4,width=49}
|
||||
local clr_c_2 = Div{parent=clr_cfg,x=2,y=4,width=49}
|
||||
local clr_c_3 = Div{parent=clr_cfg,x=2,y=4,width=49}
|
||||
local clr_c_4 = Div{parent=clr_cfg,x=2,y=4,width=49}
|
||||
|
||||
local clr_pane = MultiPane{parent=clr_cfg,x=1,y=4,panes={clr_c_1,clr_c_2,clr_c_3,clr_c_4}}
|
||||
|
||||
TextBox{parent=clr_cfg,x=1,y=2,height=1,text=" Color Configuration",fg_bg=cpair(colors.black,colors.magenta)}
|
||||
|
||||
TextBox{parent=clr_c_1,x=1,y=1,height=2,text="Here you can select the color theme for the front panel."}
|
||||
TextBox{parent=clr_c_1,x=1,y=4,height=2,text="Click 'Accessibility' below to access colorblind assistive options.",fg_bg=g_lg_fg_bg}
|
||||
|
||||
TextBox{parent=clr_c_1,x=1,y=7,height=1,text="Front Panel Theme"}
|
||||
local fp_theme = RadioButton{parent=clr_c_1,x=1,y=8,default=ini_cfg.FrontPanelTheme,options=themes.FP_THEME_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.magenta}
|
||||
|
||||
TextBox{parent=clr_c_2,x=1,y=1,height=6,text="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,height=1,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,height=1,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(tri(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("/rtu.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()
|
||||
tool_ctl.settings_confirm.hide(true)
|
||||
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)
|
||||
|
||||
TextBox{parent=clr_c_3,x=1,y=1,height=1,text="Settings saved!"}
|
||||
PushButton{parent=clr_c_3,x=1,y=14,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray)}
|
||||
PushButton{parent=clr_c_3,x=44,y=14,min_width=6,text="Home",callback=function()tool_ctl.go_home()end,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=function()tool_ctl.go_home()end,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}
|
||||
@@ -520,7 +639,7 @@ local function config_view(display)
|
||||
end
|
||||
|
||||
tool_ctl.viewing_config = false
|
||||
else main_pane.set_value(4) end
|
||||
else main_pane.set_value(5) end
|
||||
end
|
||||
|
||||
---@param element graphics_element
|
||||
@@ -552,6 +671,8 @@ local function config_view(display)
|
||||
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)
|
||||
|
||||
if not exclude_conns then
|
||||
tmp_cfg.Peripherals = deep_copy_peri(ini_cfg.Peripherals)
|
||||
@@ -589,7 +710,20 @@ local function config_view(display)
|
||||
PushButton{parent=sum_c_3,x=1,y=14,text="\x1b Back",callback=function()sum_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=sum_c_3,x=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}
|
||||
|
||||
local function jump_peri_conns()
|
||||
tool_ctl.go_home()
|
||||
show_peri_conns()
|
||||
end
|
||||
|
||||
local function jump_rs_conns()
|
||||
tool_ctl.go_home()
|
||||
show_rs_conns()
|
||||
end
|
||||
|
||||
TextBox{parent=sum_c_4,x=1,y=1,height=1,text="Settings saved!"}
|
||||
TextBox{parent=sum_c_4,x=1,y=3,height=4,text="Remember to configure any peripherals or redstone that you have connected to this RTU gateway if you have not already done so, or if you have added, removed, or modified any of them."}
|
||||
PushButton{parent=sum_c_4,x=1,y=8,min_width=24,text="Peripheral Connections",callback=jump_peri_conns,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=sum_c_4,x=1,y=10,min_width=22,text="Redstone Connections",callback=jump_rs_conns,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=sum_c_4,x=1,y=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=sum_c_4,x=44,y=14,min_width=6,text="Home",callback=function()tool_ctl.go_home()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
@@ -769,7 +903,7 @@ local function config_view(display)
|
||||
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.")
|
||||
elseif type == "inductionPort" or type == "spsPort" then
|
||||
local dev = util.trinary(type == "inductionPort", "induction matrix", "SPS")
|
||||
local dev = tri(type == "inductionPort", "induction matrix", "SPS")
|
||||
tool_ctl.p_idx.hide(true)
|
||||
tool_ctl.p_unit.hide(true)
|
||||
tool_ctl.p_prompt.set_value("This is the " .. dev .. " for the facility.")
|
||||
@@ -795,7 +929,7 @@ local function config_view(display)
|
||||
tool_ctl.ppm_devs.remove_all()
|
||||
for name, entry in pairs(mounts) do
|
||||
if util.table_contains(RTU_DEV_TYPES, entry.type) then
|
||||
local bkg = util.trinary(alternate, colors.white, colors.lightGray)
|
||||
local bkg = tri(alternate, colors.white, colors.lightGray)
|
||||
|
||||
---@cast entry ppm_entry
|
||||
local line = Div{parent=tool_ctl.ppm_devs,height=2,fg_bg=cpair(colors.black,bkg)}
|
||||
@@ -957,8 +1091,9 @@ local function config_view(display)
|
||||
local rs_c_4 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
||||
local rs_c_5 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
||||
local rs_c_6 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
||||
local rs_c_7 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
||||
|
||||
local rs_pane = MultiPane{parent=rs_cfg,x=1,y=4,panes={rs_c_1,rs_c_2,rs_c_3,rs_c_4,rs_c_5,rs_c_6}}
|
||||
local rs_pane = MultiPane{parent=rs_cfg,x=1,y=4,panes={rs_c_1,rs_c_2,rs_c_3,rs_c_4,rs_c_5,rs_c_6,rs_c_7}}
|
||||
|
||||
TextBox{parent=rs_cfg,x=1,y=2,height=1,text=" Redstone Connections",fg_bg=cpair(colors.black,colors.red)}
|
||||
|
||||
@@ -1015,9 +1150,23 @@ local function config_view(display)
|
||||
text = "You selected the ALL_WASTE shortcut."
|
||||
else
|
||||
tool_ctl.rs_cfg_shortcut.hide(true)
|
||||
tool_ctl.rs_cfg_side_l.set_value(util.trinary(rsio.get_io_dir(port) == rsio.IO_DIR.IN, "Input Side", "Output Side"))
|
||||
tool_ctl.rs_cfg_side_l.set_value(tri(rsio.get_io_dir(port) == rsio.IO_DIR.IN, "Input Side", "Output Side"))
|
||||
tool_ctl.rs_cfg_color.show()
|
||||
text = "You selected " .. rsio.to_string(port) .. " (for "
|
||||
|
||||
local io_type = "analog input "
|
||||
local io_mode = rsio.get_io_mode(port)
|
||||
local inv = tri(rsio.digital_is_active(port, IO_LVL.LOW) == true, "inverted ", "")
|
||||
|
||||
if io_mode == IO_MODE.DIGITAL_IN then
|
||||
io_type = inv .. "digital input "
|
||||
elseif io_mode == IO_MODE.DIGITAL_OUT then
|
||||
io_type = inv .. "digital output "
|
||||
elseif io_mode == IO_MODE.ANALOG_OUT then
|
||||
io_type = "analog output "
|
||||
end
|
||||
|
||||
text = "You selected the " .. io_type .. rsio.to_string(port) .. " (for "
|
||||
|
||||
if PORT_DSGN[port] == 1 then
|
||||
text = text .. "a unit)."
|
||||
tool_ctl.rs_cfg_unit_l.show()
|
||||
@@ -1039,25 +1188,35 @@ local function config_view(display)
|
||||
PushButton{parent=all_w_macro,x=1,y=1,min_width=14,alignment=LEFT,height=1,text=">ALL_WASTE",callback=function()new_rs(-1)end,fg_bg=cpair(colors.black,colors.green),active_fg_bg=cpair(colors.white,colors.black)}
|
||||
TextBox{parent=all_w_macro,x=16,y=1,width=5,height=1,text="[n/a]",fg_bg=cpair(colors.lightGray,colors.white)}
|
||||
TextBox{parent=all_w_macro,x=22,y=1,height=1,text="Create all 4 waste entries",fg_bg=cpair(colors.gray,colors.white)}
|
||||
|
||||
for i = 1, rsio.NUM_PORTS do
|
||||
local name = rsio.to_string(i)
|
||||
local io_dir = util.trinary(rsio.get_io_dir(i) == rsio.IO_DIR.IN, "[in]", "[out]")
|
||||
local btn_color = util.trinary(rsio.get_io_dir(i) == rsio.IO_DIR.IN, colors.yellow, colors.lightBlue)
|
||||
local p = PORT_DESC_MAP[i][1]
|
||||
local name = rsio.to_string(p)
|
||||
local io_dir = tri(rsio.get_io_dir(p) == rsio.IO_DIR.IN, "[in]", "[out]")
|
||||
local btn_color = tri(rsio.get_io_dir(p) == rsio.IO_DIR.IN, colors.yellow, colors.lightBlue)
|
||||
|
||||
local entry = Div{parent=rs_ports,height=1}
|
||||
PushButton{parent=entry,x=1,y=1,min_width=14,alignment=LEFT,height=1,text=">"..name,callback=function()new_rs(i)end,fg_bg=cpair(colors.black,btn_color),active_fg_bg=cpair(colors.white,colors.black)}
|
||||
PushButton{parent=entry,x=1,y=1,min_width=14,alignment=LEFT,height=1,text=">"..name,callback=function()new_rs(p)end,fg_bg=cpair(colors.black,btn_color),active_fg_bg=cpair(colors.white,colors.black)}
|
||||
TextBox{parent=entry,x=16,y=1,width=5,height=1,text=io_dir,fg_bg=cpair(colors.lightGray,colors.white)}
|
||||
TextBox{parent=entry,x=22,y=1,height=1,text=PORT_DESC[i],fg_bg=cpair(colors.gray,colors.white)}
|
||||
TextBox{parent=entry,x=22,y=1,height=1,text=PORT_DESC_MAP[i][2],fg_bg=cpair(colors.gray,colors.white)}
|
||||
end
|
||||
|
||||
PushButton{parent=rs_c_2,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
tool_ctl.rs_cfg_selection = TextBox{parent=rs_c_3,x=1,y=1,height=1,text=""}
|
||||
tool_ctl.rs_cfg_selection = TextBox{parent=rs_c_3,x=1,y=1,height=2,text=""}
|
||||
|
||||
tool_ctl.rs_cfg_unit_l = TextBox{parent=rs_c_3,x=27,y=3,width=7,height=1,text="Unit ID"}
|
||||
tool_ctl.rs_cfg_unit = NumberField{parent=rs_c_3,x=27,y=4,width=10,max_chars=2,min=1,max=4,fg_bg=bw_fg_bg}
|
||||
PushButton{parent=rs_c_3,x=36,y=3,text="What's that?",min_width=14,callback=function()rs_pane.set_value(7)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
tool_ctl.rs_cfg_side_l = TextBox{parent=rs_c_3,x=1,y=3,width=11,height=1,text="Output Side"}
|
||||
local side = Radio2D{parent=rs_c_3,x=1,y=4,rows=2,columns=3,default=1,options=side_options,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.red}
|
||||
TextBox{parent=rs_c_7,x=1,y=1,height=4,text="(Normal) Digital Input: On if there is a redstone signal, off otherwise\nInverted Digital Input: On without a redstone signal, off otherwise"}
|
||||
TextBox{parent=rs_c_7,x=1,y=6,height=4,text="(Normal) Digital Output: Redstone signal to 'turn it on', none to 'turn it off'\nInverted Digital Output: No redstone signal to 'turn it on', redstone signal to 'turn it off'"}
|
||||
TextBox{parent=rs_c_7,x=1,y=11,height=2,text="Analog Input: 0-15 redstone power level input\nAnalog Output: 0-15 scaled redstone power level output"}
|
||||
PushButton{parent=rs_c_7,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(3)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
tool_ctl.rs_cfg_side_l = TextBox{parent=rs_c_3,x=1,y=4,width=11,height=1,text="Output Side"}
|
||||
local side = Radio2D{parent=rs_c_3,x=1,y=5,rows=1,columns=6,default=1,options=side_options,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.red}
|
||||
|
||||
tool_ctl.rs_cfg_unit_l = TextBox{parent=rs_c_3,x=25,y=7,width=7,height=1,text="Unit ID"}
|
||||
tool_ctl.rs_cfg_unit = NumberField{parent=rs_c_3,x=33,y=7,width=10,max_chars=2,min=1,max=4,fg_bg=bw_fg_bg}
|
||||
|
||||
local function set_bundled(bundled)
|
||||
if bundled then tool_ctl.rs_cfg_color.enable() else tool_ctl.rs_cfg_color.disable() end
|
||||
@@ -1088,10 +1247,10 @@ local function config_view(display)
|
||||
if port >= 0 then
|
||||
---@type rtu_rs_definition
|
||||
local def = {
|
||||
unit = util.trinary(PORT_DSGN[port] == 1, u, nil),
|
||||
unit = tri(PORT_DSGN[port] == 1, u, nil),
|
||||
port = port,
|
||||
side = side_options_map[side.get_value()],
|
||||
color = util.trinary(bundled.get_value(), color_options_map[tool_ctl.rs_cfg_color.get_value()], nil)
|
||||
color = tri(bundled.get_value(), color_options_map[tool_ctl.rs_cfg_color.get_value()], nil)
|
||||
}
|
||||
|
||||
if tool_ctl.rs_cfg_editing == false then
|
||||
@@ -1105,10 +1264,10 @@ local function config_view(display)
|
||||
local default_colors = { colors.red, colors.orange, colors.yellow, colors.lime }
|
||||
for i = 0, 3 do
|
||||
table.insert(tmp_cfg.Redstone, {
|
||||
unit = util.trinary(PORT_DSGN[IO.WASTE_PU + i] == 1, u, nil),
|
||||
unit = tri(PORT_DSGN[IO.WASTE_PU + i] == 1, u, nil),
|
||||
port = IO.WASTE_PU + i,
|
||||
side = util.trinary(bundled.get_value(), side_options_map[side.get_value()], default_sides[i + 1]),
|
||||
color = util.trinary(bundled.get_value(), default_colors[i + 1], nil)
|
||||
side = tri(bundled.get_value(), side_options_map[side.get_value()], default_sides[i + 1]),
|
||||
color = tri(bundled.get_value(), default_colors[i + 1], nil)
|
||||
})
|
||||
end
|
||||
end
|
||||
@@ -1161,7 +1320,7 @@ local function config_view(display)
|
||||
peri_import_list.remove_all()
|
||||
for _, entry in ipairs(config.RTU_DEVICES) do
|
||||
local for_facility = entry.for_reactor == 0
|
||||
local ini_unit = util.trinary(for_facility, nil, entry.for_reactor)
|
||||
local ini_unit = tri(for_facility, nil, entry.for_reactor)
|
||||
|
||||
local def = { name = entry.name, unit = ini_unit, index = entry.index }
|
||||
local mount = mounts[def.name] ---@type ppm_entry|nil
|
||||
@@ -1240,7 +1399,7 @@ local function config_view(display)
|
||||
table.insert(tmp_cfg.Redstone, def)
|
||||
|
||||
local name = rsio.to_string(def.port)
|
||||
local io_dir = util.trinary(rsio.get_io_dir(def.port) == rsio.IO_DIR.IN, "\x1a", "\x1b")
|
||||
local io_dir = tri(rsio.get_io_dir(def.port) == rsio.IO_DIR.IN, "\x1a", "\x1b")
|
||||
local conn = def.side
|
||||
local unit = "facility"
|
||||
|
||||
@@ -1257,7 +1416,7 @@ local function config_view(display)
|
||||
|
||||
tool_ctl.gen_summary(tmp_cfg)
|
||||
if tool_ctl.importing_any_dc then sum_pane.set_value(7) else sum_pane.set_value(1) end
|
||||
main_pane.set_value(5)
|
||||
main_pane.set_value(6)
|
||||
tool_ctl.settings_apply.hide(true)
|
||||
tool_ctl.settings_confirm.show()
|
||||
tool_ctl.importing_legacy = true
|
||||
@@ -1271,6 +1430,7 @@ local function config_view(display)
|
||||
|
||||
main_pane.set_value(1)
|
||||
net_pane.set_value(1)
|
||||
clr_pane.set_value(1)
|
||||
sum_pane.set_value(1)
|
||||
peri_pane.set_value(1)
|
||||
rs_pane.set_value(1)
|
||||
@@ -1301,11 +1461,17 @@ local function config_view(display)
|
||||
local raw = cfg[f[1]]
|
||||
local val = util.strval(raw)
|
||||
|
||||
if f[1] == "AuthKey" then val = string.rep("*", string.len(val)) end
|
||||
if f[1] == "LogMode" then val = util.trinary(raw == log.MODE.APPEND, "append", "replace") end
|
||||
if f[1] == "AuthKey" then val = string.rep("*", string.len(val))
|
||||
elseif f[1] == "LogMode" then val = tri(raw == log.MODE.APPEND, "append", "replace")
|
||||
elseif f[1] == "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))
|
||||
local c = tri(alternate, g_lg_fg_bg, cpair(colors.gray,colors.white))
|
||||
alternate = not alternate
|
||||
|
||||
if string.len(val) > val_max_w then
|
||||
@@ -1419,7 +1585,7 @@ local function config_view(display)
|
||||
end
|
||||
|
||||
tool_ctl.rs_cfg_selection.set_value(text)
|
||||
tool_ctl.rs_cfg_side_l.set_value(util.trinary(rsio.get_io_dir(def.port) == rsio.IO_DIR.IN, "Input Side", "Output Side"))
|
||||
tool_ctl.rs_cfg_side_l.set_value(tri(rsio.get_io_dir(def.port) == rsio.IO_DIR.IN, "Input Side", "Output Side"))
|
||||
side.set_value(side_to_idx(def.side))
|
||||
bundled.set_value(def.color ~= nil)
|
||||
tool_ctl.rs_cfg_color.set_value(value)
|
||||
@@ -1440,7 +1606,7 @@ local function config_view(display)
|
||||
local def = cfg.Redstone[i] ---@type rtu_rs_definition
|
||||
|
||||
local name = rsio.to_string(def.port)
|
||||
local io_dir = util.trinary(rsio.get_io_mode(def.port) == rsio.IO_DIR.IN, "\x1a", "\x1b")
|
||||
local io_dir = tri(rsio.get_io_mode(def.port) == rsio.IO_DIR.IN, "\x1a", "\x1b")
|
||||
local conn = def.side
|
||||
local unit = util.strval(def.unit or "F")
|
||||
|
||||
|
||||
@@ -9,58 +9,49 @@ local boilerv_rtu = {}
|
||||
function boilerv_rtu.new(boiler)
|
||||
local unit = rtu.init_unit(boiler)
|
||||
|
||||
-- disable auto fault clearing
|
||||
boiler.__p_clear_fault()
|
||||
boiler.__p_disable_afc()
|
||||
|
||||
-- discrete inputs --
|
||||
unit.connect_di(boiler.isFormed)
|
||||
unit.connect_di("isFormed")
|
||||
|
||||
-- coils --
|
||||
-- none
|
||||
|
||||
-- input registers --
|
||||
-- multiblock properties
|
||||
unit.connect_input_reg(boiler.getLength)
|
||||
unit.connect_input_reg(boiler.getWidth)
|
||||
unit.connect_input_reg(boiler.getHeight)
|
||||
unit.connect_input_reg(boiler.getMinPos)
|
||||
unit.connect_input_reg(boiler.getMaxPos)
|
||||
unit.connect_input_reg("getLength")
|
||||
unit.connect_input_reg("getWidth")
|
||||
unit.connect_input_reg("getHeight")
|
||||
unit.connect_input_reg("getMinPos")
|
||||
unit.connect_input_reg("getMaxPos")
|
||||
-- build properties
|
||||
unit.connect_input_reg(boiler.getBoilCapacity)
|
||||
unit.connect_input_reg(boiler.getSteamCapacity)
|
||||
unit.connect_input_reg(boiler.getWaterCapacity)
|
||||
unit.connect_input_reg(boiler.getHeatedCoolantCapacity)
|
||||
unit.connect_input_reg(boiler.getCooledCoolantCapacity)
|
||||
unit.connect_input_reg(boiler.getSuperheaters)
|
||||
unit.connect_input_reg(boiler.getMaxBoilRate)
|
||||
unit.connect_input_reg("getBoilCapacity")
|
||||
unit.connect_input_reg("getSteamCapacity")
|
||||
unit.connect_input_reg("getWaterCapacity")
|
||||
unit.connect_input_reg("getHeatedCoolantCapacity")
|
||||
unit.connect_input_reg("getCooledCoolantCapacity")
|
||||
unit.connect_input_reg("getSuperheaters")
|
||||
unit.connect_input_reg("getMaxBoilRate")
|
||||
-- current state
|
||||
unit.connect_input_reg(boiler.getTemperature)
|
||||
unit.connect_input_reg(boiler.getBoilRate)
|
||||
unit.connect_input_reg(boiler.getEnvironmentalLoss)
|
||||
unit.connect_input_reg("getTemperature")
|
||||
unit.connect_input_reg("getBoilRate")
|
||||
unit.connect_input_reg("getEnvironmentalLoss")
|
||||
-- tanks
|
||||
unit.connect_input_reg(boiler.getSteam)
|
||||
unit.connect_input_reg(boiler.getSteamNeeded)
|
||||
unit.connect_input_reg(boiler.getSteamFilledPercentage)
|
||||
unit.connect_input_reg(boiler.getWater)
|
||||
unit.connect_input_reg(boiler.getWaterNeeded)
|
||||
unit.connect_input_reg(boiler.getWaterFilledPercentage)
|
||||
unit.connect_input_reg(boiler.getHeatedCoolant)
|
||||
unit.connect_input_reg(boiler.getHeatedCoolantNeeded)
|
||||
unit.connect_input_reg(boiler.getHeatedCoolantFilledPercentage)
|
||||
unit.connect_input_reg(boiler.getCooledCoolant)
|
||||
unit.connect_input_reg(boiler.getCooledCoolantNeeded)
|
||||
unit.connect_input_reg(boiler.getCooledCoolantFilledPercentage)
|
||||
unit.connect_input_reg("getSteam")
|
||||
unit.connect_input_reg("getSteamNeeded")
|
||||
unit.connect_input_reg("getSteamFilledPercentage")
|
||||
unit.connect_input_reg("getWater")
|
||||
unit.connect_input_reg("getWaterNeeded")
|
||||
unit.connect_input_reg("getWaterFilledPercentage")
|
||||
unit.connect_input_reg("getHeatedCoolant")
|
||||
unit.connect_input_reg("getHeatedCoolantNeeded")
|
||||
unit.connect_input_reg("getHeatedCoolantFilledPercentage")
|
||||
unit.connect_input_reg("getCooledCoolant")
|
||||
unit.connect_input_reg("getCooledCoolantNeeded")
|
||||
unit.connect_input_reg("getCooledCoolantFilledPercentage")
|
||||
|
||||
-- holding registers --
|
||||
-- none
|
||||
|
||||
-- check if any calls faulted
|
||||
local faulted = boiler.__p_is_faulted()
|
||||
boiler.__p_clear_fault()
|
||||
boiler.__p_enable_afc()
|
||||
|
||||
return unit.interface(), faulted
|
||||
return unit.interface(), false
|
||||
end
|
||||
|
||||
return boilerv_rtu
|
||||
|
||||
@@ -9,12 +9,8 @@ local dynamicv_rtu = {}
|
||||
function dynamicv_rtu.new(dynamic_tank)
|
||||
local unit = rtu.init_unit(dynamic_tank)
|
||||
|
||||
-- disable auto fault clearing
|
||||
dynamic_tank.__p_clear_fault()
|
||||
dynamic_tank.__p_disable_afc()
|
||||
|
||||
-- discrete inputs --
|
||||
unit.connect_di(dynamic_tank.isFormed)
|
||||
unit.connect_di("isFormed")
|
||||
|
||||
-- coils --
|
||||
unit.connect_coil(function () dynamic_tank.incrementContainerEditMode() end, function () end)
|
||||
@@ -22,27 +18,22 @@ function dynamicv_rtu.new(dynamic_tank)
|
||||
|
||||
-- input registers --
|
||||
-- multiblock properties
|
||||
unit.connect_input_reg(dynamic_tank.getLength)
|
||||
unit.connect_input_reg(dynamic_tank.getWidth)
|
||||
unit.connect_input_reg(dynamic_tank.getHeight)
|
||||
unit.connect_input_reg(dynamic_tank.getMinPos)
|
||||
unit.connect_input_reg(dynamic_tank.getMaxPos)
|
||||
unit.connect_input_reg("getLength")
|
||||
unit.connect_input_reg("getWidth")
|
||||
unit.connect_input_reg("getHeight")
|
||||
unit.connect_input_reg("getMinPos")
|
||||
unit.connect_input_reg("getMaxPos")
|
||||
-- build properties
|
||||
unit.connect_input_reg(dynamic_tank.getTankCapacity)
|
||||
unit.connect_input_reg(dynamic_tank.getChemicalTankCapacity)
|
||||
unit.connect_input_reg("getTankCapacity")
|
||||
unit.connect_input_reg("getChemicalTankCapacity")
|
||||
-- tanks/containers
|
||||
unit.connect_input_reg(dynamic_tank.getStored)
|
||||
unit.connect_input_reg(dynamic_tank.getFilledPercentage)
|
||||
unit.connect_input_reg("getStored")
|
||||
unit.connect_input_reg("getFilledPercentage")
|
||||
|
||||
-- holding registers --
|
||||
unit.connect_holding_reg(dynamic_tank.getContainerEditMode, dynamic_tank.setContainerEditMode)
|
||||
unit.connect_holding_reg("getContainerEditMode", "setContainerEditMode")
|
||||
|
||||
-- check if any calls faulted
|
||||
local faulted = dynamic_tank.__p_is_faulted()
|
||||
dynamic_tank.__p_clear_fault()
|
||||
dynamic_tank.__p_enable_afc()
|
||||
|
||||
return unit.interface(), faulted
|
||||
return unit.interface(), false
|
||||
end
|
||||
|
||||
return dynamicv_rtu
|
||||
|
||||
@@ -9,45 +9,36 @@ local imatrix_rtu = {}
|
||||
function imatrix_rtu.new(imatrix)
|
||||
local unit = rtu.init_unit(imatrix)
|
||||
|
||||
-- disable auto fault clearing
|
||||
imatrix.__p_clear_fault()
|
||||
imatrix.__p_disable_afc()
|
||||
|
||||
-- discrete inputs --
|
||||
unit.connect_di(imatrix.isFormed)
|
||||
unit.connect_di("isFormed")
|
||||
|
||||
-- coils --
|
||||
-- none
|
||||
|
||||
-- input registers --
|
||||
-- multiblock properties
|
||||
unit.connect_input_reg(imatrix.getLength)
|
||||
unit.connect_input_reg(imatrix.getWidth)
|
||||
unit.connect_input_reg(imatrix.getHeight)
|
||||
unit.connect_input_reg(imatrix.getMinPos)
|
||||
unit.connect_input_reg(imatrix.getMaxPos)
|
||||
unit.connect_input_reg("getLength")
|
||||
unit.connect_input_reg("getWidth")
|
||||
unit.connect_input_reg("getHeight")
|
||||
unit.connect_input_reg("getMinPos")
|
||||
unit.connect_input_reg("getMaxPos")
|
||||
-- build properties
|
||||
unit.connect_input_reg(imatrix.getMaxEnergy)
|
||||
unit.connect_input_reg(imatrix.getTransferCap)
|
||||
unit.connect_input_reg(imatrix.getInstalledCells)
|
||||
unit.connect_input_reg(imatrix.getInstalledProviders)
|
||||
unit.connect_input_reg("getMaxEnergy")
|
||||
unit.connect_input_reg("getTransferCap")
|
||||
unit.connect_input_reg("getInstalledCells")
|
||||
unit.connect_input_reg("getInstalledProviders")
|
||||
-- I/O rates
|
||||
unit.connect_input_reg(imatrix.getLastInput)
|
||||
unit.connect_input_reg(imatrix.getLastOutput)
|
||||
unit.connect_input_reg("getLastInput")
|
||||
unit.connect_input_reg("getLastOutput")
|
||||
-- tanks
|
||||
unit.connect_input_reg(imatrix.getEnergy)
|
||||
unit.connect_input_reg(imatrix.getEnergyNeeded)
|
||||
unit.connect_input_reg(imatrix.getEnergyFilledPercentage)
|
||||
unit.connect_input_reg("getEnergy")
|
||||
unit.connect_input_reg("getEnergyNeeded")
|
||||
unit.connect_input_reg("getEnergyFilledPercentage")
|
||||
|
||||
-- holding registers --
|
||||
-- none
|
||||
|
||||
-- check if any calls faulted
|
||||
local faulted = imatrix.__p_is_faulted()
|
||||
imatrix.__p_clear_fault()
|
||||
imatrix.__p_enable_afc()
|
||||
|
||||
return unit.interface(), faulted
|
||||
return unit.interface(), false
|
||||
end
|
||||
|
||||
return imatrix_rtu
|
||||
|
||||
@@ -9,50 +9,41 @@ local sps_rtu = {}
|
||||
function sps_rtu.new(sps)
|
||||
local unit = rtu.init_unit(sps)
|
||||
|
||||
-- disable auto fault clearing
|
||||
sps.__p_clear_fault()
|
||||
sps.__p_disable_afc()
|
||||
|
||||
-- discrete inputs --
|
||||
unit.connect_di(sps.isFormed)
|
||||
unit.connect_di("isFormed")
|
||||
|
||||
-- coils --
|
||||
-- none
|
||||
|
||||
-- input registers --
|
||||
-- multiblock properties
|
||||
unit.connect_input_reg(sps.getLength)
|
||||
unit.connect_input_reg(sps.getWidth)
|
||||
unit.connect_input_reg(sps.getHeight)
|
||||
unit.connect_input_reg(sps.getMinPos)
|
||||
unit.connect_input_reg(sps.getMaxPos)
|
||||
unit.connect_input_reg("getLength")
|
||||
unit.connect_input_reg("getWidth")
|
||||
unit.connect_input_reg("getHeight")
|
||||
unit.connect_input_reg("getMinPos")
|
||||
unit.connect_input_reg("getMaxPos")
|
||||
-- build properties
|
||||
unit.connect_input_reg(sps.getCoils)
|
||||
unit.connect_input_reg(sps.getInputCapacity)
|
||||
unit.connect_input_reg(sps.getOutputCapacity)
|
||||
unit.connect_input_reg(sps.getMaxEnergy)
|
||||
unit.connect_input_reg("getCoils")
|
||||
unit.connect_input_reg("getInputCapacity")
|
||||
unit.connect_input_reg("getOutputCapacity")
|
||||
unit.connect_input_reg("getMaxEnergy")
|
||||
-- current state
|
||||
unit.connect_input_reg(sps.getProcessRate)
|
||||
unit.connect_input_reg("getProcessRate")
|
||||
-- tanks
|
||||
unit.connect_input_reg(sps.getInput)
|
||||
unit.connect_input_reg(sps.getInputNeeded)
|
||||
unit.connect_input_reg(sps.getInputFilledPercentage)
|
||||
unit.connect_input_reg(sps.getOutput)
|
||||
unit.connect_input_reg(sps.getOutputNeeded)
|
||||
unit.connect_input_reg(sps.getOutputFilledPercentage)
|
||||
unit.connect_input_reg(sps.getEnergy)
|
||||
unit.connect_input_reg(sps.getEnergyNeeded)
|
||||
unit.connect_input_reg(sps.getEnergyFilledPercentage)
|
||||
unit.connect_input_reg("getInput")
|
||||
unit.connect_input_reg("getInputNeeded")
|
||||
unit.connect_input_reg("getInputFilledPercentage")
|
||||
unit.connect_input_reg("getOutput")
|
||||
unit.connect_input_reg("getOutputNeeded")
|
||||
unit.connect_input_reg("getOutputFilledPercentage")
|
||||
unit.connect_input_reg("getEnergy")
|
||||
unit.connect_input_reg("getEnergyNeeded")
|
||||
unit.connect_input_reg("getEnergyFilledPercentage")
|
||||
|
||||
-- holding registers --
|
||||
-- none
|
||||
|
||||
-- check if any calls faulted
|
||||
local faulted = sps.__p_is_faulted()
|
||||
sps.__p_clear_fault()
|
||||
sps.__p_enable_afc()
|
||||
|
||||
return unit.interface(), faulted
|
||||
return unit.interface(), false
|
||||
end
|
||||
|
||||
return sps_rtu
|
||||
|
||||
@@ -9,12 +9,8 @@ local turbinev_rtu = {}
|
||||
function turbinev_rtu.new(turbine)
|
||||
local unit = rtu.init_unit(turbine)
|
||||
|
||||
-- disable auto fault clearing
|
||||
turbine.__p_clear_fault()
|
||||
turbine.__p_disable_afc()
|
||||
|
||||
-- discrete inputs --
|
||||
unit.connect_di(turbine.isFormed)
|
||||
unit.connect_di("isFormed")
|
||||
|
||||
-- coils --
|
||||
unit.connect_coil(function () turbine.incrementDumpingMode() end, function () end)
|
||||
@@ -22,44 +18,39 @@ function turbinev_rtu.new(turbine)
|
||||
|
||||
-- input registers --
|
||||
-- multiblock properties
|
||||
unit.connect_input_reg(turbine.getLength)
|
||||
unit.connect_input_reg(turbine.getWidth)
|
||||
unit.connect_input_reg(turbine.getHeight)
|
||||
unit.connect_input_reg(turbine.getMinPos)
|
||||
unit.connect_input_reg(turbine.getMaxPos)
|
||||
unit.connect_input_reg("getLength")
|
||||
unit.connect_input_reg("getWidth")
|
||||
unit.connect_input_reg("getHeight")
|
||||
unit.connect_input_reg("getMinPos")
|
||||
unit.connect_input_reg("getMaxPos")
|
||||
-- build properties
|
||||
unit.connect_input_reg(turbine.getBlades)
|
||||
unit.connect_input_reg(turbine.getCoils)
|
||||
unit.connect_input_reg(turbine.getVents)
|
||||
unit.connect_input_reg(turbine.getDispersers)
|
||||
unit.connect_input_reg(turbine.getCondensers)
|
||||
unit.connect_input_reg(turbine.getSteamCapacity)
|
||||
unit.connect_input_reg(turbine.getMaxEnergy)
|
||||
unit.connect_input_reg(turbine.getMaxFlowRate)
|
||||
unit.connect_input_reg(turbine.getMaxProduction)
|
||||
unit.connect_input_reg(turbine.getMaxWaterOutput)
|
||||
unit.connect_input_reg("getBlades")
|
||||
unit.connect_input_reg("getCoils")
|
||||
unit.connect_input_reg("getVents")
|
||||
unit.connect_input_reg("getDispersers")
|
||||
unit.connect_input_reg("getCondensers")
|
||||
unit.connect_input_reg("getSteamCapacity")
|
||||
unit.connect_input_reg("getMaxEnergy")
|
||||
unit.connect_input_reg("getMaxFlowRate")
|
||||
unit.connect_input_reg("getMaxProduction")
|
||||
unit.connect_input_reg("getMaxWaterOutput")
|
||||
-- current state
|
||||
unit.connect_input_reg(turbine.getFlowRate)
|
||||
unit.connect_input_reg(turbine.getProductionRate)
|
||||
unit.connect_input_reg(turbine.getLastSteamInputRate)
|
||||
unit.connect_input_reg(turbine.getDumpingMode)
|
||||
unit.connect_input_reg("getFlowRate")
|
||||
unit.connect_input_reg("getProductionRate")
|
||||
unit.connect_input_reg("getLastSteamInputRate")
|
||||
unit.connect_input_reg("getDumpingMode")
|
||||
-- tanks/containers
|
||||
unit.connect_input_reg(turbine.getSteam)
|
||||
unit.connect_input_reg(turbine.getSteamNeeded)
|
||||
unit.connect_input_reg(turbine.getSteamFilledPercentage)
|
||||
unit.connect_input_reg(turbine.getEnergy)
|
||||
unit.connect_input_reg(turbine.getEnergyNeeded)
|
||||
unit.connect_input_reg(turbine.getEnergyFilledPercentage)
|
||||
unit.connect_input_reg("getSteam")
|
||||
unit.connect_input_reg("getSteamNeeded")
|
||||
unit.connect_input_reg("getSteamFilledPercentage")
|
||||
unit.connect_input_reg("getEnergy")
|
||||
unit.connect_input_reg("getEnergyNeeded")
|
||||
unit.connect_input_reg("getEnergyFilledPercentage")
|
||||
|
||||
-- holding registers --
|
||||
unit.connect_holding_reg(turbine.getDumpingMode, turbine.setDumpingMode)
|
||||
unit.connect_holding_reg("getDumpingMode", "setDumpingMode")
|
||||
|
||||
-- check if any calls faulted
|
||||
local faulted = turbine.__p_is_faulted()
|
||||
turbine.__p_clear_fault()
|
||||
turbine.__p_enable_afc()
|
||||
|
||||
return unit.interface(), faulted
|
||||
return unit.interface(), false
|
||||
end
|
||||
|
||||
return turbinev_rtu
|
||||
|
||||
@@ -16,14 +16,15 @@ local TextBox = require("graphics.elements.textbox")
|
||||
|
||||
local DataIndicator = require("graphics.elements.indicators.data")
|
||||
local LED = require("graphics.elements.indicators.led")
|
||||
local LEDPair = require("graphics.elements.indicators.ledpair")
|
||||
local RGBLED = require("graphics.elements.indicators.ledrgb")
|
||||
|
||||
local LINK_STATE = types.PANEL_LINK_STATE
|
||||
|
||||
local ALIGN = core.ALIGN
|
||||
|
||||
local cpair = core.cpair
|
||||
|
||||
local fp_label = style.fp_label
|
||||
|
||||
local ind_grn = style.ind_grn
|
||||
|
||||
local UNIT_TYPE_LABELS = { "UNKNOWN", "REDSTONE", "BOILER", "TURBINE", "DYNAMIC TANK", "IND MATRIX", "SPS", "SNA", "ENV DETECTOR" }
|
||||
@@ -32,7 +33,9 @@ local UNIT_TYPE_LABELS = { "UNKNOWN", "REDSTONE", "BOILER", "TURBINE", "DYNAMIC
|
||||
---@param panel graphics_element main displaybox
|
||||
---@param units table unit list
|
||||
local function init(panel, units)
|
||||
TextBox{parent=panel,y=1,text="RTU GATEWAY",alignment=ALIGN.CENTER,height=1,fg_bg=style.header}
|
||||
local disabled_fg = style.fp.disabled_fg
|
||||
|
||||
TextBox{parent=panel,y=1,text="RTU GATEWAY",alignment=ALIGN.CENTER,height=1,fg_bg=style.theme.header}
|
||||
|
||||
--
|
||||
-- system indicators
|
||||
@@ -48,12 +51,43 @@ local function init(panel, units)
|
||||
heartbeat.register(databus.ps, "heartbeat", heartbeat.update)
|
||||
|
||||
local modem = LED{parent=system,label="MODEM",colors=ind_grn}
|
||||
local network = RGBLED{parent=system,label="NETWORK",colors={colors.green,colors.red,colors.orange,colors.yellow,colors.gray}}
|
||||
network.update(types.PANEL_LINK_STATE.DISCONNECTED)
|
||||
|
||||
if not style.colorblind then
|
||||
local network = RGBLED{parent=system,label="NETWORK",colors={colors.green,colors.red,colors.orange,colors.yellow,style.ind_bkg}}
|
||||
network.update(types.PANEL_LINK_STATE.DISCONNECTED)
|
||||
network.register(databus.ps, "link_state", network.update)
|
||||
else
|
||||
local nt_lnk = LEDPair{parent=system,label="NT LINKED",off=style.ind_bkg,c1=colors.red,c2=colors.green}
|
||||
local nt_ver = LEDPair{parent=system,label="NT VERSION",off=style.ind_bkg,c1=colors.red,c2=colors.green}
|
||||
|
||||
nt_lnk.register(databus.ps, "link_state", function (state)
|
||||
local value = 2
|
||||
|
||||
if state == LINK_STATE.DISCONNECTED then
|
||||
value = 1
|
||||
elseif state == LINK_STATE.LINKED then
|
||||
value = 3
|
||||
end
|
||||
|
||||
nt_lnk.update(value)
|
||||
end)
|
||||
|
||||
nt_ver.register(databus.ps, "link_state", function (state)
|
||||
local value = 3
|
||||
|
||||
if state == LINK_STATE.BAD_VERSION then
|
||||
value = 2
|
||||
elseif state == LINK_STATE.DISCONNECTED then
|
||||
value = 1
|
||||
end
|
||||
|
||||
nt_ver.update(value)
|
||||
end)
|
||||
end
|
||||
|
||||
system.line_break()
|
||||
|
||||
modem.register(databus.ps, "has_modem", modem.update)
|
||||
network.register(databus.ps, "link_state", network.update)
|
||||
|
||||
local rt_main = LED{parent=system,label="RT MAIN",colors=ind_grn}
|
||||
local rt_comm = LED{parent=system,label="RT COMMS",colors=ind_grn}
|
||||
@@ -64,17 +98,17 @@ local function init(panel, units)
|
||||
|
||||
---@diagnostic disable-next-line: undefined-field
|
||||
local comp_id = util.sprintf("(%d)", os.getComputerID())
|
||||
TextBox{parent=system,x=9,y=4,width=6,height=1,text=comp_id,fg_bg=fp_label}
|
||||
TextBox{parent=system,x=9,y=4,width=6,height=1,text=comp_id,fg_bg=disabled_fg}
|
||||
|
||||
TextBox{parent=system,x=1,y=14,text="SPEAKERS",height=1,width=8,fg_bg=style.label}
|
||||
local speaker_count = DataIndicator{parent=system,x=10,y=14,label="",format="%3d",value=0,width=3,fg_bg=cpair(colors.gray,colors.white)}
|
||||
TextBox{parent=system,x=1,y=14,text="SPEAKERS",height=1,width=8,fg_bg=style.fp.text_fg}
|
||||
local speaker_count = DataIndicator{parent=system,x=10,y=14,label="",format="%3d",value=0,width=3,fg_bg=style.theme.field_box}
|
||||
speaker_count.register(databus.ps, "speaker_count", speaker_count.update)
|
||||
|
||||
--
|
||||
-- about label
|
||||
--
|
||||
|
||||
local about = Div{parent=panel,width=15,height=3,x=1,y=18,fg_bg=fp_label}
|
||||
local about = Div{parent=panel,width=15,height=3,x=1,y=18,fg_bg=disabled_fg}
|
||||
local fw_v = TextBox{parent=about,x=1,y=1,text="FW: v00.00.00",alignment=ALIGN.LEFT,height=1}
|
||||
local comms_v = TextBox{parent=about,x=1,y=2,text="NT: v00.00.00",alignment=ALIGN.LEFT,height=1}
|
||||
|
||||
@@ -116,7 +150,7 @@ local function init(panel, units)
|
||||
|
||||
-- assignment (unit # or facility)
|
||||
local for_unit = util.trinary(unit.reactor == 0, "\x1a FACIL ", "\x1a UNIT " .. unit.reactor)
|
||||
TextBox{parent=unit_hw_statuses,y=i,x=19,text=for_unit,height=1,fg_bg=fp_label}
|
||||
TextBox{parent=unit_hw_statuses,y=i,x=19,text=for_unit,height=1,fg_bg=disabled_fg}
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -2,47 +2,39 @@
|
||||
-- Graphics Style Options
|
||||
--
|
||||
|
||||
local core = require("graphics.core")
|
||||
local core = require("graphics.core")
|
||||
local themes = require("graphics.themes")
|
||||
|
||||
---@class rtu_style
|
||||
local style = {}
|
||||
|
||||
local cpair = core.cpair
|
||||
|
||||
-- GLOBAL --
|
||||
|
||||
-- remap global colors
|
||||
colors.ivory = colors.pink
|
||||
colors.yellow_hc = colors.purple
|
||||
colors.red_off = colors.brown
|
||||
colors.yellow_off = colors.magenta
|
||||
colors.green_off = colors.lime
|
||||
|
||||
style.root = cpair(colors.black, colors.ivory)
|
||||
style.header = cpair(colors.black, colors.lightGray)
|
||||
|
||||
style.colors = {
|
||||
{ c = colors.red, hex = 0xdf4949 }, -- RED ON
|
||||
{ c = colors.orange, hex = 0xffb659 },
|
||||
{ c = colors.yellow, hex = 0xf9fb53 }, -- YELLOW ON
|
||||
{ c = colors.lime, hex = 0x16665a }, -- GREEN OFF
|
||||
{ c = colors.green, hex = 0x6be551 }, -- GREEN ON
|
||||
{ c = colors.cyan, hex = 0x34bac8 },
|
||||
{ c = colors.lightBlue, hex = 0x6cc0f2 },
|
||||
{ c = colors.blue, hex = 0x0096ff },
|
||||
{ c = colors.purple, hex = 0xb156ee }, -- YELLOW HIGH CONTRAST
|
||||
{ c = colors.pink, hex = 0xdcd9ca }, -- IVORY
|
||||
{ c = colors.magenta, hex = 0x85862c }, -- YELLOW OFF
|
||||
-- { c = colors.white, hex = 0xdcd9ca },
|
||||
{ c = colors.lightGray, hex = 0xb1b8b3 },
|
||||
{ c = colors.gray, hex = 0x575757 },
|
||||
-- { c = colors.black, hex = 0x191919 },
|
||||
{ c = colors.brown, hex = 0x672223 } -- RED OFF
|
||||
}
|
||||
|
||||
-- COMMON COLOR PAIRS --
|
||||
|
||||
style.fp_label = cpair(colors.lightGray, colors.ivory)
|
||||
style.theme = themes.sandstone
|
||||
style.fp = themes.get_fp_style(style.theme)
|
||||
style.colorblind = false
|
||||
|
||||
style.ind_grn = cpair(colors.green, colors.green_off)
|
||||
|
||||
-- set theme per configuration
|
||||
---@param fp FP_THEME front panel theme
|
||||
---@param color_mode COLOR_MODE the color mode to use
|
||||
function style.set_theme(fp, color_mode)
|
||||
if fp == themes.FP_THEME.SANDSTONE then
|
||||
style.theme = themes.sandstone
|
||||
elseif fp == themes.FP_THEME.BASALT then
|
||||
style.theme = themes.basalt
|
||||
end
|
||||
|
||||
style.fp = themes.get_fp_style(style.theme)
|
||||
|
||||
style.colorblind = color_mode ~= themes.COLOR_MODE.STANDARD and color_mode ~= themes.COLOR_MODE.STD_ON_BLACK
|
||||
|
||||
if color_mode == themes.COLOR_MODE.STANDARD or color_mode == themes.COLOR_MODE.BLUE_IND then
|
||||
style.ind_bkg = colors.gray
|
||||
else
|
||||
style.ind_bkg = colors.black
|
||||
end
|
||||
end
|
||||
|
||||
return style
|
||||
|
||||
@@ -19,11 +19,16 @@ local ui = {
|
||||
|
||||
-- try to start the UI
|
||||
---@param units table RTU units
|
||||
---@param theme FP_THEME front panel theme
|
||||
---@param color_mode COLOR_MODE color mode
|
||||
---@return boolean success, any error_msg
|
||||
function renderer.try_start_ui(units)
|
||||
function renderer.try_start_ui(units, theme, color_mode)
|
||||
local status, msg = true, nil
|
||||
|
||||
if ui.display == nil then
|
||||
-- set theme
|
||||
style.set_theme(theme, color_mode)
|
||||
|
||||
-- reset terminal
|
||||
term.setTextColor(colors.white)
|
||||
term.setBackgroundColor(colors.black)
|
||||
@@ -31,13 +36,19 @@ function renderer.try_start_ui(units)
|
||||
term.setCursorPos(1, 1)
|
||||
|
||||
-- set overridden colors
|
||||
for i = 1, #style.colors do
|
||||
term.setPaletteColor(style.colors[i].c, style.colors[i].hex)
|
||||
for i = 1, #style.theme.colors do
|
||||
term.setPaletteColor(style.theme.colors[i].c, style.theme.colors[i].hex)
|
||||
end
|
||||
|
||||
-- apply color mode
|
||||
local c_mode_overrides = style.theme.color_modes[color_mode]
|
||||
for i = 1, #c_mode_overrides do
|
||||
term.setPaletteColor(c_mode_overrides[i].c, c_mode_overrides[i].hex)
|
||||
end
|
||||
|
||||
-- init front panel view
|
||||
status, msg = pcall(function ()
|
||||
ui.display = DisplayBox{window=term.current(),fg_bg=style.root}
|
||||
ui.display = DisplayBox{window=term.current(),fg_bg=style.fp.root}
|
||||
panel_view(ui.display, units)
|
||||
end)
|
||||
|
||||
@@ -65,9 +76,9 @@ function renderer.close_ui()
|
||||
ui.display = nil
|
||||
|
||||
-- restore colors
|
||||
for i = 1, #style.colors do
|
||||
local r, g, b = term.nativePaletteColor(style.colors[i].c)
|
||||
term.setPaletteColor(style.colors[i].c, r, g, b)
|
||||
for i = 1, #style.theme.colors do
|
||||
local r, g, b = term.nativePaletteColor(style.theme.colors[i].c)
|
||||
term.setPaletteColor(style.theme.colors[i].c, r, g, b)
|
||||
end
|
||||
|
||||
-- reset terminal
|
||||
|
||||
49
rtu/rtu.lua
49
rtu/rtu.lua
@@ -5,6 +5,8 @@ local log = require("scada-common.log")
|
||||
local types = require("scada-common.types")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local themes = require("graphics.themes")
|
||||
|
||||
local databus = require("rtu.databus")
|
||||
local modbus = require("rtu.modbus")
|
||||
|
||||
@@ -29,15 +31,20 @@ function rtu.load_config()
|
||||
config.Redstone = settings.get("Redstone")
|
||||
|
||||
config.SpeakerVolume = settings.get("SpeakerVolume")
|
||||
|
||||
config.SVR_Channel = settings.get("SVR_Channel")
|
||||
config.RTU_Channel = settings.get("RTU_Channel")
|
||||
config.ConnTimeout = settings.get("ConnTimeout")
|
||||
config.TrustedRange = settings.get("TrustedRange")
|
||||
config.AuthKey = settings.get("AuthKey")
|
||||
|
||||
config.LogMode = settings.get("LogMode")
|
||||
config.LogPath = settings.get("LogPath")
|
||||
config.LogDebug = settings.get("LogDebug")
|
||||
|
||||
config.FrontPanelTheme = settings.get("FrontPanelTheme")
|
||||
config.ColorMode = settings.get("ColorMode")
|
||||
|
||||
local cfv = util.new_validator()
|
||||
|
||||
cfv.assert_type_num(config.SpeakerVolume)
|
||||
@@ -53,7 +60,7 @@ function rtu.load_config()
|
||||
|
||||
if type(config.AuthKey) == "string" then
|
||||
local len = string.len(config.AuthKey)
|
||||
cfv.assert_eq(len == 0 or len >= 8, true)
|
||||
cfv.assert(len == 0 or len >= 8)
|
||||
end
|
||||
|
||||
cfv.assert_type_int(config.LogMode)
|
||||
@@ -61,6 +68,11 @@ function rtu.load_config()
|
||||
cfv.assert_type_str(config.LogPath)
|
||||
cfv.assert_type_bool(config.LogDebug)
|
||||
|
||||
cfv.assert_type_int(config.FrontPanelTheme)
|
||||
cfv.assert_range(config.FrontPanelTheme, 1, 2)
|
||||
cfv.assert_type_int(config.ColorMode)
|
||||
cfv.assert_range(config.ColorMode, 1, themes.COLOR_MODE.NUM_MODES)
|
||||
|
||||
cfv.assert_type_table(config.Peripherals)
|
||||
cfv.assert_type_table(config.Redstone)
|
||||
|
||||
@@ -82,6 +94,8 @@ function rtu.init_unit(device)
|
||||
|
||||
local insert = table.insert
|
||||
|
||||
local stub = function () log.warning("tried to call an RTU function stub") end
|
||||
|
||||
---@class rtu_device
|
||||
local public = {}
|
||||
|
||||
@@ -103,13 +117,26 @@ function rtu.init_unit(device)
|
||||
return self.io_count_cache[1], self.io_count_cache[2], self.io_count_cache[3], self.io_count_cache[4]
|
||||
end
|
||||
|
||||
-- pass a function through or generate one to call a function by name from the device
|
||||
---@param f function|string function or device function name
|
||||
local function _as_func(f)
|
||||
if type(f) == "string" then
|
||||
local name = f
|
||||
if device then
|
||||
f = function (...) return device[name](...) end
|
||||
else f = stub end
|
||||
end
|
||||
|
||||
return f
|
||||
end
|
||||
|
||||
-- discrete inputs: single bit read-only
|
||||
|
||||
-- connect discrete input
|
||||
---@param f function
|
||||
---@param f function|string function or function name
|
||||
---@return integer count count of discrete inputs
|
||||
function protected.connect_di(f)
|
||||
insert(self.discrete_inputs, { read = f })
|
||||
insert(self.discrete_inputs, { read = _as_func(f) })
|
||||
_count_io()
|
||||
return #self.discrete_inputs
|
||||
end
|
||||
@@ -125,11 +152,11 @@ function rtu.init_unit(device)
|
||||
-- coils: single bit read-write
|
||||
|
||||
-- connect coil
|
||||
---@param f_read function
|
||||
---@param f_write function
|
||||
---@param f_read function|string function or function name
|
||||
---@param f_write function|string function or function name
|
||||
---@return integer count count of coils
|
||||
function protected.connect_coil(f_read, f_write)
|
||||
insert(self.coils, { read = f_read, write = f_write })
|
||||
insert(self.coils, { read = _as_func(f_read), write = _as_func(f_write) })
|
||||
_count_io()
|
||||
return #self.coils
|
||||
end
|
||||
@@ -154,10 +181,10 @@ function rtu.init_unit(device)
|
||||
-- input registers: multi-bit read-only
|
||||
|
||||
-- connect input register
|
||||
---@param f function
|
||||
---@param f function|string function or function name
|
||||
---@return integer count count of input registers
|
||||
function protected.connect_input_reg(f)
|
||||
insert(self.input_regs, { read = f })
|
||||
insert(self.input_regs, { read = _as_func(f) })
|
||||
_count_io()
|
||||
return #self.input_regs
|
||||
end
|
||||
@@ -173,11 +200,11 @@ function rtu.init_unit(device)
|
||||
-- holding registers: multi-bit read-write
|
||||
|
||||
-- connect holding register
|
||||
---@param f_read function
|
||||
---@param f_write function
|
||||
---@param f_read function|string function or function name
|
||||
---@param f_write function|string function or function name
|
||||
---@return integer count count of holding registers
|
||||
function protected.connect_holding_reg(f_read, f_write)
|
||||
insert(self.holding_regs, { read = f_read, write = f_write })
|
||||
insert(self.holding_regs, { read = _as_func(f_read), write = _as_func(f_write) })
|
||||
_count_io()
|
||||
return #self.holding_regs
|
||||
end
|
||||
|
||||
@@ -31,7 +31,7 @@ local sna_rtu = require("rtu.dev.sna_rtu")
|
||||
local sps_rtu = require("rtu.dev.sps_rtu")
|
||||
local turbinev_rtu = require("rtu.dev.turbinev_rtu")
|
||||
|
||||
local RTU_VERSION = "v1.7.15"
|
||||
local RTU_VERSION = "v1.9.5"
|
||||
|
||||
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
|
||||
local RTU_UNIT_HW_STATE = databus.RTU_UNIT_HW_STATE
|
||||
@@ -71,6 +71,7 @@ log.info("========================================")
|
||||
println(">> RTU GATEWAY " .. RTU_VERSION .. " <<")
|
||||
|
||||
crash.set_env("rtu", RTU_VERSION)
|
||||
crash.dbg_log_env()
|
||||
|
||||
----------------------------------------
|
||||
-- main application
|
||||
@@ -342,7 +343,7 @@ local function main()
|
||||
is_multiblock = true
|
||||
formed = device.isFormed()
|
||||
|
||||
if formed == ppm.UNDEFINED_FIELD or formed == ppm.ACCESS_FAULT then
|
||||
if formed == ppm.ACCESS_FAULT then
|
||||
println_ts(util.c("sys_config> failed to check if '", name, "' is formed"))
|
||||
log.fatal(util.c("sys_config> failed to check if '", name, "' is a formed boiler multiblock"))
|
||||
return false
|
||||
@@ -357,7 +358,7 @@ local function main()
|
||||
is_multiblock = true
|
||||
formed = device.isFormed()
|
||||
|
||||
if formed == ppm.UNDEFINED_FIELD or formed == ppm.ACCESS_FAULT then
|
||||
if formed == ppm.ACCESS_FAULT then
|
||||
println_ts(util.c("sys_config> failed to check if '", name, "' is formed"))
|
||||
log.fatal(util.c("sys_config> failed to check if '", name, "' is a formed turbine multiblock"))
|
||||
return false
|
||||
@@ -377,7 +378,7 @@ local function main()
|
||||
is_multiblock = true
|
||||
formed = device.isFormed()
|
||||
|
||||
if formed == ppm.UNDEFINED_FIELD or formed == ppm.ACCESS_FAULT then
|
||||
if formed == ppm.ACCESS_FAULT then
|
||||
println_ts(util.c("sys_config> failed to check if '", name, "' is formed"))
|
||||
log.fatal(util.c("sys_config> failed to check if '", name, "' is a formed dynamic tank multiblock"))
|
||||
return false
|
||||
@@ -391,7 +392,7 @@ local function main()
|
||||
is_multiblock = true
|
||||
formed = device.isFormed()
|
||||
|
||||
if formed == ppm.UNDEFINED_FIELD or formed == ppm.ACCESS_FAULT then
|
||||
if formed == ppm.ACCESS_FAULT then
|
||||
println_ts(util.c("sys_config> failed to check if '", name, "' is formed"))
|
||||
log.fatal(util.c("sys_config> failed to check if '", name, "' is a formed induction matrix multiblock"))
|
||||
return false
|
||||
@@ -405,7 +406,7 @@ local function main()
|
||||
is_multiblock = true
|
||||
formed = device.isFormed()
|
||||
|
||||
if formed == ppm.UNDEFINED_FIELD or formed == ppm.ACCESS_FAULT then
|
||||
if formed == ppm.ACCESS_FAULT then
|
||||
println_ts(util.c("sys_config> failed to check if '", name, "' is formed"))
|
||||
log.fatal(util.c("sys_config> failed to check if '", name, "' is a formed SPS multiblock"))
|
||||
return false
|
||||
@@ -471,7 +472,8 @@ local function main()
|
||||
for_message = util.c("reactor ", for_reactor)
|
||||
end
|
||||
|
||||
log.info(util.c("sys_config> initialized RTU unit #", #units, ": ", name, " (", types.rtu_type_to_string(rtu_type), ") [", index, "] for ", for_message))
|
||||
local index_str = util.trinary(index ~= nil, util.c(" [", index, "]"), "")
|
||||
log.info(util.c("sys_config> initialized RTU unit #", #units, ": ", name, " (", types.rtu_type_to_string(rtu_type), ")", index_str, " for ", for_message))
|
||||
|
||||
rtu_unit.uid = #units
|
||||
|
||||
@@ -506,7 +508,7 @@ local function main()
|
||||
if sys_config() then
|
||||
-- start UI
|
||||
local message
|
||||
rtu_state.fp_ok, message = renderer.try_start_ui(units)
|
||||
rtu_state.fp_ok, message = renderer.try_start_ui(units, config.FrontPanelTheme, config.ColorMode)
|
||||
|
||||
if not rtu_state.fp_ok then
|
||||
println_ts(util.c("UI error: ", message))
|
||||
|
||||
@@ -517,82 +517,23 @@ function threads.thread__unit_comms(smem, unit)
|
||||
|
||||
-- check if multiblock is still formed if this is a multiblock
|
||||
if unit.is_multiblock and (util.time_ms() - last_f_check > 250) then
|
||||
local is_formed = unit.device.isFormed()
|
||||
|
||||
last_f_check = util.time_ms()
|
||||
|
||||
local is_formed = unit.device.isFormed()
|
||||
|
||||
if unit.formed == nil then
|
||||
unit.formed = is_formed
|
||||
if is_formed then unit.hw_state = UNIT_HW_STATE.OK end
|
||||
elseif not unit.formed then
|
||||
unit.hw_state = UNIT_HW_STATE.UNFORMED
|
||||
end
|
||||
|
||||
if not unit.formed then unit.hw_state = UNIT_HW_STATE.UNFORMED end
|
||||
|
||||
if (not unit.formed) and is_formed then
|
||||
-- newly re-formed
|
||||
local iface = ppm.get_iface(unit.device)
|
||||
if iface then
|
||||
log.info(util.c("unmounting and remounting reformed RTU unit ", detail_name))
|
||||
|
||||
ppm.unmount(unit.device)
|
||||
|
||||
local type, device = ppm.mount(iface)
|
||||
local faulted = false
|
||||
|
||||
if device ~= nil then
|
||||
if type == "boilerValve" and unit.type == RTU_UNIT_TYPE.BOILER_VALVE then
|
||||
-- boiler multiblock
|
||||
unit.device = device
|
||||
unit.rtu, faulted = boilerv_rtu.new(device)
|
||||
unit.formed = device.isFormed()
|
||||
unit.modbus_io = modbus.new(unit.rtu, true)
|
||||
elseif type == "turbineValve" and unit.type == RTU_UNIT_TYPE.TURBINE_VALVE then
|
||||
-- turbine multiblock
|
||||
unit.device = device
|
||||
unit.rtu, faulted = turbinev_rtu.new(device)
|
||||
unit.formed = device.isFormed()
|
||||
unit.modbus_io = modbus.new(unit.rtu, true)
|
||||
elseif type == "dynamicValve" and unit.type == RTU_UNIT_TYPE.DYNAMIC_VALVE then
|
||||
-- dynamic tank multiblock
|
||||
unit.device = device
|
||||
unit.rtu, faulted = dynamicv_rtu.new(device)
|
||||
unit.formed = device.isFormed()
|
||||
unit.modbus_io = modbus.new(unit.rtu, true)
|
||||
elseif type == "inductionPort" and unit.type == RTU_UNIT_TYPE.IMATRIX then
|
||||
-- induction matrix multiblock
|
||||
unit.device = device
|
||||
unit.rtu, faulted = imatrix_rtu.new(device)
|
||||
unit.formed = device.isFormed()
|
||||
unit.modbus_io = modbus.new(unit.rtu, true)
|
||||
elseif type == "spsPort" and unit.type == RTU_UNIT_TYPE.SPS then
|
||||
-- SPS multiblock
|
||||
unit.device = device
|
||||
unit.rtu, faulted = sps_rtu.new(device)
|
||||
unit.formed = device.isFormed()
|
||||
unit.modbus_io = modbus.new(unit.rtu, true)
|
||||
else
|
||||
log.error("illegal remount of non-multiblock RTU or type change attempted for " .. short_name, true)
|
||||
end
|
||||
|
||||
if unit.formed and faulted then
|
||||
-- something is still wrong = can't mark as formed yet
|
||||
unit.formed = false
|
||||
unit.hw_state = UNIT_HW_STATE.UNFORMED
|
||||
log.info(util.c("assuming ", unit.name, " is not formed due to PPM faults while initializing"))
|
||||
else
|
||||
unit.hw_state = UNIT_HW_STATE.OK
|
||||
rtu_comms.send_remounted(unit.uid)
|
||||
end
|
||||
|
||||
local type_name = types.rtu_type_to_string(unit.type)
|
||||
log.info(util.c("reconnected the ", type_name, " on interface ", unit.name))
|
||||
else
|
||||
-- fully lost the peripheral now :(
|
||||
log.error(util.c(unit.name, " lost (failed reconnect)"))
|
||||
end
|
||||
else
|
||||
log.error("failed to get interface of previously connected RTU unit " .. detail_name, true)
|
||||
end
|
||||
if (is_formed == true) and not unit.formed then
|
||||
unit.hw_state = UNIT_HW_STATE.OK
|
||||
log.info(util.c(detail_name, " is now formed"))
|
||||
rtu_comms.send_remounted(unit.uid)
|
||||
elseif (is_formed == false) and unit.formed then
|
||||
log.warning(util.c(detail_name, " is no longer formed"))
|
||||
end
|
||||
|
||||
unit.formed = is_formed
|
||||
|
||||
@@ -16,8 +16,9 @@ local max_distance = nil
|
||||
---@class comms
|
||||
local comms = {}
|
||||
|
||||
-- protocol/data version (protocol/data independent changes tracked by util.lua version)
|
||||
comms.version = "2.4.5"
|
||||
-- protocol/data versions (protocol/data independent changes tracked by util.lua version)
|
||||
comms.version = "2.5.1"
|
||||
comms.api_version = "0.0.1"
|
||||
|
||||
---@enum PROTOCOL
|
||||
local PROTOCOL = {
|
||||
@@ -64,7 +65,9 @@ local CRDN_TYPE = {
|
||||
FAC_CMD = 3, -- faility command
|
||||
UNIT_BUILDS = 4, -- build of each reactor unit (reactor + RTUs)
|
||||
UNIT_STATUSES = 5, -- state of each of the reactor units
|
||||
UNIT_CMD = 6 -- command a reactor unit
|
||||
UNIT_CMD = 6, -- command a reactor unit
|
||||
API_GET_FAC = 7, -- API: get all the facility data
|
||||
API_GET_UNITS = 8 -- API: get all the reactor unit data
|
||||
}
|
||||
|
||||
---@enum ESTABLISH_ACK
|
||||
@@ -72,7 +75,8 @@ local ESTABLISH_ACK = {
|
||||
ALLOW = 0, -- link approved
|
||||
DENY = 1, -- link denied
|
||||
COLLISION = 2, -- link denied due to existing active link
|
||||
BAD_VERSION = 3 -- link denied due to comms version mismatch
|
||||
BAD_VERSION = 3, -- link denied due to comms version mismatch
|
||||
BAD_API_VERSION = 4 -- link denied due to api version mismatch
|
||||
}
|
||||
|
||||
---@enum DEVICE_TYPE device types for establish messages
|
||||
@@ -93,7 +97,8 @@ local FAC_COMMAND = {
|
||||
START = 2, -- start automatic process control
|
||||
ACK_ALL_ALARMS = 3, -- acknowledge all alarms on all units
|
||||
SET_WASTE_MODE = 4, -- set automatic waste processing mode
|
||||
SET_PU_FB = 5 -- set plutonium fallback mode
|
||||
SET_PU_FB = 5, -- set plutonium fallback mode
|
||||
SET_SPS_LP = 6 -- set SPS at low power mode
|
||||
}
|
||||
|
||||
---@enum UNIT_COMMAND
|
||||
|
||||
@@ -29,7 +29,7 @@ local annunc = {}
|
||||
annunc.RCSFlowLow_H2O = -3.2 -- flow < -3.2 mB/s
|
||||
annunc.RCSFlowLow_NA = -2.0 -- flow < -2.0 mB/s
|
||||
annunc.CoolantLevelLow = 0.4 -- fill < 40%
|
||||
annunc.ReactorTempHigh = 1000 -- temp > 1000K
|
||||
annunc.OpTempTolerance = 5 -- high temp if >= operational temp + X
|
||||
annunc.ReactorHighDeltaT = 50 -- rate > 50K/s
|
||||
annunc.FuelLevelLow = 0.05 -- fill <= 5%
|
||||
annunc.WasteLevelHigh = 0.80 -- fill >= 80%
|
||||
@@ -66,21 +66,48 @@ constants.ALARM_LIMITS = alarms
|
||||
|
||||
--#endregion
|
||||
|
||||
--#region Supervisor Redstone Activation Thresholds
|
||||
|
||||
---@class _rs_threshold_constants
|
||||
local rs = {}
|
||||
|
||||
rs.IMATRIX_CHARGE_LOW = 0.05 -- activation threshold (less than) for F_MATRIX_LOW
|
||||
rs.IMATRIX_CHARGE_HIGH = 0.95 -- activation threshold (greater than) for F_MATRIX_HIGH
|
||||
|
||||
constants.RS_THRESHOLDS = rs
|
||||
|
||||
--#endregion
|
||||
|
||||
--#region Supervisor Constants
|
||||
|
||||
-- milliseconds until turbine flow is assumed to be stable enough to enable coolant checks
|
||||
constants.FLOW_STABILITY_DELAY_MS = 15000
|
||||
-- milliseconds until coolant flow is assumed to be stable enough to enable certain coolant checks
|
||||
constants.FLOW_STABILITY_DELAY_MS = 10000
|
||||
|
||||
-- Notes on Radiation
|
||||
-- - background radiation 0.0000001 Sv/h (99.99 nSv/h)
|
||||
-- - "green tint" radiation 0.00001 Sv/h (10 uSv/h)
|
||||
-- - damaging radiation 0.00006 Sv/h (60 uSv/h)
|
||||
constants.LOW_RADIATION = 0.00001
|
||||
constants.HAZARD_RADIATION = 0.00006
|
||||
constants.HIGH_RADIATION = 0.001
|
||||
constants.LOW_RADIATION = 0.00001
|
||||
constants.HAZARD_RADIATION = 0.00006
|
||||
constants.HIGH_RADIATION = 0.001
|
||||
constants.VERY_HIGH_RADIATION = 0.1
|
||||
constants.SEVERE_RADIATION = 8.0
|
||||
constants.EXTREME_RADIATION = 100.0
|
||||
constants.SEVERE_RADIATION = 8.0
|
||||
constants.EXTREME_RADIATION = 100.0
|
||||
|
||||
--#endregion
|
||||
|
||||
--#region Mekanism Configuration Constants
|
||||
|
||||
---@class _mek_constants
|
||||
local mek = {}
|
||||
|
||||
mek.BASE_BOIL_TEMP = 373.15 -- mekanism: HeatUtils.BASE_BOIL_TEMP
|
||||
mek.JOULES_PER_MB = 1000000 -- mekanism: energyPerFissionFuel
|
||||
mek.TURBINE_GAS_PER_TANK = 64000 -- mekanism: turbineGasPerTank
|
||||
mek.TURBINE_DISPERSER_FLOW = 1280 -- mekanism: turbineDisperserGasFlow
|
||||
mek.TURBINE_VENT_FLOW = 32000 -- mekanism: turbineVentGasFlow
|
||||
|
||||
constants.mek = mek
|
||||
|
||||
--#endregion
|
||||
|
||||
|
||||
@@ -24,6 +24,21 @@ function crash.set_env(application, version)
|
||||
ver = version
|
||||
end
|
||||
|
||||
-- log environment versions
|
||||
---@param log_msg function log function to use
|
||||
local function log_versions(log_msg)
|
||||
log_msg(util.c("RUNTIME: ", _HOST))
|
||||
log_msg(util.c("LUA VERSION: ", _VERSION))
|
||||
log_msg(util.c("APPLICATION: ", app))
|
||||
log_msg(util.c("FIRMWARE VERSION: ", ver))
|
||||
log_msg(util.c("COMMS VERSION: ", comms.version))
|
||||
if has_graphics then log_msg(util.c("GRAPHICS VERSION: ", core.version)) end
|
||||
if has_lockbox then log_msg(util.c("LOCKBOX VERSION: ", lockbox.version)) end
|
||||
end
|
||||
|
||||
-- when running with debug logs, log the useful information that the crash handler knows
|
||||
function crash.dbg_log_env() log_versions(log.debug) end
|
||||
|
||||
-- handle a crash error
|
||||
---@param error string error message
|
||||
function crash.handler(error)
|
||||
@@ -31,13 +46,7 @@ function crash.handler(error)
|
||||
log.info("=====> FATAL SOFTWARE FAULT <=====")
|
||||
log.fatal(error)
|
||||
log.info("----------------------------------")
|
||||
log.info(util.c("RUNTIME: ", _HOST))
|
||||
log.info(util.c("LUA VERSION: ", _VERSION))
|
||||
log.info(util.c("APPLICATION: ", app))
|
||||
log.info(util.c("FIRMWARE VERSION: ", ver))
|
||||
log.info(util.c("COMMS VERSION: ", comms.version))
|
||||
if has_graphics then log.info(util.c("GRAPHICS VERSION: ", core.version)) end
|
||||
if has_lockbox then log.info(util.c("LOCKBOX VERSION: ", lockbox.version)) end
|
||||
log_versions(log.info)
|
||||
log.info("----------------------------------")
|
||||
log.info(debug.traceback("--- begin debug trace ---", 1))
|
||||
log.info("--- end debug trace ---")
|
||||
|
||||
@@ -51,11 +51,13 @@ local function peri_init(iface)
|
||||
self.device = peripheral.wrap(iface)
|
||||
end
|
||||
|
||||
-- initialization process (re-map)
|
||||
|
||||
for key, func in pairs(self.device) do
|
||||
self.fault_counts[key] = 0
|
||||
self.device[key] = function (...)
|
||||
-- create a protected version of a peripheral function call
|
||||
---@nodiscard
|
||||
---@param key string function name
|
||||
---@param func function function
|
||||
---@return function method protected version of the function
|
||||
local function protect_peri_function(key, func)
|
||||
return function (...)
|
||||
local return_table = table.pack(pcall(func, ...))
|
||||
|
||||
local status = return_table[1]
|
||||
@@ -85,20 +87,24 @@ local function peri_init(iface)
|
||||
count_str = " [" .. self.fault_counts[key] .. " total faults]"
|
||||
end
|
||||
|
||||
log.error(util.c("PPM: protected ", key, "() -> ", result, count_str))
|
||||
log.error(util.c("PPM: [@", iface, "] protected ", key, "() -> ", result, count_str))
|
||||
end
|
||||
|
||||
self.fault_counts[key] = self.fault_counts[key] + 1
|
||||
|
||||
if result == "Terminated" then
|
||||
ppm_sys.terminate = true
|
||||
end
|
||||
if result == "Terminated" then ppm_sys.terminate = true end
|
||||
|
||||
return ACCESS_FAULT
|
||||
return ACCESS_FAULT, result
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- initialization process (re-map)
|
||||
for key, func in pairs(self.device) do
|
||||
self.fault_counts[key] = 0
|
||||
self.device[key] = protect_peri_function(key, func)
|
||||
end
|
||||
|
||||
-- fault management & monitoring functions
|
||||
|
||||
local function clear_fault() self.faulted = false end
|
||||
@@ -131,12 +137,23 @@ local function peri_init(iface)
|
||||
|
||||
local mt = {
|
||||
__index = function (_, key)
|
||||
-- try to find the function in case it was added (multiblock formed)
|
||||
local funcs = peripheral.wrap(iface)
|
||||
if (type(funcs) == "table") and (type(funcs[key]) == "function") then
|
||||
-- add this function then return it
|
||||
self.fault_counts[key] = 0
|
||||
self.device[key] = protect_peri_function(key, funcs[key])
|
||||
|
||||
log.info(util.c("PPM: [@", iface, "] initialized previously undefined field ", key, "()"))
|
||||
|
||||
return self.device[key]
|
||||
end
|
||||
|
||||
-- function still missing, return an undefined function handler
|
||||
-- note: code should avoid storing functions for multiblocks and instead try to index them again
|
||||
return (function ()
|
||||
-- this will continuously be counting calls here as faults
|
||||
-- unlike other functions, faults here can't be cleared as it is just not defined
|
||||
if self.fault_counts[key] == nil then
|
||||
self.fault_counts[key] = 0
|
||||
end
|
||||
if self.fault_counts[key] == nil then self.fault_counts[key] = 0 end
|
||||
|
||||
-- function failed
|
||||
self.faulted = true
|
||||
@@ -151,12 +168,12 @@ local function peri_init(iface)
|
||||
count_str = " [" .. self.fault_counts[key] .. " total calls]"
|
||||
end
|
||||
|
||||
log.error(util.c("PPM: caught undefined function ", key, "()", count_str))
|
||||
log.error(util.c("PPM: [@", iface, "] caught undefined function ", key, "()", count_str))
|
||||
end
|
||||
|
||||
self.fault_counts[key] = self.fault_counts[key] + 1
|
||||
|
||||
return UNDEFINED_FIELD
|
||||
return ACCESS_FAULT, UNDEFINED_FIELD
|
||||
end)
|
||||
end
|
||||
}
|
||||
|
||||
@@ -52,6 +52,8 @@ local IO_PORT = {
|
||||
-- facility
|
||||
F_ALARM = 7, -- active high, facility-wide alarm (any high priority unit alarm)
|
||||
F_ALARM_ANY = 8, -- active high, any alarm regardless of priority
|
||||
F_MATRIX_LOW = 27, -- active high, induction matrix charge low
|
||||
F_MATRIX_HIGH = 28, -- active high, induction matrix charge high
|
||||
|
||||
-- waste
|
||||
WASTE_PU = 9, -- active low, waste -> plutonium -> pellets route
|
||||
@@ -75,17 +77,27 @@ local IO_PORT = {
|
||||
|
||||
-- unit outputs
|
||||
U_ALARM = 25, -- active high, unit alarm
|
||||
U_EMER_COOL = 26 -- active low, emergency coolant control
|
||||
U_EMER_COOL = 26, -- active low, emergency coolant control
|
||||
|
||||
-- analog outputs --
|
||||
|
||||
-- facility
|
||||
F_MATRIX_CHG = 29 -- analog charge level of the induction matrix
|
||||
}
|
||||
|
||||
rsio.IO_LVL = IO_LVL
|
||||
rsio.IO_DIR = IO_DIR
|
||||
rsio.IO_MODE = IO_MODE
|
||||
rsio.IO = IO_PORT
|
||||
rsio.NUM_PORTS = IO_PORT.U_EMER_COOL
|
||||
|
||||
rsio.NUM_PORTS = 29
|
||||
rsio.NUM_DIG_PORTS = 28
|
||||
rsio.NUM_ANA_PORTS = 1
|
||||
|
||||
-- self checks
|
||||
|
||||
assert(rsio.NUM_PORTS == (rsio.NUM_DIG_PORTS + rsio.NUM_ANA_PORTS), "port counts inconsistent")
|
||||
|
||||
local dup_chk = {}
|
||||
for _, v in pairs(IO_PORT) do
|
||||
assert(dup_chk[v] ~= true, "duplicate in port list")
|
||||
@@ -96,64 +108,45 @@ assert(#dup_chk == rsio.NUM_PORTS, "port list malformed")
|
||||
|
||||
--#endregion
|
||||
|
||||
--#region Utility Functions
|
||||
--#region Utility Functions and Attribute Tables
|
||||
|
||||
local PORT_NAMES = {
|
||||
"F_SCRAM",
|
||||
"F_ACK",
|
||||
"R_SCRAM",
|
||||
"R_RESET",
|
||||
"R_ENABLE",
|
||||
"U_ACK",
|
||||
"F_ALARM",
|
||||
"F_ALARM_ANY",
|
||||
"WASTE_PU",
|
||||
"WASTE_PO",
|
||||
"WASTE_POPL",
|
||||
"WASTE_AM",
|
||||
"R_ACTIVE",
|
||||
"R_AUTO_CTRL",
|
||||
"R_SCRAMMED",
|
||||
"R_AUTO_SCRAM",
|
||||
"R_HIGH_DMG",
|
||||
"R_HIGH_TEMP",
|
||||
"R_LOW_COOLANT",
|
||||
"R_EXCESS_HC",
|
||||
"R_EXCESS_WS",
|
||||
"R_INSUFF_FUEL",
|
||||
"R_PLC_FAULT",
|
||||
"R_PLC_TIMEOUT",
|
||||
"U_ALARM",
|
||||
"U_EMER_COOL"
|
||||
}
|
||||
local IO = IO_PORT
|
||||
|
||||
-- list of all port names
|
||||
local PORT_NAMES = {}
|
||||
for k, v in pairs(IO) do PORT_NAMES[v] = k end
|
||||
|
||||
-- list of all port I/O modes
|
||||
local MODES = {
|
||||
IO_MODE.DIGITAL_IN, -- F_SCRAM
|
||||
IO_MODE.DIGITAL_IN, -- F_ACK
|
||||
IO_MODE.DIGITAL_IN, -- R_SCRAM
|
||||
IO_MODE.DIGITAL_IN, -- R_RESET
|
||||
IO_MODE.DIGITAL_IN, -- R_ENABLE
|
||||
IO_MODE.DIGITAL_IN, -- U_ACK
|
||||
IO_MODE.DIGITAL_OUT, -- F_ALARM
|
||||
IO_MODE.DIGITAL_OUT, -- F_ALARM_ANY
|
||||
IO_MODE.DIGITAL_OUT, -- WASTE_PU
|
||||
IO_MODE.DIGITAL_OUT, -- WASTE_PO
|
||||
IO_MODE.DIGITAL_OUT, -- WASTE_POPL
|
||||
IO_MODE.DIGITAL_OUT, -- WASTE_AM
|
||||
IO_MODE.DIGITAL_OUT, -- R_ACTIVE
|
||||
IO_MODE.DIGITAL_OUT, -- R_AUTO_CTRL
|
||||
IO_MODE.DIGITAL_OUT, -- R_SCRAMMED
|
||||
IO_MODE.DIGITAL_OUT, -- R_AUTO_SCRAM
|
||||
IO_MODE.DIGITAL_OUT, -- R_HIGH_DMG
|
||||
IO_MODE.DIGITAL_OUT, -- R_HIGH_TEMP
|
||||
IO_MODE.DIGITAL_OUT, -- R_LOW_COOLANT
|
||||
IO_MODE.DIGITAL_OUT, -- R_EXCESS_HC
|
||||
IO_MODE.DIGITAL_OUT, -- R_EXCESS_WS
|
||||
IO_MODE.DIGITAL_OUT, -- R_INSUFF_FUEL
|
||||
IO_MODE.DIGITAL_OUT, -- R_PLC_FAULT
|
||||
IO_MODE.DIGITAL_OUT, -- R_PLC_TIMEOUT
|
||||
IO_MODE.DIGITAL_OUT, -- U_ALARM
|
||||
IO_MODE.DIGITAL_OUT -- U_EMER_COOL
|
||||
[IO.F_SCRAM] = IO_MODE.DIGITAL_IN,
|
||||
[IO.F_ACK] = IO_MODE.DIGITAL_IN,
|
||||
[IO.R_SCRAM] = IO_MODE.DIGITAL_IN,
|
||||
[IO.R_RESET] = IO_MODE.DIGITAL_IN,
|
||||
[IO.R_ENABLE] = IO_MODE.DIGITAL_IN,
|
||||
[IO.U_ACK] = IO_MODE.DIGITAL_IN,
|
||||
[IO.F_ALARM] = IO_MODE.DIGITAL_OUT,
|
||||
[IO.F_ALARM_ANY] = IO_MODE.DIGITAL_OUT,
|
||||
[IO.F_MATRIX_LOW] = IO_MODE.DIGITAL_OUT,
|
||||
[IO.F_MATRIX_HIGH] = IO_MODE.DIGITAL_OUT,
|
||||
[IO.WASTE_PU] = IO_MODE.DIGITAL_OUT,
|
||||
[IO.WASTE_PO] = IO_MODE.DIGITAL_OUT,
|
||||
[IO.WASTE_POPL] = IO_MODE.DIGITAL_OUT,
|
||||
[IO.WASTE_AM] = IO_MODE.DIGITAL_OUT,
|
||||
[IO.R_ACTIVE] = IO_MODE.DIGITAL_OUT,
|
||||
[IO.R_AUTO_CTRL] = IO_MODE.DIGITAL_OUT,
|
||||
[IO.R_SCRAMMED] = IO_MODE.DIGITAL_OUT,
|
||||
[IO.R_AUTO_SCRAM] = IO_MODE.DIGITAL_OUT,
|
||||
[IO.R_HIGH_DMG] = IO_MODE.DIGITAL_OUT,
|
||||
[IO.R_HIGH_TEMP] = IO_MODE.DIGITAL_OUT,
|
||||
[IO.R_LOW_COOLANT] = IO_MODE.DIGITAL_OUT,
|
||||
[IO.R_EXCESS_HC] = IO_MODE.DIGITAL_OUT,
|
||||
[IO.R_EXCESS_WS] = IO_MODE.DIGITAL_OUT,
|
||||
[IO.R_INSUFF_FUEL] = IO_MODE.DIGITAL_OUT,
|
||||
[IO.R_PLC_FAULT] = IO_MODE.DIGITAL_OUT,
|
||||
[IO.R_PLC_TIMEOUT] = IO_MODE.DIGITAL_OUT,
|
||||
[IO.U_ALARM] = IO_MODE.DIGITAL_OUT,
|
||||
[IO.U_EMER_COOL] = IO_MODE.DIGITAL_OUT,
|
||||
[IO.F_MATRIX_CHG] = IO_MODE.ANALOG_OUT
|
||||
}
|
||||
|
||||
assert(rsio.NUM_PORTS == #PORT_NAMES, "port names length incorrect")
|
||||
@@ -179,74 +172,51 @@ local function _O_ACTIVE_LOW(active) if active then return IO_LVL.LOW else retur
|
||||
|
||||
-- I/O mappings to I/O function and I/O mode
|
||||
local RS_DIO_MAP = {
|
||||
-- F_SCRAM
|
||||
{ _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.IN },
|
||||
-- F_ACK
|
||||
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.IN },
|
||||
[IO.F_SCRAM] = { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.IN },
|
||||
[IO.F_ACK] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.IN },
|
||||
|
||||
-- R_SCRAM
|
||||
{ _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.IN },
|
||||
-- R_RESET
|
||||
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.IN },
|
||||
-- R_ENABLE
|
||||
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.IN },
|
||||
[IO.R_SCRAM] = { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.IN },
|
||||
[IO.R_RESET] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.IN },
|
||||
[IO.R_ENABLE] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.IN },
|
||||
|
||||
-- U_ACK
|
||||
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.IN },
|
||||
[IO.U_ACK] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.IN },
|
||||
|
||||
-- F_ALARM
|
||||
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
||||
-- F_ALARM_ANY
|
||||
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
||||
[IO.F_ALARM] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
||||
[IO.F_ALARM_ANY] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
||||
[IO.F_MATRIX_LOW] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
||||
[IO.F_MATRIX_HIGH] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
||||
|
||||
-- WASTE_PU
|
||||
{ _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT },
|
||||
-- WASTE_PO
|
||||
{ _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT },
|
||||
-- WASTE_POPL
|
||||
{ _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT },
|
||||
-- WASTE_AM
|
||||
{ _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT },
|
||||
[IO.WASTE_PU] = { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT },
|
||||
[IO.WASTE_PO] = { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT },
|
||||
[IO.WASTE_POPL] = { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT },
|
||||
[IO.WASTE_AM] = { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT },
|
||||
|
||||
-- R_ACTIVE
|
||||
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
||||
-- R_AUTO_CTRL
|
||||
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
||||
-- R_SCRAMMED
|
||||
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
||||
-- R_AUTO_SCRAM
|
||||
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
||||
-- R_HIGH_DMG
|
||||
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
||||
-- R_HIGH_TEMP
|
||||
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
||||
-- R_LOW_COOLANT
|
||||
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
||||
-- R_EXCESS_HC
|
||||
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
||||
-- R_EXCESS_WS
|
||||
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
||||
-- R_INSUFF_FUEL
|
||||
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
||||
-- R_PLC_FAULT
|
||||
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
||||
-- R_PLC_TIMEOUT
|
||||
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
||||
[IO.R_ACTIVE] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
||||
[IO.R_AUTO_CTRL] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
||||
[IO.R_SCRAMMED] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
||||
[IO.R_AUTO_SCRAM] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
||||
[IO.R_HIGH_DMG] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
||||
[IO.R_HIGH_TEMP] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
||||
[IO.R_LOW_COOLANT] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
||||
[IO.R_EXCESS_HC] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
||||
[IO.R_EXCESS_WS] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
||||
[IO.R_INSUFF_FUEL] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
||||
[IO.R_PLC_FAULT] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
||||
[IO.R_PLC_TIMEOUT] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
||||
|
||||
-- U_ALARM
|
||||
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
||||
-- U_EMER_COOL
|
||||
{ _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT }
|
||||
[IO.U_ALARM] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
|
||||
[IO.U_EMER_COOL] = { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT }
|
||||
}
|
||||
|
||||
assert(rsio.NUM_PORTS == #RS_DIO_MAP, "RS_DIO_MAP length incorrect")
|
||||
assert(rsio.NUM_DIG_PORTS == #RS_DIO_MAP, "RS_DIO_MAP length incorrect")
|
||||
|
||||
-- get the I/O direction of a port
|
||||
---@nodiscard
|
||||
---@param port IO_PORT
|
||||
---@return IO_DIR
|
||||
function rsio.get_io_dir(port)
|
||||
if rsio.is_valid_port(port) then return RS_DIO_MAP[port].mode
|
||||
if rsio.is_valid_port(port) then
|
||||
return util.trinary(MODES[port] == IO_MODE.DIGITAL_OUT or MODES[port] == IO_MODE.ANALOG_OUT, IO_DIR.OUT, IO_DIR.IN)
|
||||
else return IO_DIR.IN end
|
||||
end
|
||||
|
||||
@@ -310,6 +280,13 @@ end
|
||||
|
||||
--#region Digital I/O
|
||||
|
||||
-- check if a port is digital
|
||||
---@nodiscard
|
||||
---@param port IO_PORT
|
||||
function rsio.is_digital(port)
|
||||
return rsio.is_valid_port(port) and (MODES[port] == IO_MODE.DIGITAL_IN or MODES[port] == IO_MODE.DIGITAL_OUT)
|
||||
end
|
||||
|
||||
-- get digital I/O level reading from a redstone boolean input value
|
||||
---@nodiscard
|
||||
---@param rs_value boolean raw value from redstone
|
||||
@@ -330,7 +307,7 @@ function rsio.digital_write(level) return level == IO_LVL.HIGH end
|
||||
---@param active boolean state to convert to logic level
|
||||
---@return IO_LVL|false
|
||||
function rsio.digital_write_active(port, active)
|
||||
if (not util.is_int(port)) or (port < IO_PORT.F_ALARM) or (port > IO_PORT.U_EMER_COOL) then
|
||||
if not rsio.is_digital(port) then
|
||||
return false
|
||||
else
|
||||
return RS_DIO_MAP[port]._out(active)
|
||||
@@ -343,9 +320,7 @@ end
|
||||
---@param level IO_LVL logic level
|
||||
---@return boolean|nil state true for active, false for inactive, or nil if invalid port or level provided
|
||||
function rsio.digital_is_active(port, level)
|
||||
if not util.is_int(port) then
|
||||
return nil
|
||||
elseif level == IO_LVL.FLOATING or level == IO_LVL.DISCONNECT then
|
||||
if (not rsio.is_digital(port)) or level == IO_LVL.FLOATING or level == IO_LVL.DISCONNECT then
|
||||
return nil
|
||||
else
|
||||
return RS_DIO_MAP[port]._in(level)
|
||||
@@ -356,6 +331,13 @@ end
|
||||
|
||||
--#region Analog I/O
|
||||
|
||||
-- check if a port is analog
|
||||
---@nodiscard
|
||||
---@param port IO_PORT
|
||||
function rsio.is_analog(port)
|
||||
return rsio.is_valid_port(port) and (MODES[port] == IO_MODE.ANALOG_IN or MODES[port] == IO_MODE.ANALOG_OUT)
|
||||
end
|
||||
|
||||
-- read an analog value scaled from min to max
|
||||
---@nodiscard
|
||||
---@param rs_value number redstone reading (0 to 15)
|
||||
@@ -372,7 +354,7 @@ end
|
||||
---@param value number value to write (from min to max range)
|
||||
---@param min number minimum of range
|
||||
---@param max number maximum of range
|
||||
---@return number rs_value scaled redstone reading (0 to 15)
|
||||
---@return integer rs_value scaled redstone reading (0 to 15)
|
||||
function rsio.analog_write(value, min, max)
|
||||
local scaled_value = (value - min) / (max - min)
|
||||
return math.floor(scaled_value * 15)
|
||||
|
||||
@@ -22,7 +22,7 @@ local t_pack = table.pack
|
||||
local util = {}
|
||||
|
||||
-- scada-common version
|
||||
util.version = "1.1.19"
|
||||
util.version = "1.3.0"
|
||||
|
||||
util.TICK_TIME_S = 0.05
|
||||
util.TICK_TIME_MS = 50
|
||||
@@ -181,8 +181,7 @@ function util.round(x) return math.floor(x + 0.5) end
|
||||
-- get a new moving average object
|
||||
---@nodiscard
|
||||
---@param length integer history length
|
||||
---@param default number value to fill history with for first call to compute()
|
||||
function util.mov_avg(length, default)
|
||||
function util.mov_avg(length)
|
||||
local data = {}
|
||||
local index = 1
|
||||
local last_t = 0 ---@type number|nil
|
||||
@@ -190,11 +189,15 @@ function util.mov_avg(length, default)
|
||||
---@class moving_average
|
||||
local public = {}
|
||||
|
||||
-- reset all to a given value
|
||||
---@param x number value
|
||||
-- reset all to a given value, or clear all data if no value is given
|
||||
---@param x number? value
|
||||
function public.reset(x)
|
||||
index = 1
|
||||
data = {}
|
||||
for _ = 1, length do t_insert(data, x) end
|
||||
|
||||
if x then
|
||||
for _ = 1, length do t_insert(data, x) end
|
||||
end
|
||||
end
|
||||
|
||||
-- record a new value
|
||||
@@ -214,12 +217,15 @@ function util.mov_avg(length, default)
|
||||
---@nodiscard
|
||||
---@return number average
|
||||
function public.compute()
|
||||
local sum = 0
|
||||
for i = 1, length do sum = sum + data[i] end
|
||||
return sum / length
|
||||
end
|
||||
if #data == 0 then return 0 end
|
||||
|
||||
public.reset(default)
|
||||
local sum = 0
|
||||
for i = 1, #data do
|
||||
sum = sum + data[i]
|
||||
end
|
||||
|
||||
return sum / #data
|
||||
end
|
||||
|
||||
return public
|
||||
end
|
||||
|
||||
@@ -7,6 +7,7 @@ local tcd = require("scada-common.tcd")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local core = require("graphics.core")
|
||||
local themes = require("graphics.themes")
|
||||
|
||||
local DisplayBox = require("graphics.elements.displaybox")
|
||||
local Div = require("graphics.elements.div")
|
||||
@@ -22,6 +23,8 @@ 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 println = util.println
|
||||
local tri = util.trinary
|
||||
|
||||
@@ -32,7 +35,10 @@ local CENTER = core.ALIGN.CENTER
|
||||
local RIGHT = core.ALIGN.RIGHT
|
||||
|
||||
-- changes to the config data/format to let the user know
|
||||
local changes = {}
|
||||
local changes = {
|
||||
{ "v1.2.12", { "Added front panel UI theme", "Added color accessibility modes" } },
|
||||
{ "v1.3.2", { "Added standard with black off state color mode", "Added blue indicator color modes" } }
|
||||
}
|
||||
|
||||
---@class svr_configurator
|
||||
local configurator = {}
|
||||
@@ -42,34 +48,25 @@ local style = {}
|
||||
style.root = cpair(colors.black, colors.lightGray)
|
||||
style.header = cpair(colors.white, colors.gray)
|
||||
|
||||
style.colors = {
|
||||
{ c = colors.red, hex = 0xdf4949 },
|
||||
{ c = colors.orange, hex = 0xffb659 },
|
||||
{ c = colors.yellow, hex = 0xfffc79 },
|
||||
{ c = colors.lime, hex = 0x80ff80 },
|
||||
{ c = colors.green, hex = 0x4aee8a },
|
||||
{ c = colors.cyan, hex = 0x34bac8 },
|
||||
{ c = colors.lightBlue, hex = 0x6cc0f2 },
|
||||
{ c = colors.blue, hex = 0x0096ff },
|
||||
{ c = colors.purple, hex = 0xb156ee },
|
||||
{ c = colors.pink, hex = 0xf26ba2 },
|
||||
{ c = colors.magenta, hex = 0xf9488a },
|
||||
{ c = colors.lightGray, hex = 0xcacaca },
|
||||
{ c = colors.gray, hex = 0x575757 }
|
||||
}
|
||||
style.colors = themes.smooth_stone.colors
|
||||
|
||||
local bw_fg_bg = cpair(colors.black, colors.white)
|
||||
local g_lg_fg_bg = cpair(colors.gray, colors.lightGray)
|
||||
local nav_fg_bg = bw_fg_bg
|
||||
local btn_act_fg_bg = cpair(colors.white, colors.gray)
|
||||
|
||||
---@class _svr_cfg_tool_ctl
|
||||
local tool_ctl = {
|
||||
ask_config = false,
|
||||
has_config = false,
|
||||
viewing_config = false,
|
||||
importing_legacy = false,
|
||||
jumped_to_color = false,
|
||||
|
||||
view_cfg = nil, ---@type graphics_element
|
||||
color_cfg = nil, ---@type graphics_element
|
||||
color_next = nil, ---@type graphics_element
|
||||
color_apply = nil, ---@type graphics_element
|
||||
settings_apply = nil, ---@type graphics_element
|
||||
|
||||
gen_summary = nil, ---@type function
|
||||
@@ -94,6 +91,7 @@ local tmp_cfg = {
|
||||
CoolingConfig = {},
|
||||
FacilityTankMode = 0,
|
||||
FacilityTankDefs = {},
|
||||
ExtChargeIdling = false,
|
||||
SVR_Channel = nil, ---@type integer
|
||||
PLC_Channel = nil, ---@type integer
|
||||
RTU_Channel = nil, ---@type integer
|
||||
@@ -108,6 +106,8 @@ local tmp_cfg = {
|
||||
LogMode = 0,
|
||||
LogPath = "",
|
||||
LogDebug = false,
|
||||
FrontPanelTheme = 1,
|
||||
ColorMode = 1
|
||||
}
|
||||
|
||||
---@class svr_config
|
||||
@@ -121,6 +121,7 @@ local fields = {
|
||||
{ "CoolingConfig", "Cooling Configuration", {} },
|
||||
{ "FacilityTankMode", "Facility Tank Mode", 0 },
|
||||
{ "FacilityTankDefs", "Facility Tank Definitions", {} },
|
||||
{ "ExtChargeIdling", "Extended Charge Idling", false },
|
||||
{ "SVR_Channel", "SVR Channel", 16240 },
|
||||
{ "PLC_Channel", "PLC Channel", 16241 },
|
||||
{ "RTU_Channel", "RTU Channel", 16242 },
|
||||
@@ -134,7 +135,9 @@ local fields = {
|
||||
{ "AuthKey", "Facility Auth Key" , ""},
|
||||
{ "LogMode", "Log Mode", log.MODE.APPEND },
|
||||
{ "LogPath", "Log Path", "/log.txt" },
|
||||
{ "LogDebug","Log Debug Messages", false }
|
||||
{ "LogDebug", "Log Debug Messages", false },
|
||||
{ "FrontPanelTheme", "Front Panel Theme", themes.FP_THEME.SANDSTONE },
|
||||
{ "ColorMode", "Color Mode", themes.COLOR_MODE.STANDARD }
|
||||
}
|
||||
|
||||
-- load data from the settings file
|
||||
@@ -164,11 +167,12 @@ local function config_view(display)
|
||||
local svr_cfg = Div{parent=root_pane_div,x=1,y=1}
|
||||
local net_cfg = Div{parent=root_pane_div,x=1,y=1}
|
||||
local log_cfg = Div{parent=root_pane_div,x=1,y=1}
|
||||
local clr_cfg = 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 import_err = Div{parent=root_pane_div,x=1,y=1}
|
||||
|
||||
local main_pane = MultiPane{parent=root_pane_div,x=1,y=1,panes={main_page,svr_cfg,net_cfg,log_cfg,summary,changelog,import_err}}
|
||||
local main_pane = MultiPane{parent=root_pane_div,x=1,y=1,panes={main_page,svr_cfg,net_cfg,log_cfg,clr_cfg,summary,changelog,import_err}}
|
||||
|
||||
-- Main Page
|
||||
|
||||
@@ -185,7 +189,7 @@ local function config_view(display)
|
||||
tool_ctl.viewing_config = true
|
||||
tool_ctl.gen_summary(settings_cfg)
|
||||
tool_ctl.settings_apply.hide(true)
|
||||
main_pane.set_value(5)
|
||||
main_pane.set_value(6)
|
||||
end
|
||||
|
||||
if fs.exists("/supervisor/config.lua") then
|
||||
@@ -196,10 +200,21 @@ local function config_view(display)
|
||||
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)}
|
||||
|
||||
if not tool_ctl.has_config then tool_ctl.view_cfg.disable() end
|
||||
local function jump_color()
|
||||
tool_ctl.jumped_to_color = true
|
||||
tool_ctl.color_next.hide(true)
|
||||
tool_ctl.color_apply.show()
|
||||
main_pane.set_value(5)
|
||||
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=39,y=17,min_width=12,text="Change Log",callback=function()main_pane.set_value(6)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
tool_ctl.color_cfg = PushButton{parent=main_page,x=23,y=17,min_width=15,text="Color Options",callback=jump_color,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)}
|
||||
PushButton{parent=main_page,x=39,y=17,min_width=12,text="Change Log",callback=function()main_pane.set_value(7)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
if not tool_ctl.has_config then
|
||||
tool_ctl.view_cfg.disable()
|
||||
tool_ctl.color_cfg.disable()
|
||||
end
|
||||
|
||||
--#region Facility
|
||||
|
||||
@@ -209,8 +224,9 @@ local function config_view(display)
|
||||
local svr_c_4 = Div{parent=svr_cfg,x=2,y=4,width=49}
|
||||
local svr_c_5 = Div{parent=svr_cfg,x=2,y=4,width=49}
|
||||
local svr_c_6 = Div{parent=svr_cfg,x=2,y=4,width=49}
|
||||
local svr_c_7 = Div{parent=svr_cfg,x=2,y=4,width=49}
|
||||
|
||||
local svr_pane = MultiPane{parent=svr_cfg,x=1,y=4,panes={svr_c_1,svr_c_2,svr_c_3,svr_c_4,svr_c_5,svr_c_6}}
|
||||
local svr_pane = MultiPane{parent=svr_cfg,x=1,y=4,panes={svr_c_1,svr_c_2,svr_c_3,svr_c_4,svr_c_5,svr_c_6,svr_c_7}}
|
||||
|
||||
TextBox{parent=svr_cfg,x=1,y=2,height=1,text=" Facility Configuration",fg_bg=cpair(colors.black,colors.yellow)}
|
||||
|
||||
@@ -316,7 +332,7 @@ local function config_view(display)
|
||||
else
|
||||
tmp_cfg.FacilityTankMode = 0
|
||||
tmp_cfg.FacilityTankDefs = {}
|
||||
main_pane.set_value(3)
|
||||
svr_pane.set_value(7)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -550,7 +566,7 @@ local function config_view(display)
|
||||
|
||||
local function submit_mode()
|
||||
tmp_cfg.FacilityTankMode = tank_mode.get_value()
|
||||
main_pane.set_value(3)
|
||||
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}
|
||||
@@ -564,6 +580,23 @@ local function config_view(display)
|
||||
|
||||
PushButton{parent=svr_c_6,x=1,y=14,text="\x1b Back",callback=function()svr_pane.set_value(5)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
TextBox{parent=svr_c_7,height=6,text="Charge control provides automatic control to maintain an induction matrix charge level. In order to have smoother control, reactors that were activated will be held on at 0.01 mB/t for a short period before allowing them to turn off. This minimizes overshooting the charge target."}
|
||||
TextBox{parent=svr_c_7,y=8,height=3,text="You can extend this to a full minute to minimize reactors flickering on/off, but there may be more overshoot of the target."}
|
||||
|
||||
local ext_idling = CheckBox{parent=svr_c_7,x=1,y=12,label="Enable Extended Idling",default=ini_cfg.ExtChargeIdling,box_fg_bg=cpair(colors.yellow,colors.black)}
|
||||
|
||||
local function back_from_idling()
|
||||
svr_pane.set_value(util.trinary(tmp_cfg.FacilityTankMode == 0, 3, 5))
|
||||
end
|
||||
|
||||
local function submit_idling()
|
||||
tmp_cfg.ExtChargeIdling = ext_idling.get_value()
|
||||
main_pane.set_value(3)
|
||||
end
|
||||
|
||||
PushButton{parent=svr_c_7,x=1,y=14,text="\x1b Back",callback=back_from_idling,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=svr_c_7,x=44,y=14,text="Next \x1a",callback=submit_idling,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
--#endregion
|
||||
|
||||
--#region Network
|
||||
@@ -721,10 +754,8 @@ local function config_view(display)
|
||||
tmp_cfg.LogMode = mode.get_value() - 1
|
||||
tmp_cfg.LogPath = path.get_value()
|
||||
tmp_cfg.LogDebug = en_dbg.get_value()
|
||||
tool_ctl.gen_summary(tmp_cfg)
|
||||
tool_ctl.viewing_config = false
|
||||
tool_ctl.importing_legacy = false
|
||||
tool_ctl.settings_apply.show()
|
||||
tool_ctl.color_apply.hide(true)
|
||||
tool_ctl.color_next.show()
|
||||
main_pane.set_value(5)
|
||||
else path_err.show() end
|
||||
end
|
||||
@@ -734,6 +765,116 @@ local function config_view(display)
|
||||
|
||||
--#endregion
|
||||
|
||||
--#region Color Options
|
||||
|
||||
local clr_c_1 = Div{parent=clr_cfg,x=2,y=4,width=49}
|
||||
local clr_c_2 = Div{parent=clr_cfg,x=2,y=4,width=49}
|
||||
local clr_c_3 = Div{parent=clr_cfg,x=2,y=4,width=49}
|
||||
local clr_c_4 = Div{parent=clr_cfg,x=2,y=4,width=49}
|
||||
|
||||
local clr_pane = MultiPane{parent=clr_cfg,x=1,y=4,panes={clr_c_1,clr_c_2,clr_c_3,clr_c_4}}
|
||||
|
||||
TextBox{parent=clr_cfg,x=1,y=2,height=1,text=" Color Configuration",fg_bg=cpair(colors.black,colors.magenta)}
|
||||
|
||||
TextBox{parent=clr_c_1,x=1,y=1,height=2,text="Here you can select the color theme for the front panel."}
|
||||
TextBox{parent=clr_c_1,x=1,y=4,height=2,text="Click 'Accessibility' below to access colorblind assistive options.",fg_bg=g_lg_fg_bg}
|
||||
|
||||
TextBox{parent=clr_c_1,x=1,y=7,height=1,text="Front Panel Theme"}
|
||||
local fp_theme = RadioButton{parent=clr_c_1,x=1,y=8,default=ini_cfg.FrontPanelTheme,options=themes.FP_THEME_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.magenta}
|
||||
|
||||
TextBox{parent=clr_c_2,x=1,y=1,height=6,text="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,height=1,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,height=1,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("/supervisor.settings") then
|
||||
load_settings(settings_cfg, true)
|
||||
load_settings(ini_cfg)
|
||||
clr_pane.set_value(3)
|
||||
else
|
||||
clr_pane.set_value(4)
|
||||
end
|
||||
else
|
||||
tool_ctl.gen_summary(tmp_cfg)
|
||||
tool_ctl.viewing_config = false
|
||||
tool_ctl.importing_legacy = false
|
||||
tool_ctl.settings_apply.show()
|
||||
main_pane.set_value(6)
|
||||
end
|
||||
end
|
||||
|
||||
PushButton{parent=clr_c_1,x=1,y=14,text="\x1b Back",callback=back_from_colors,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=clr_c_1,x=8,y=14,min_width=15,text="Accessibility",callback=show_access,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
tool_ctl.color_next = PushButton{parent=clr_c_1,x=44,y=14,text="Next \x1a",callback=submit_colors,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
tool_ctl.color_apply = PushButton{parent=clr_c_1,x=43,y=14,min_width=7,text="Apply",callback=submit_colors,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
tool_ctl.color_apply.hide(true)
|
||||
|
||||
local function c_go_home()
|
||||
main_pane.set_value(1)
|
||||
clr_pane.set_value(1)
|
||||
end
|
||||
|
||||
TextBox{parent=clr_c_3,x=1,y=1,height=1,text="Settings saved!"}
|
||||
PushButton{parent=clr_c_3,x=1,y=14,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray)}
|
||||
PushButton{parent=clr_c_3,x=44,y=14,min_width=6,text="Home",callback=c_go_home,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
TextBox{parent=clr_c_4,x=1,y=1,height=5,text="Failed to save the settings file.\n\nThere may not be enough space for the modification or server file permissions may be denying writes."}
|
||||
PushButton{parent=clr_c_4,x=1,y=14,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray)}
|
||||
PushButton{parent=clr_c_4,x=44,y=14,min_width=6,text="Home",callback=c_go_home,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
--#endregion
|
||||
|
||||
--#region Summary and Saving
|
||||
|
||||
local sum_c_1 = Div{parent=summary,x=2,y=4,width=49}
|
||||
@@ -754,7 +895,7 @@ local function config_view(display)
|
||||
tool_ctl.importing_legacy = false
|
||||
tool_ctl.settings_apply.show()
|
||||
else
|
||||
main_pane.set_value(4)
|
||||
main_pane.set_value(5)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -787,6 +928,8 @@ local function config_view(display)
|
||||
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)
|
||||
|
||||
for i = 1, #ini_cfg.CoolingConfig do
|
||||
local cfg, elems = ini_cfg.CoolingConfig[i], tool_ctl.cooling_elems[i]
|
||||
@@ -824,6 +967,7 @@ local function config_view(display)
|
||||
main_pane.set_value(1)
|
||||
svr_pane.set_value(1)
|
||||
net_pane.set_value(1)
|
||||
clr_pane.set_value(1)
|
||||
sum_pane.set_value(1)
|
||||
end
|
||||
|
||||
@@ -887,7 +1031,7 @@ local function config_view(display)
|
||||
|
||||
if config.REACTOR_COOLING == nil or tmp_cfg.UnitCount ~= #config.REACTOR_COOLING then
|
||||
import_err_msg.set_value("Cooling configuration table length must match the number of units.")
|
||||
main_pane.set_value(7)
|
||||
main_pane.set_value(8)
|
||||
return
|
||||
end
|
||||
|
||||
@@ -896,7 +1040,7 @@ local function config_view(display)
|
||||
|
||||
if type(cfg) ~= "table" then
|
||||
import_err_msg.set_value("Cooling configuration for unit " .. i .. " must be a table.")
|
||||
main_pane.set_value(7)
|
||||
main_pane.set_value(8)
|
||||
return
|
||||
end
|
||||
|
||||
@@ -907,14 +1051,14 @@ local function config_view(display)
|
||||
|
||||
if not (util.is_int(tmp_cfg.FacilityTankMode) and tmp_cfg.FacilityTankMode >= 0 and tmp_cfg.FacilityTankMode <= 8) then
|
||||
import_err_msg.set_value("Invalid tank mode present in config. FAC_TANK_MODE must be a number 0 through 8.")
|
||||
main_pane.set_value(7)
|
||||
main_pane.set_value(8)
|
||||
return
|
||||
end
|
||||
|
||||
if config.FAC_TANK_MODE > 0 then
|
||||
if config.FAC_TANK_DEFS == nil or tmp_cfg.UnitCount ~= #config.FAC_TANK_DEFS then
|
||||
import_err_msg.set_value("Facility tank definitions table length must match the number of units when using facility tanks.")
|
||||
main_pane.set_value(7)
|
||||
main_pane.set_value(8)
|
||||
return
|
||||
end
|
||||
|
||||
@@ -945,7 +1089,7 @@ local function config_view(display)
|
||||
|
||||
tool_ctl.gen_summary(tmp_cfg)
|
||||
sum_pane.set_value(1)
|
||||
main_pane.set_value(5)
|
||||
main_pane.set_value(6)
|
||||
tool_ctl.importing_legacy = true
|
||||
end
|
||||
|
||||
@@ -976,6 +1120,10 @@ local function config_view(display)
|
||||
|
||||
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] == "FrontPanelTheme" then
|
||||
val = util.strval(themes.fp_theme_name(raw))
|
||||
elseif f[1] == "ColorMode" then
|
||||
val = util.strval(themes.color_mode_name(raw))
|
||||
elseif f[1] == "CoolingConfig" and type(cfg.CoolingConfig) == "table" then
|
||||
val = ""
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ function databus.tx_plc_rtt(reactor_id, rtt)
|
||||
elseif rtt > WARN_RTT then
|
||||
databus.ps.publish("plc_" .. reactor_id .. "_rtt_color", colors.yellow_hc)
|
||||
else
|
||||
databus.ps.publish("plc_" .. reactor_id .. "_rtt_color", colors.green)
|
||||
databus.ps.publish("plc_" .. reactor_id .. "_rtt_color", colors.green_hc)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -95,7 +95,7 @@ function databus.tx_rtu_rtt(session_id, rtt)
|
||||
elseif rtt > WARN_RTT then
|
||||
databus.ps.publish("rtu_" .. session_id .. "_rtt_color", colors.yellow_hc)
|
||||
else
|
||||
databus.ps.publish("rtu_" .. session_id .. "_rtt_color", colors.green)
|
||||
databus.ps.publish("rtu_" .. session_id .. "_rtt_color", colors.green_hc)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -134,7 +134,7 @@ function databus.tx_crd_rtt(rtt)
|
||||
elseif rtt > WARN_RTT then
|
||||
databus.ps.publish("crd_rtt_color", colors.yellow_hc)
|
||||
else
|
||||
databus.ps.publish("crd_rtt_color", colors.green)
|
||||
databus.ps.publish("crd_rtt_color", colors.green_hc)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -165,7 +165,7 @@ function databus.tx_pdg_rtt(session_id, rtt)
|
||||
elseif rtt > WARN_RTT then
|
||||
databus.ps.publish("pdg_" .. session_id .. "_rtt_color", colors.yellow_hc)
|
||||
else
|
||||
databus.ps.publish("pdg_" .. session_id .. "_rtt_color", colors.green)
|
||||
databus.ps.publish("pdg_" .. session_id .. "_rtt_color", colors.green_hc)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
|
||||
local WASTE_MODE = types.WASTE_MODE
|
||||
local WASTE = types.WASTE_PRODUCT
|
||||
|
||||
local IO = rsio.IO
|
||||
local IO = rsio.IO
|
||||
|
||||
local DTV_RTU_S_DATA = qtypes.DTV_RTU_S_DATA
|
||||
|
||||
@@ -50,9 +50,9 @@ local START_STATUS = {
|
||||
BLADE_MISMATCH = 2
|
||||
}
|
||||
|
||||
local charge_Kp = 0.275
|
||||
local charge_Kp = 0.15
|
||||
local charge_Ki = 0.0
|
||||
local charge_Kd = 4.5
|
||||
local charge_Kd = 0.6
|
||||
|
||||
local rate_Kp = 2.45
|
||||
local rate_Ki = 0.4825
|
||||
@@ -63,9 +63,9 @@ local facility = {}
|
||||
|
||||
-- create a new facility management object
|
||||
---@nodiscard
|
||||
---@param num_reactors integer number of reactor units
|
||||
---@param config svr_config supervisor configuration
|
||||
---@param cooling_conf sv_cooling_conf cooling configurations of reactor units
|
||||
function facility.new(num_reactors, cooling_conf)
|
||||
function facility.new(config, cooling_conf)
|
||||
local self = {
|
||||
units = {},
|
||||
status_text = { "START UP", "initializing..." },
|
||||
@@ -120,6 +120,8 @@ function facility.new(num_reactors, cooling_conf)
|
||||
waste_product = WASTE.PLUTONIUM,
|
||||
current_waste_product = WASTE.PLUTONIUM,
|
||||
pu_fallback = false,
|
||||
sps_low_power = false,
|
||||
disabled_sps = false,
|
||||
-- alarm tones
|
||||
tone_states = {},
|
||||
test_tone_set = false,
|
||||
@@ -128,14 +130,21 @@ function facility.new(num_reactors, cooling_conf)
|
||||
test_alarm_states = {},
|
||||
-- statistics
|
||||
im_stat_init = false,
|
||||
avg_charge = util.mov_avg(3, 0.0),
|
||||
avg_inflow = util.mov_avg(6, 0.0),
|
||||
avg_outflow = util.mov_avg(6, 0.0)
|
||||
avg_charge = util.mov_avg(3), -- 3 seconds
|
||||
avg_inflow = util.mov_avg(6), -- 3 seconds
|
||||
avg_outflow = util.mov_avg(6), -- 3 seconds
|
||||
-- induction matrix charge delta stats
|
||||
avg_net = util.mov_avg(60), -- 60 seconds
|
||||
imtx_last_capacity = 0,
|
||||
imtx_last_charge = 0,
|
||||
imtx_last_charge_t = 0,
|
||||
-- track faulted induction matrix update times to reject
|
||||
imtx_faulted_times = { 0, 0, 0 }
|
||||
}
|
||||
|
||||
-- create units
|
||||
for i = 1, num_reactors do
|
||||
table.insert(self.units, unit.new(i, cooling_conf.r_cool[i].BoilerCount, cooling_conf.r_cool[i].TurbineCount))
|
||||
for i = 1, config.UnitCount do
|
||||
table.insert(self.units, unit.new(i, cooling_conf.r_cool[i].BoilerCount, cooling_conf.r_cool[i].TurbineCount, config.ExtChargeIdling))
|
||||
table.insert(self.group_map, 0)
|
||||
end
|
||||
|
||||
@@ -225,6 +234,14 @@ function facility.new(num_reactors, cooling_conf)
|
||||
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
|
||||
|
||||
-- PUBLIC FUNCTIONS --
|
||||
|
||||
---@class facility
|
||||
@@ -292,23 +309,68 @@ function facility.new(num_reactors, cooling_conf)
|
||||
|
||||
-- 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 matrix = self.induction[1] ---@type unit_session
|
||||
local db = matrix.get_db() ---@type imatrix_session_db
|
||||
|
||||
charge_update = db.tanks.last_update
|
||||
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(db.tanks.energy)
|
||||
local input = util.joules_to_fe(db.state.last_input)
|
||||
local output = util.joules_to_fe(db.state.last_output)
|
||||
|
||||
if (charge_update > 0) and (rate_update > 0) then
|
||||
if self.im_stat_init then
|
||||
self.avg_charge.record(util.joules_to_fe(db.tanks.energy), charge_update)
|
||||
self.avg_inflow.record(util.joules_to_fe(db.state.last_input), rate_update)
|
||||
self.avg_outflow.record(util.joules_to_fe(db.state.last_output), rate_update)
|
||||
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(util.joules_to_fe(db.tanks.energy))
|
||||
self.avg_inflow.reset(util.joules_to_fe(db.state.last_input))
|
||||
self.avg_outflow.reset(util.joules_to_fe(db.state.last_output))
|
||||
|
||||
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
|
||||
@@ -325,10 +387,11 @@ function facility.new(num_reactors, cooling_conf)
|
||||
|
||||
--#region
|
||||
|
||||
local avg_charge = self.avg_charge.compute()
|
||||
local avg_inflow = self.avg_inflow.compute()
|
||||
local avg_charge = self.avg_charge.compute()
|
||||
local avg_inflow = self.avg_inflow.compute()
|
||||
local avg_outflow = self.avg_outflow.compute()
|
||||
|
||||
local now = util.time_s()
|
||||
local now = os.clock()
|
||||
|
||||
local state_changed = self.mode ~= self.last_mode
|
||||
local next_mode = self.mode
|
||||
@@ -390,6 +453,7 @@ function facility.new(num_reactors, cooling_conf)
|
||||
-- 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
|
||||
@@ -460,9 +524,12 @@ function facility.new(num_reactors, cooling_conf)
|
||||
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
|
||||
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
|
||||
|
||||
@@ -475,9 +542,9 @@ function facility.new(num_reactors, cooling_conf)
|
||||
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 P = charge_Kp * error
|
||||
local I = charge_Ki * integral
|
||||
local D = charge_Kd * derivative
|
||||
|
||||
local output = P + I + D
|
||||
|
||||
@@ -486,7 +553,12 @@ function facility.new(num_reactors, cooling_conf)
|
||||
|
||||
self.saturated = output ~= out_c
|
||||
|
||||
-- log.debug(util.sprintf("CHARGE[%f] { CHRG[%f] ERR[%f] INT[%f] => OUT[%f] OUT_C[%f] <= P[%f] I[%f] D[%d] }",
|
||||
if not config.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)
|
||||
@@ -531,7 +603,7 @@ function facility.new(num_reactors, cooling_conf)
|
||||
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
|
||||
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
|
||||
|
||||
@@ -544,9 +616,9 @@ function facility.new(num_reactors, cooling_conf)
|
||||
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)
|
||||
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
|
||||
@@ -602,8 +674,7 @@ function facility.new(num_reactors, cooling_conf)
|
||||
local astatus = self.ascram_status
|
||||
|
||||
if self.induction[1] ~= nil then
|
||||
local matrix = self.induction[1] ---@type unit_session
|
||||
local db = matrix.get_db() ---@type imatrix_session_db
|
||||
local db = self.induction[1].get_db() ---@type imatrix_session_db
|
||||
|
||||
-- clear matrix disconnected
|
||||
if astatus.matrix_dc then
|
||||
@@ -756,6 +827,15 @@ function facility.new(num_reactors, cooling_conf)
|
||||
|
||||
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
|
||||
|
||||
--#endregion
|
||||
@@ -786,9 +866,25 @@ function facility.new(num_reactors, cooling_conf)
|
||||
end
|
||||
|
||||
-- update waste product
|
||||
if self.waste_product == WASTE.PLUTONIUM or (self.pu_fallback and insufficent_po_rate) then
|
||||
|
||||
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
|
||||
else self.current_waste_product = self.waste_product end
|
||||
end
|
||||
|
||||
-- make sure dynamic tanks are allowing outflow if required
|
||||
-- set all, rather than trying to determine which is for which (simpler & safer)
|
||||
@@ -936,41 +1032,41 @@ function facility.new(num_reactors, cooling_conf)
|
||||
function public.auto_stop() self.mode = PROCESS.INACTIVE end
|
||||
|
||||
-- set automatic control configuration and start the process
|
||||
---@param config coord_auto_config configuration
|
||||
---@param auto_cfg coord_auto_config configuration
|
||||
---@return table response ready state (successfully started) and current configuration (after updating)
|
||||
function public.auto_start(config)
|
||||
function public.auto_start(auto_cfg)
|
||||
local charge_scaler = 1000000 -- convert MFE to FE
|
||||
local gen_scaler = 1000 -- convert kFE to FE
|
||||
local ready = false
|
||||
|
||||
-- load up current limits
|
||||
local limits = {}
|
||||
for i = 1, num_reactors do
|
||||
for i = 1, config.UnitCount do
|
||||
local u = self.units[i] ---@type reactor_unit
|
||||
limits[i] = u.get_control_inf().lim_br100 * 100
|
||||
end
|
||||
|
||||
-- only allow changes if not running
|
||||
if self.mode == PROCESS.INACTIVE then
|
||||
if (type(config.mode) == "number") and (config.mode > PROCESS.INACTIVE) and (config.mode <= PROCESS.GEN_RATE) then
|
||||
self.mode_set = config.mode
|
||||
if (type(auto_cfg.mode) == "number") and (auto_cfg.mode > PROCESS.INACTIVE) and (auto_cfg.mode <= PROCESS.GEN_RATE) then
|
||||
self.mode_set = auto_cfg.mode
|
||||
end
|
||||
|
||||
if (type(config.burn_target) == "number") and config.burn_target >= 0.1 then
|
||||
self.burn_target = config.burn_target
|
||||
if (type(auto_cfg.burn_target) == "number") and auto_cfg.burn_target >= 0.1 then
|
||||
self.burn_target = auto_cfg.burn_target
|
||||
end
|
||||
|
||||
if (type(config.charge_target) == "number") and config.charge_target >= 0 then
|
||||
self.charge_setpoint = config.charge_target * charge_scaler
|
||||
if (type(auto_cfg.charge_target) == "number") and auto_cfg.charge_target >= 0 then
|
||||
self.charge_setpoint = auto_cfg.charge_target * charge_scaler
|
||||
end
|
||||
|
||||
if (type(config.gen_target) == "number") and config.gen_target >= 0 then
|
||||
self.gen_rate_setpoint = config.gen_target * gen_scaler
|
||||
if (type(auto_cfg.gen_target) == "number") and auto_cfg.gen_target >= 0 then
|
||||
self.gen_rate_setpoint = auto_cfg.gen_target * gen_scaler
|
||||
end
|
||||
|
||||
if (type(config.limits) == "table") and (#config.limits == num_reactors) then
|
||||
for i = 1, num_reactors do
|
||||
local limit = config.limits[i]
|
||||
if (type(auto_cfg.limits) == "table") and (#auto_cfg.limits == config.UnitCount) then
|
||||
for i = 1, config.UnitCount do
|
||||
local limit = auto_cfg.limits[i]
|
||||
|
||||
if (type(limit) == "number") and (limit >= 0.1) then
|
||||
limits[i] = limit
|
||||
@@ -1010,7 +1106,7 @@ function facility.new(num_reactors, cooling_conf)
|
||||
---@param unit_id integer unit ID
|
||||
---@param group integer group ID or 0 for independent
|
||||
function public.set_group(unit_id, group)
|
||||
if (group >= 0 and group <= 4) and (unit_id > 0 and unit_id <= num_reactors) and self.mode == PROCESS.INACTIVE then
|
||||
if (group >= 0 and group <= 4) and (unit_id > 0 and unit_id <= config.UnitCount) and self.mode == PROCESS.INACTIVE then
|
||||
-- remove from old group if previously assigned
|
||||
local old_group = self.group_map[unit_id]
|
||||
if old_group ~= 0 then
|
||||
@@ -1045,6 +1141,14 @@ function facility.new(num_reactors, cooling_conf)
|
||||
return self.pu_fallback
|
||||
end
|
||||
|
||||
-- enable/disable SPS at low power
|
||||
---@param enabled boolean requested state
|
||||
---@return boolean enabled newly set value
|
||||
function public.set_sps_low_power(enabled)
|
||||
self.sps_low_power = enabled == true
|
||||
return self.sps_low_power
|
||||
end
|
||||
|
||||
--#endregion
|
||||
|
||||
--#region Diagnostic Testing
|
||||
@@ -1149,7 +1253,8 @@ function facility.new(num_reactors, cooling_conf)
|
||||
self.status_text[2],
|
||||
self.group_map,
|
||||
self.current_waste_product,
|
||||
(self.current_waste_product == WASTE.PLUTONIUM) and (self.waste_product ~= WASTE.PLUTONIUM)
|
||||
self.pu_fallback and (self.current_waste_product == WASTE.PLUTONIUM) and (self.waste_product ~= WASTE.PLUTONIUM),
|
||||
self.disabled_sps
|
||||
}
|
||||
end
|
||||
|
||||
@@ -1165,15 +1270,21 @@ function facility.new(num_reactors, cooling_conf)
|
||||
status.power = {
|
||||
self.avg_charge.compute(),
|
||||
self.avg_inflow.compute(),
|
||||
self.avg_outflow.compute()
|
||||
self.avg_outflow.compute(),
|
||||
0
|
||||
}
|
||||
|
||||
-- status of induction matricies (including tanks)
|
||||
status.induction = {}
|
||||
for i = 1, #self.induction do
|
||||
local matrix = self.induction[i] ---@type unit_session
|
||||
local db = matrix.get_db() ---@type imatrix_session_db
|
||||
local matrix = self.induction[i] ---@type unit_session
|
||||
local db = matrix.get_db() ---@type imatrix_session_db
|
||||
|
||||
status.induction[i] = { matrix.is_faulted(), db.formed, db.state, db.tanks }
|
||||
|
||||
local fe_per_ms = self.avg_net.compute()
|
||||
local remaining = util.joules_to_fe(util.trinary(fe_per_ms >= 0, db.tanks.energy_need, db.tanks.energy))
|
||||
status.power[4] = remaining / fe_per_ms
|
||||
end
|
||||
|
||||
-- status of sps
|
||||
|
||||
@@ -17,31 +17,32 @@ local ALIGN = core.ALIGN
|
||||
|
||||
local cpair = core.cpair
|
||||
|
||||
local black_lg = style.black_lg
|
||||
local lg_white = style.lg_white
|
||||
|
||||
-- create a pocket diagnostics list entry
|
||||
---@param parent graphics_element parent
|
||||
---@param id integer PDG session ID
|
||||
local function init(parent, id)
|
||||
local s_hi_box = style.theme.highlight_box
|
||||
|
||||
local label_fg = style.fp.label_fg
|
||||
|
||||
-- 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.bw_fg_bg}
|
||||
local entry = Div{parent=root,x=2,y=1,height=3,fg_bg=style.theme.highlight_box_bright}
|
||||
|
||||
local ps_prefix = "pdg_" .. id .. "_"
|
||||
|
||||
TextBox{parent=entry,x=1,y=1,text="",width=8,height=1,fg_bg=black_lg}
|
||||
local pdg_addr = TextBox{parent=entry,x=1,y=2,text="@ C ??",alignment=ALIGN.CENTER,width=8,height=1,fg_bg=black_lg,nav_active=cpair(colors.gray,colors.black)}
|
||||
TextBox{parent=entry,x=1,y=3,text="",width=8,height=1,fg_bg=black_lg}
|
||||
TextBox{parent=entry,x=1,y=1,text="",width=8,height=1,fg_bg=s_hi_box}
|
||||
local pdg_addr = TextBox{parent=entry,x=1,y=2,text="@ C ??",alignment=ALIGN.CENTER,width=8,height=1,fg_bg=s_hi_box,nav_active=cpair(colors.gray,colors.black)}
|
||||
TextBox{parent=entry,x=1,y=3,text="",width=8,height=1,fg_bg=s_hi_box}
|
||||
pdg_addr.register(databus.ps, ps_prefix .. "addr", pdg_addr.set_value)
|
||||
|
||||
TextBox{parent=entry,x=10,y=2,text="FW:",width=3,height=1}
|
||||
local pdg_fw_v = TextBox{parent=entry,x=14,y=2,text=" ------- ",width=20,height=1,fg_bg=lg_white}
|
||||
local pdg_fw_v = TextBox{parent=entry,x=14,y=2,text=" ------- ",width=20,height=1,fg_bg=label_fg}
|
||||
pdg_fw_v.register(databus.ps, ps_prefix .. "fw", pdg_fw_v.set_value)
|
||||
|
||||
TextBox{parent=entry,x=35,y=2,text="RTT:",width=4,height=1}
|
||||
local pdg_rtt = DataIndicator{parent=entry,x=40,y=2,label="",unit="",format="%5d",value=0,width=5,fg_bg=lg_white}
|
||||
TextBox{parent=entry,x=46,y=2,text="ms",width=4,height=1,fg_bg=lg_white}
|
||||
local pdg_rtt = DataIndicator{parent=entry,x=40,y=2,label="",unit="",format="%5d",value=0,width=5,fg_bg=label_fg}
|
||||
TextBox{parent=entry,x=46,y=2,text="ms",width=4,height=1,fg_bg=label_fg}
|
||||
pdg_rtt.register(databus.ps, ps_prefix .. "rtt", pdg_rtt.update)
|
||||
pdg_rtt.register(databus.ps, ps_prefix .. "rtt_color", pdg_rtt.recolor)
|
||||
|
||||
|
||||
@@ -17,35 +17,36 @@ local ALIGN = core.ALIGN
|
||||
|
||||
local cpair = core.cpair
|
||||
|
||||
local black_lg = style.black_lg
|
||||
local lg_white = style.lg_white
|
||||
|
||||
-- create an RTU list entry
|
||||
---@param parent graphics_element parent
|
||||
---@param id integer RTU session ID
|
||||
local function init(parent, id)
|
||||
local s_hi_box = style.theme.highlight_box
|
||||
|
||||
local label_fg = style.fp.label_fg
|
||||
|
||||
-- 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.bw_fg_bg}
|
||||
local entry = Div{parent=root,x=2,y=1,height=3,fg_bg=style.theme.highlight_box_bright}
|
||||
|
||||
local ps_prefix = "rtu_" .. id .. "_"
|
||||
|
||||
TextBox{parent=entry,x=1,y=1,text="",width=8,height=1,fg_bg=black_lg}
|
||||
local rtu_addr = TextBox{parent=entry,x=1,y=2,text="@ C ??",alignment=ALIGN.CENTER,width=8,height=1,fg_bg=black_lg,nav_active=cpair(colors.gray,colors.black)}
|
||||
TextBox{parent=entry,x=1,y=3,text="",width=8,height=1,fg_bg=black_lg}
|
||||
TextBox{parent=entry,x=1,y=1,text="",width=8,height=1,fg_bg=s_hi_box}
|
||||
local rtu_addr = TextBox{parent=entry,x=1,y=2,text="@ C ??",alignment=ALIGN.CENTER,width=8,height=1,fg_bg=s_hi_box,nav_active=cpair(colors.gray,colors.black)}
|
||||
TextBox{parent=entry,x=1,y=3,text="",width=8,height=1,fg_bg=s_hi_box}
|
||||
rtu_addr.register(databus.ps, ps_prefix .. "addr", rtu_addr.set_value)
|
||||
|
||||
TextBox{parent=entry,x=10,y=2,text="UNITS:",width=7,height=1}
|
||||
local unit_count = DataIndicator{parent=entry,x=17,y=2,label="",unit="",format="%2d",value=0,width=2,fg_bg=style.gray_white}
|
||||
local unit_count = DataIndicator{parent=entry,x=17,y=2,label="",unit="",format="%2d",value=0,width=2,fg_bg=style.fp.label_d_fg}
|
||||
unit_count.register(databus.ps, ps_prefix .. "units", unit_count.set_value)
|
||||
|
||||
TextBox{parent=entry,x=21,y=2,text="FW:",width=3,height=1}
|
||||
local rtu_fw_v = TextBox{parent=entry,x=25,y=2,text=" ------- ",width=9,height=1,fg_bg=lg_white}
|
||||
local rtu_fw_v = TextBox{parent=entry,x=25,y=2,text=" ------- ",width=9,height=1,fg_bg=label_fg}
|
||||
rtu_fw_v.register(databus.ps, ps_prefix .. "fw", rtu_fw_v.set_value)
|
||||
|
||||
TextBox{parent=entry,x=36,y=2,text="RTT:",width=4,height=1}
|
||||
local rtu_rtt = DataIndicator{parent=entry,x=40,y=2,label="",unit="",format="%5d",value=0,width=5,fg_bg=lg_white}
|
||||
TextBox{parent=entry,x=46,y=2,text="ms",width=4,height=1,fg_bg=lg_white}
|
||||
local rtu_rtt = DataIndicator{parent=entry,x=40,y=2,label="",unit="",format="%5d",value=0,width=5,fg_bg=label_fg}
|
||||
TextBox{parent=entry,x=46,y=2,text="ms",width=4,height=1,fg_bg=label_fg}
|
||||
rtu_rtt.register(databus.ps, ps_prefix .. "rtt", rtu_rtt.update)
|
||||
rtu_rtt.register(databus.ps, ps_prefix .. "rtt_color", rtu_rtt.recolor)
|
||||
|
||||
|
||||
@@ -29,18 +29,18 @@ local ALIGN = core.ALIGN
|
||||
|
||||
local cpair = core.cpair
|
||||
|
||||
local bw_fg_bg = style.bw_fg_bg
|
||||
|
||||
local black_lg = style.black_lg
|
||||
local lg_white = style.lg_white
|
||||
local gry_wht = style.gray_white
|
||||
|
||||
local ind_grn = style.ind_grn
|
||||
|
||||
-- create new front panel view
|
||||
---@param panel graphics_element main displaybox
|
||||
local function init(panel)
|
||||
TextBox{parent=panel,y=1,text="SCADA SUPERVISOR",alignment=ALIGN.CENTER,height=1,fg_bg=style.header}
|
||||
local s_hi_box = style.theme.highlight_box
|
||||
local s_hi_bright = style.theme.highlight_box_bright
|
||||
|
||||
local label_fg = style.fp.label_fg
|
||||
local label_d_fg = style.fp.label_d_fg
|
||||
|
||||
TextBox{parent=panel,y=1,text="SCADA SUPERVISOR",alignment=ALIGN.CENTER,height=1,fg_bg=style.theme.header}
|
||||
|
||||
local page_div = Div{parent=panel,x=1,y=3}
|
||||
|
||||
@@ -66,13 +66,13 @@ local function init(panel)
|
||||
|
||||
---@diagnostic disable-next-line: undefined-field
|
||||
local comp_id = util.sprintf("(%d)", os.getComputerID())
|
||||
TextBox{parent=system,x=9,y=4,width=6,height=1,text=comp_id,fg_bg=style.fp_label}
|
||||
TextBox{parent=system,x=9,y=4,width=6,height=1,text=comp_id,fg_bg=style.fp.disabled_fg}
|
||||
|
||||
--
|
||||
-- about footer
|
||||
--
|
||||
|
||||
local about = Div{parent=main_page,width=15,height=3,x=1,y=16,fg_bg=style.fp_label}
|
||||
local about = Div{parent=main_page,width=15,height=3,x=1,y=16,fg_bg=style.fp.disabled_fg}
|
||||
local fw_v = TextBox{parent=about,x=1,y=1,text="FW: v00.00.00",alignment=ALIGN.LEFT,height=1}
|
||||
local comms_v = TextBox{parent=about,x=1,y=2,text="NT: v00.00.00",alignment=ALIGN.LEFT,height=1}
|
||||
|
||||
@@ -90,25 +90,25 @@ local function init(panel)
|
||||
|
||||
for i = 1, supervisor.config.UnitCount do
|
||||
local ps_prefix = "plc_" .. i .. "_"
|
||||
local plc_entry = Div{parent=plc_list,height=3,fg_bg=bw_fg_bg}
|
||||
local plc_entry = Div{parent=plc_list,height=3,fg_bg=s_hi_bright}
|
||||
|
||||
TextBox{parent=plc_entry,x=1,y=1,text="",width=8,height=1,fg_bg=black_lg}
|
||||
TextBox{parent=plc_entry,x=1,y=2,text="UNIT "..i,alignment=ALIGN.CENTER,width=8,height=1,fg_bg=black_lg}
|
||||
TextBox{parent=plc_entry,x=1,y=3,text="",width=8,height=1,fg_bg=black_lg}
|
||||
TextBox{parent=plc_entry,x=1,y=1,text="",width=8,height=1,fg_bg=s_hi_box}
|
||||
TextBox{parent=plc_entry,x=1,y=2,text="UNIT "..i,alignment=ALIGN.CENTER,width=8,height=1,fg_bg=s_hi_box}
|
||||
TextBox{parent=plc_entry,x=1,y=3,text="",width=8,height=1,fg_bg=s_hi_box}
|
||||
|
||||
local conn = LED{parent=plc_entry,x=10,y=2,label="LINK",colors=ind_grn}
|
||||
local conn = LED{parent=plc_entry,x=10,y=2,label="LINK",colors=cpair(colors.green_hc,colors.green_off)}
|
||||
conn.register(databus.ps, ps_prefix .. "conn", conn.update)
|
||||
|
||||
local plc_addr = TextBox{parent=plc_entry,x=17,y=2,text=" --- ",width=5,height=1,fg_bg=gry_wht}
|
||||
local plc_addr = TextBox{parent=plc_entry,x=17,y=2,text=" --- ",width=5,height=1,fg_bg=label_d_fg}
|
||||
plc_addr.register(databus.ps, ps_prefix .. "addr", plc_addr.set_value)
|
||||
|
||||
TextBox{parent=plc_entry,x=23,y=2,text="FW:",width=3,height=1}
|
||||
local plc_fw_v = TextBox{parent=plc_entry,x=27,y=2,text=" ------- ",width=9,height=1,fg_bg=lg_white}
|
||||
local plc_fw_v = TextBox{parent=plc_entry,x=27,y=2,text=" ------- ",width=9,height=1,fg_bg=label_fg}
|
||||
plc_fw_v.register(databus.ps, ps_prefix .. "fw", plc_fw_v.set_value)
|
||||
|
||||
TextBox{parent=plc_entry,x=37,y=2,text="RTT:",width=4,height=1}
|
||||
local plc_rtt = DataIndicator{parent=plc_entry,x=42,y=2,label="",unit="",format="%4d",value=0,width=4,fg_bg=lg_white}
|
||||
TextBox{parent=plc_entry,x=47,y=2,text="ms",width=4,height=1,fg_bg=lg_white}
|
||||
local plc_rtt = DataIndicator{parent=plc_entry,x=42,y=2,label="",unit="",format="%4d",value=0,width=4,fg_bg=label_fg}
|
||||
TextBox{parent=plc_entry,x=47,y=2,text="ms",width=4,height=1,fg_bg=label_fg}
|
||||
plc_rtt.register(databus.ps, ps_prefix .. "rtt", plc_rtt.update)
|
||||
plc_rtt.register(databus.ps, ps_prefix .. "rtt_color", plc_rtt.recolor)
|
||||
|
||||
@@ -124,29 +124,29 @@ local function init(panel)
|
||||
-- coordinator page
|
||||
|
||||
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=bw_fg_bg}
|
||||
local crd_box = Div{parent=crd_page,x=2,y=2,width=49,height=4,fg_bg=s_hi_bright}
|
||||
|
||||
local crd_conn = LED{parent=crd_box,x=2,y=2,label="CONNECTION",colors=ind_grn}
|
||||
local crd_conn = LED{parent=crd_box,x=2,y=2,label="CONNECTION",colors=cpair(colors.green_hc,colors.green_off)}
|
||||
crd_conn.register(databus.ps, "crd_conn", crd_conn.update)
|
||||
|
||||
TextBox{parent=crd_box,x=4,y=3,text="COMPUTER",width=8,height=1,fg_bg=gry_wht}
|
||||
local crd_addr = TextBox{parent=crd_box,x=13,y=3,text="---",width=5,height=1,fg_bg=gry_wht}
|
||||
TextBox{parent=crd_box,x=4,y=3,text="COMPUTER",width=8,height=1,fg_bg=label_d_fg}
|
||||
local crd_addr = TextBox{parent=crd_box,x=13,y=3,text="---",width=5,height=1,fg_bg=label_d_fg}
|
||||
crd_addr.register(databus.ps, "crd_addr", crd_addr.set_value)
|
||||
|
||||
TextBox{parent=crd_box,x=22,y=2,text="FW:",width=3,height=1}
|
||||
local crd_fw_v = TextBox{parent=crd_box,x=26,y=2,text=" ------- ",width=9,height=1,fg_bg=lg_white}
|
||||
local crd_fw_v = TextBox{parent=crd_box,x=26,y=2,text=" ------- ",width=9,height=1,fg_bg=label_fg}
|
||||
crd_fw_v.register(databus.ps, "crd_fw", crd_fw_v.set_value)
|
||||
|
||||
TextBox{parent=crd_box,x=36,y=2,text="RTT:",width=4,height=1}
|
||||
local crd_rtt = DataIndicator{parent=crd_box,x=41,y=2,label="",unit="",format="%5d",value=0,width=5,fg_bg=lg_white}
|
||||
TextBox{parent=crd_box,x=47,y=2,text="ms",width=4,height=1,fg_bg=lg_white}
|
||||
local crd_rtt = DataIndicator{parent=crd_box,x=41,y=2,label="",unit="",format="%5d",value=0,width=5,fg_bg=label_fg}
|
||||
TextBox{parent=crd_box,x=47,y=2,text="ms",width=4,height=1,fg_bg=label_fg}
|
||||
crd_rtt.register(databus.ps, "crd_rtt", crd_rtt.update)
|
||||
crd_rtt.register(databus.ps, "crd_rtt_color", crd_rtt.recolor)
|
||||
|
||||
-- pocket diagnostics page
|
||||
|
||||
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=cpair(colors.black,colors.ivory),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
|
||||
|
||||
-- assemble page panes
|
||||
@@ -156,14 +156,14 @@ local function init(panel)
|
||||
local page_pane = MultiPane{parent=page_div,x=1,y=1,panes=panes}
|
||||
|
||||
local tabs = {
|
||||
{ name = "SVR", color = cpair(colors.black, colors.ivory) },
|
||||
{ name = "PLC", color = cpair(colors.black, colors.ivory) },
|
||||
{ name = "RTU", color = cpair(colors.black, colors.ivory) },
|
||||
{ name = "CRD", color = cpair(colors.black, colors.ivory) },
|
||||
{ name = "PKT", color = cpair(colors.black, colors.ivory) },
|
||||
{ name = "SVR", color = style.fp.text },
|
||||
{ name = "PLC", color = style.fp.text },
|
||||
{ name = "RTU", color = style.fp.text },
|
||||
{ name = "CRD", color = style.fp.text },
|
||||
{ name = "PKT", color = style.fp.text },
|
||||
}
|
||||
|
||||
TabBar{parent=panel,y=2,tabs=tabs,min_width=9,callback=page_pane.set_value,fg_bg=bw_fg_bg}
|
||||
TabBar{parent=panel,y=2,tabs=tabs,min_width=9,callback=page_pane.set_value,fg_bg=style.theme.highlight_box_bright}
|
||||
|
||||
-- link RTU/PDG list management to PGI
|
||||
pgi.link_elements(rtu_list, rtu_entry, pdg_list, pdg_entry)
|
||||
|
||||
@@ -2,53 +2,33 @@
|
||||
-- Graphics Style Options
|
||||
--
|
||||
|
||||
local core = require("graphics.core")
|
||||
local core = require("graphics.core")
|
||||
local themes = require("graphics.themes")
|
||||
|
||||
---@class svr_style
|
||||
local style = {}
|
||||
|
||||
local cpair = core.cpair
|
||||
|
||||
-- GLOBAL --
|
||||
|
||||
-- remap global colors
|
||||
colors.ivory = colors.pink
|
||||
colors.yellow_hc = colors.purple
|
||||
colors.red_off = colors.brown
|
||||
colors.yellow_off = colors.magenta
|
||||
colors.green_off = colors.lime
|
||||
|
||||
style.root = cpair(colors.black, colors.ivory)
|
||||
style.header = cpair(colors.black, colors.lightGray)
|
||||
|
||||
style.colors = {
|
||||
{ c = colors.red, hex = 0xdf4949 }, -- RED ON
|
||||
{ c = colors.orange, hex = 0xffb659 },
|
||||
{ c = colors.yellow, hex = 0xf9fb53 }, -- YELLOW ON
|
||||
{ c = colors.lime, hex = 0x16665a }, -- GREEN OFF
|
||||
{ c = colors.green, hex = 0x6be551 }, -- GREEN ON
|
||||
{ c = colors.cyan, hex = 0x34bac8 },
|
||||
{ c = colors.lightBlue, hex = 0x6cc0f2 },
|
||||
{ c = colors.blue, hex = 0x0008fe }, -- LCD BLUE
|
||||
{ c = colors.purple, hex = 0xe3bc2a }, -- YELLOW HIGH CONTRAST
|
||||
{ c = colors.pink, hex = 0xdcd9ca }, -- IVORY
|
||||
{ c = colors.magenta, hex = 0x85862c }, -- YELLOW OFF
|
||||
-- { c = colors.white, hex = 0xdcd9ca },
|
||||
{ c = colors.lightGray, hex = 0xb1b8b3 },
|
||||
{ c = colors.gray, hex = 0x575757 },
|
||||
-- { c = colors.black, hex = 0x191919 },
|
||||
{ c = colors.brown, hex = 0x672223 } -- RED OFF
|
||||
}
|
||||
|
||||
-- COMMON COLOR PAIRS --
|
||||
|
||||
style.text_fg_bg = cpair(colors.black, colors.ivory)
|
||||
style.bw_fg_bg = cpair(colors.black, colors.white)
|
||||
style.fp_label = cpair(colors.lightGray, colors.ivory)
|
||||
|
||||
style.black_lg = cpair(colors.black, colors.lightGray)
|
||||
style.lg_white = cpair(colors.lightGray, colors.white)
|
||||
style.gray_white = cpair(colors.gray, colors.white)
|
||||
style.theme = themes.sandstone
|
||||
style.fp = themes.get_fp_style(style.theme)
|
||||
style.colorblind = false
|
||||
|
||||
style.ind_grn = cpair(colors.green, colors.green_off)
|
||||
|
||||
-- set theme per configuration
|
||||
---@param fp FP_THEME front panel theme
|
||||
---@param color_mode COLOR_MODE the color mode to use
|
||||
function style.set_theme(fp, color_mode)
|
||||
if fp == themes.FP_THEME.SANDSTONE then
|
||||
style.theme = themes.sandstone
|
||||
elseif fp == themes.FP_THEME.BASALT then
|
||||
style.theme = themes.basalt
|
||||
end
|
||||
|
||||
style.fp = themes.get_fp_style(style.theme)
|
||||
|
||||
style.colorblind = color_mode ~= themes.COLOR_MODE.STANDARD and color_mode ~= themes.COLOR_MODE.STD_ON_BLACK
|
||||
end
|
||||
|
||||
return style
|
||||
|
||||
@@ -19,11 +19,16 @@ local ui = {
|
||||
}
|
||||
|
||||
-- try to start the UI
|
||||
---@param theme FP_THEME front panel theme
|
||||
---@param color_mode COLOR_MODE color mode
|
||||
---@return boolean success, any error_msg
|
||||
function renderer.try_start_ui()
|
||||
function renderer.try_start_ui(theme, color_mode)
|
||||
local status, msg = true, nil
|
||||
|
||||
if ui.display == nil then
|
||||
-- set theme
|
||||
style.set_theme(theme, color_mode)
|
||||
|
||||
-- reset terminal
|
||||
term.setTextColor(colors.white)
|
||||
term.setBackgroundColor(colors.black)
|
||||
@@ -31,13 +36,19 @@ function renderer.try_start_ui()
|
||||
term.setCursorPos(1, 1)
|
||||
|
||||
-- set overridden colors
|
||||
for i = 1, #style.colors do
|
||||
term.setPaletteColor(style.colors[i].c, style.colors[i].hex)
|
||||
for i = 1, #style.theme.colors do
|
||||
term.setPaletteColor(style.theme.colors[i].c, style.theme.colors[i].hex)
|
||||
end
|
||||
|
||||
-- apply color mode
|
||||
local c_mode_overrides = style.theme.color_modes[color_mode]
|
||||
for i = 1, #c_mode_overrides do
|
||||
term.setPaletteColor(c_mode_overrides[i].c, c_mode_overrides[i].hex)
|
||||
end
|
||||
|
||||
-- init front panel view
|
||||
status, msg = pcall(function ()
|
||||
ui.display = DisplayBox{window=term.current(),fg_bg=style.root}
|
||||
ui.display = DisplayBox{window=term.current(),fg_bg=style.fp.root}
|
||||
panel_view(ui.display)
|
||||
end)
|
||||
|
||||
@@ -70,9 +81,9 @@ function renderer.close_ui()
|
||||
ui.display = nil
|
||||
|
||||
-- restore colors
|
||||
for i = 1, #style.colors do
|
||||
local r, g, b = term.nativePaletteColor(style.colors[i].c)
|
||||
term.setPaletteColor(style.colors[i].c, r, g, b)
|
||||
for i = 1, #style.theme.colors do
|
||||
local r, g, b = term.nativePaletteColor(style.theme.colors[i].c)
|
||||
term.setPaletteColor(style.theme.colors[i].c, r, g, b)
|
||||
end
|
||||
|
||||
-- reset terminal
|
||||
|
||||
@@ -17,9 +17,6 @@ local FAC_COMMAND = comms.FAC_COMMAND
|
||||
|
||||
local SV_Q_DATA = svqtypes.SV_Q_DATA
|
||||
|
||||
-- grace period in seconds for coordinator to finish UI draw to prevent timeout
|
||||
local WATCHDOG_GRACE = 20.0
|
||||
|
||||
-- retry time constants in ms
|
||||
-- local INITIAL_WAIT = 1500
|
||||
local RETRY_PERIOD = 1000
|
||||
@@ -273,6 +270,12 @@ function coordinator.new_session(id, s_addr, in_queue, out_queue, timeout, facil
|
||||
else
|
||||
log.debug(log_header .. "CRDN set pu fallback packet length mismatch")
|
||||
end
|
||||
elseif cmd == FAC_COMMAND.SET_SPS_LP then
|
||||
if pkt.length == 2 then
|
||||
_send(CRDN_TYPE.FAC_CMD, { cmd, facility.set_sps_low_power(pkt.data[2]) })
|
||||
else
|
||||
log.debug(log_header .. "CRDN set sps low power packet length mismatch")
|
||||
end
|
||||
else
|
||||
log.debug(log_header .. "CRDN facility command unknown")
|
||||
end
|
||||
@@ -360,15 +363,7 @@ function coordinator.new_session(id, s_addr, in_queue, out_queue, timeout, facil
|
||||
-- check if a timer matches this session's watchdog
|
||||
---@nodiscard
|
||||
function public.check_wd(timer)
|
||||
local is_wd = self.conn_watchdog.is_timer(timer) and self.connected
|
||||
|
||||
-- if we are waiting for initial coordinator UI draw, don't close yet
|
||||
if is_wd and (util.time_s() - self.establish_time) <= WATCHDOG_GRACE then
|
||||
self.conn_watchdog.feed()
|
||||
is_wd = false
|
||||
end
|
||||
|
||||
return is_wd
|
||||
return self.conn_watchdog.is_timer(timer) and self.connected
|
||||
end
|
||||
|
||||
-- close the connection
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
local comms = require("scada-common.comms")
|
||||
local const = require("scada-common.constants")
|
||||
local log = require("scada-common.log")
|
||||
local mqueue = require("scada-common.mqueue")
|
||||
local types = require("scada-common.types")
|
||||
@@ -105,6 +106,8 @@ function plc.new_session(id, s_addr, reactor_id, in_queue, out_queue, timeout, f
|
||||
formed = false,
|
||||
rps_tripped = false,
|
||||
rps_trip_cause = "ok", ---@type rps_trip_cause
|
||||
max_op_temp_H2O = 1200,
|
||||
max_op_temp_Na = 1200,
|
||||
---@class rps_status
|
||||
rps_status = {
|
||||
high_dmg = false,
|
||||
@@ -138,11 +141,11 @@ function plc.new_session(id, s_addr, reactor_id, in_queue, out_queue, timeout, f
|
||||
waste = 0,
|
||||
waste_need = 0,
|
||||
waste_fill = 0.0,
|
||||
ccool_type = "?",
|
||||
ccool_type = types.FLUID.EMPTY_GAS, ---@type fluid
|
||||
ccool_amnt = 0,
|
||||
ccool_need = 0,
|
||||
ccool_fill = 0.0,
|
||||
hcool_type = "?",
|
||||
hcool_type = types.FLUID.EMPTY_GAS, ---@type fluid
|
||||
hcool_amnt = 0,
|
||||
hcool_need = 0,
|
||||
hcool_fill = 0.0
|
||||
@@ -169,6 +172,21 @@ function plc.new_session(id, s_addr, reactor_id, in_queue, out_queue, timeout, f
|
||||
---@class plc_session
|
||||
local public = {}
|
||||
|
||||
-- compute maximum expected operational temperatures for high temp warnings
|
||||
local function _compute_op_temps()
|
||||
local JOULES_PER_MB = const.mek.JOULES_PER_MB
|
||||
local BASE_BOIL_TEMP = const.mek.BASE_BOIL_TEMP
|
||||
|
||||
local heat_cap = self.sDB.mek_struct.heat_cap
|
||||
local max_burn = self.sDB.mek_struct.max_burn
|
||||
|
||||
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
|
||||
|
||||
log.info(util.sprintf(log_header .. "computed maximum operational temperatures %.3fK (H2O) and %.3fK (Na)",
|
||||
self.sDB.max_op_temp_H2O, self.sDB.max_op_temp_Na))
|
||||
end
|
||||
|
||||
-- copy in the RPS status
|
||||
---@param rps_status table
|
||||
local function _copy_rps_status(rps_status)
|
||||
@@ -351,6 +369,7 @@ function plc.new_session(id, s_addr, reactor_id, in_queue, out_queue, timeout, f
|
||||
local status = pcall(_copy_struct, pkt.data)
|
||||
if status then
|
||||
-- copied in structure data OK
|
||||
_compute_op_temps()
|
||||
self.received_struct = true
|
||||
out_queue.push_data(svqtypes.SV_Q_DATA.PLC_BUILD_CHANGED, reactor_id)
|
||||
else
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
-- Redstone RTU Session I/O Controller
|
||||
--
|
||||
|
||||
local rsio = require("scada-common.rsio")
|
||||
|
||||
local rsctl = {}
|
||||
|
||||
-- create a new redstone RTU I/O controller
|
||||
@@ -16,7 +18,7 @@ function rsctl.new(redstone_rtus)
|
||||
---@return boolean
|
||||
function public.is_connected(port)
|
||||
for i = 1, #redstone_rtus do
|
||||
local db = redstone_rtus[i].get_db() ---@type redstone_session_db
|
||||
local db = redstone_rtus[i].get_db() ---@type redstone_session_db
|
||||
if db.io[port] ~= nil then return true end
|
||||
end
|
||||
|
||||
@@ -28,8 +30,8 @@ function rsctl.new(redstone_rtus)
|
||||
---@param value boolean
|
||||
function public.digital_write(port, value)
|
||||
for i = 1, #redstone_rtus do
|
||||
local db = redstone_rtus[i].get_db() ---@type redstone_session_db
|
||||
local io = db.io[port] ---@type rs_db_dig_io|nil
|
||||
local db = redstone_rtus[i].get_db() ---@type redstone_session_db
|
||||
local io = db.io[port] ---@type rs_db_dig_io|nil
|
||||
if io ~= nil then io.write(value) end
|
||||
end
|
||||
end
|
||||
@@ -40,12 +42,25 @@ function rsctl.new(redstone_rtus)
|
||||
---@return boolean|nil
|
||||
function public.digital_read(port)
|
||||
for i = 1, #redstone_rtus do
|
||||
local db = redstone_rtus[i].get_db() ---@type redstone_session_db
|
||||
local io = db.io[port] ---@type rs_db_dig_io|nil
|
||||
local db = redstone_rtus[i].get_db() ---@type redstone_session_db
|
||||
local io = db.io[port] ---@type rs_db_dig_io|nil
|
||||
if io ~= nil then return io.read() end
|
||||
end
|
||||
end
|
||||
|
||||
-- write to an analog redstone port (applies to all RTUs)
|
||||
---@param port IO_PORT
|
||||
---@param value number value
|
||||
---@param min number minimum value for scaling 0 to 15
|
||||
---@param max number maximum value for scaling 0 to 15
|
||||
function public.analog_write(port, value, min, max)
|
||||
for i = 1, #redstone_rtus do
|
||||
local db = redstone_rtus[i].get_db() ---@type redstone_session_db
|
||||
local io = db.io[port] ---@type rs_db_ana_io|nil
|
||||
if io ~= nil then io.write(rsio.analog_write(value, min, max)) end
|
||||
end
|
||||
end
|
||||
|
||||
return public
|
||||
end
|
||||
|
||||
|
||||
@@ -201,7 +201,7 @@ function svsessions.init(nic, fp_ok, config, cooling_conf)
|
||||
self.nic = nic
|
||||
self.fp_ok = fp_ok
|
||||
self.config = config
|
||||
self.facility = facility.new(config.UnitCount, cooling_conf)
|
||||
self.facility = facility.new(config, cooling_conf)
|
||||
end
|
||||
|
||||
-- find an RTU session by the computer ID
|
||||
|
||||
@@ -21,7 +21,7 @@ local supervisor = require("supervisor.supervisor")
|
||||
|
||||
local svsessions = require("supervisor.session.svsessions")
|
||||
|
||||
local SUPERVISOR_VERSION = "v1.2.11"
|
||||
local SUPERVISOR_VERSION = "v1.3.10"
|
||||
|
||||
local println = util.println
|
||||
local println_ts = util.println_ts
|
||||
@@ -86,6 +86,7 @@ log.info("========================================")
|
||||
println(">> SCADA Supervisor " .. SUPERVISOR_VERSION .. " <<")
|
||||
|
||||
crash.set_env("supervisor", SUPERVISOR_VERSION)
|
||||
crash.dbg_log_env()
|
||||
|
||||
----------------------------------------
|
||||
-- main application
|
||||
@@ -118,7 +119,7 @@ local function main()
|
||||
databus.tx_hw_modem(true)
|
||||
|
||||
-- start UI
|
||||
local fp_ok, message = renderer.try_start_ui()
|
||||
local fp_ok, message = renderer.try_start_ui(config.FrontPanelTheme, config.ColorMode)
|
||||
|
||||
if not fp_ok then
|
||||
println_ts(util.c("UI error: ", message))
|
||||
|
||||
@@ -2,6 +2,8 @@ local comms = require("scada-common.comms")
|
||||
local log = require("scada-common.log")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local themes = require("graphics.themes")
|
||||
|
||||
local svsessions = require("supervisor.session.svsessions")
|
||||
|
||||
local supervisor = {}
|
||||
@@ -24,6 +26,7 @@ function supervisor.load_config()
|
||||
config.CoolingConfig = settings.get("CoolingConfig")
|
||||
config.FacilityTankMode = settings.get("FacilityTankMode")
|
||||
config.FacilityTankDefs = settings.get("FacilityTankDefs")
|
||||
config.ExtChargeIdling = settings.get("ExtChargeIdling")
|
||||
|
||||
config.SVR_Channel = settings.get("SVR_Channel")
|
||||
config.PLC_Channel = settings.get("PLC_Channel")
|
||||
@@ -43,6 +46,9 @@ function supervisor.load_config()
|
||||
config.LogPath = settings.get("LogPath")
|
||||
config.LogDebug = settings.get("LogDebug")
|
||||
|
||||
config.FrontPanelTheme = settings.get("FrontPanelTheme")
|
||||
config.ColorMode = settings.get("ColorMode")
|
||||
|
||||
local cfv = util.new_validator()
|
||||
|
||||
cfv.assert_type_int(config.UnitCount)
|
||||
@@ -53,6 +59,8 @@ function supervisor.load_config()
|
||||
cfv.assert_type_int(config.FacilityTankMode)
|
||||
cfv.assert_range(config.FacilityTankMode, 0, 8)
|
||||
|
||||
cfv.assert_type_bool(config.ExtChargeIdling)
|
||||
|
||||
cfv.assert_channel(config.SVR_Channel)
|
||||
cfv.assert_channel(config.PLC_Channel)
|
||||
cfv.assert_channel(config.RTU_Channel)
|
||||
@@ -73,7 +81,7 @@ function supervisor.load_config()
|
||||
|
||||
if type(config.AuthKey) == "string" then
|
||||
local len = string.len(config.AuthKey)
|
||||
cfv.assert_eq(len == 0 or len >= 8, true)
|
||||
cfv.assert(len == 0 or len >= 8)
|
||||
end
|
||||
|
||||
cfv.assert_type_int(config.LogMode)
|
||||
@@ -81,6 +89,11 @@ function supervisor.load_config()
|
||||
cfv.assert_type_str(config.LogPath)
|
||||
cfv.assert_type_bool(config.LogDebug)
|
||||
|
||||
cfv.assert_type_int(config.FrontPanelTheme)
|
||||
cfv.assert_range(config.FrontPanelTheme, 1, 2)
|
||||
cfv.assert_type_int(config.ColorMode)
|
||||
cfv.assert_range(config.ColorMode, 1, themes.COLOR_MODE.NUM_MODES)
|
||||
|
||||
return cfv.valid()
|
||||
end
|
||||
|
||||
|
||||
@@ -8,9 +8,6 @@ local logic = require("supervisor.unitlogic")
|
||||
local plc = require("supervisor.session.plc")
|
||||
local rsctl = require("supervisor.session.rsctl")
|
||||
|
||||
---@class reactor_control_unit
|
||||
local unit = {}
|
||||
|
||||
local WASTE_MODE = types.WASTE_MODE
|
||||
local WASTE = types.WASTE_PRODUCT
|
||||
local ALARM = types.ALARM
|
||||
@@ -55,17 +52,27 @@ local AISTATE = {
|
||||
---@field id ALARM alarm ID
|
||||
---@field tier integer alarm urgency tier (0 = highest)
|
||||
|
||||
-- burn rate to idle at
|
||||
local IDLE_RATE = 0.01
|
||||
|
||||
---@class reactor_control_unit
|
||||
local unit = {}
|
||||
|
||||
-- create a new reactor unit
|
||||
---@nodiscard
|
||||
---@param reactor_id integer reactor unit number
|
||||
---@param num_boilers integer number of boilers expected
|
||||
---@param num_turbines integer number of turbines expected
|
||||
function unit.new(reactor_id, num_boilers, num_turbines)
|
||||
---@param ext_idle boolean extended idling mode
|
||||
function unit.new(reactor_id, num_boilers, num_turbines, ext_idle)
|
||||
-- time (ms) to idle for auto idling
|
||||
local IDLE_TIME = util.trinary(ext_idle, 60000, 10000)
|
||||
|
||||
---@class _unit_self
|
||||
local self = {
|
||||
r_id = reactor_id,
|
||||
plc_s = nil, ---@class plc_session_struct
|
||||
plc_i = nil, ---@class plc_session
|
||||
plc_s = nil, ---@type plc_session_struct
|
||||
plc_i = nil, ---@type plc_session
|
||||
num_boilers = num_boilers,
|
||||
num_turbines = num_turbines,
|
||||
types = { DT_KEYS = DT_KEYS, AISTATE = AISTATE },
|
||||
@@ -83,6 +90,9 @@ function unit.new(reactor_id, num_boilers, num_turbines)
|
||||
emcool_opened = false,
|
||||
-- auto control
|
||||
auto_engaged = false,
|
||||
auto_idle = false,
|
||||
auto_idling = false,
|
||||
auto_idle_start = 0,
|
||||
auto_was_alarmed = false,
|
||||
ramp_target_br100 = 0,
|
||||
-- state tracking
|
||||
@@ -98,6 +108,8 @@ function unit.new(reactor_id, num_boilers, num_turbines)
|
||||
status_text = { "UNKNOWN", "awaiting connection..." },
|
||||
-- logic for alarms
|
||||
had_reactor = false,
|
||||
turbine_flow_stable = false,
|
||||
turbine_stability_data = {},
|
||||
last_rate_change_ms = 0,
|
||||
---@type rps_status
|
||||
last_rps_trips = {
|
||||
@@ -135,7 +147,8 @@ function unit.new(reactor_id, num_boilers, num_turbines)
|
||||
},
|
||||
damage = 0,
|
||||
temp = 0,
|
||||
waste = 0
|
||||
waste = 0,
|
||||
high_temp_lim = 1150
|
||||
},
|
||||
---@class alarm_monitors
|
||||
alarms = {
|
||||
@@ -251,6 +264,7 @@ function unit.new(reactor_id, num_boilers, num_turbines)
|
||||
table.insert(self.db.annunciator.TurbineOverSpeed, false)
|
||||
table.insert(self.db.annunciator.GeneratorTrip, false)
|
||||
table.insert(self.db.annunciator.TurbineTrip, false)
|
||||
table.insert(self.turbine_stability_data, { time_state = 0, time_tanks = 0, rotation = 1 })
|
||||
end
|
||||
|
||||
-- PRIVATE FUNCTIONS --
|
||||
@@ -530,6 +544,13 @@ function unit.new(reactor_id, num_boilers, num_turbines)
|
||||
|
||||
-- re-engage auto lock if it reconnected without it
|
||||
if self.auto_engaged and not self.plc_i.is_auto_locked() then self.plc_i.auto_lock(true) end
|
||||
|
||||
-- stop idling when completed
|
||||
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"))
|
||||
self.auto_idling = false
|
||||
self.plc_i.auto_set_burn(0, false)
|
||||
end
|
||||
end
|
||||
|
||||
-- update deltas
|
||||
@@ -578,6 +599,23 @@ function unit.new(reactor_id, num_boilers, num_turbines)
|
||||
end
|
||||
end
|
||||
|
||||
-- set automatic control idling mode to change behavior when given a burn rate command of zero<br>
|
||||
-- - enabling it will hold the reactor at 0.01 mB/t for a period when commanded zero before disabling
|
||||
-- - disabling it will stop the reactor when commanded zero
|
||||
---@param idle boolean true to enable, false to disable (and stop)
|
||||
function public.auto_set_idle(idle)
|
||||
if idle and not self.auto_idle then
|
||||
self.auto_idling = false
|
||||
self.auto_idle_start = 0
|
||||
end
|
||||
|
||||
if idle ~= self.auto_idle then
|
||||
log.debug(util.c("UNIT ", self.r_id, ": idling mode changed to ", idle))
|
||||
end
|
||||
|
||||
self.auto_idle = idle
|
||||
end
|
||||
|
||||
-- get the actual limit of this unit<br>
|
||||
-- if it is degraded or not ready, the limit will be 0
|
||||
---@nodiscard
|
||||
@@ -597,7 +635,35 @@ function unit.new(reactor_id, num_boilers, num_turbines)
|
||||
if self.auto_engaged 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))
|
||||
self.plc_i.auto_set_burn(self.db.control.br100 / 100, ramp)
|
||||
|
||||
local rate = self.db.control.br100 / 100
|
||||
|
||||
if self.auto_idle then
|
||||
if rate <= IDLE_RATE then
|
||||
if self.auto_idle_start == 0 then
|
||||
self.auto_idling = true
|
||||
self.auto_idle_start = util.time_ms()
|
||||
log.info(util.c("UNIT ", self.r_id, ": started idling at ", IDLE_RATE, " mB/t"))
|
||||
|
||||
rate = IDLE_RATE
|
||||
elseif (util.time_ms() - self.auto_idle_start) > IDLE_TIME then
|
||||
if self.auto_idling then
|
||||
self.auto_idling = false
|
||||
log.info(util.c("UNIT ", self.r_id, ": completed idling period"))
|
||||
end
|
||||
else
|
||||
log.debug(util.c("UNIT ", self.r_id, ": continuing idle at ", IDLE_RATE, " mB/t"))
|
||||
|
||||
rate = IDLE_RATE
|
||||
end
|
||||
else
|
||||
self.auto_idling = false
|
||||
self.auto_idle_start = 0
|
||||
end
|
||||
end
|
||||
|
||||
self.plc_i.auto_set_burn(rate, ramp)
|
||||
|
||||
if ramp then self.ramp_target_br100 = self.db.control.br100 end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -39,6 +39,21 @@ local ALARM_LIMS = const.ALARM_LIMITS
|
||||
---@class unit_logic_extension
|
||||
local logic = {}
|
||||
|
||||
-- compute Mekanism's rotation rate for a turbine
|
||||
---@param turbine turbinev_session_db
|
||||
local function turbine_rotation(turbine)
|
||||
local build = turbine.build
|
||||
|
||||
local inner_vol = build.steam_cap / const.mek.TURBINE_GAS_PER_TANK
|
||||
local disp_rate = (build.dispersers * const.mek.TURBINE_DISPERSER_FLOW) * inner_vol
|
||||
local vent_rate = build.vents * const.mek.TURBINE_VENT_FLOW
|
||||
|
||||
local max_rate = math.min(disp_rate, vent_rate)
|
||||
local flow = math.min(max_rate, turbine.tanks.steam.amount)
|
||||
|
||||
return (flow * (turbine.tanks.steam.amount / build.steam_cap)) / max_rate
|
||||
end
|
||||
|
||||
-- update the annunciator
|
||||
---@param self _unit_self
|
||||
function logic.update_annunciator(self)
|
||||
@@ -81,6 +96,11 @@ function logic.update_annunciator(self)
|
||||
-- some alarms wait until the burn rate has stabilized, so keep track of that
|
||||
if math.abs(_get_dt(DT_KEYS.ReactorBurnR)) > 0 then
|
||||
self.last_rate_change_ms = util.time_ms()
|
||||
self.turbine_flow_stable = false
|
||||
|
||||
for t = 1, self.num_turbines do
|
||||
self.turbine_stability_data[t] = { time_state = 0, time_tanks = 0, rotation = 1 }
|
||||
end
|
||||
end
|
||||
|
||||
-- record reactor stats
|
||||
@@ -113,7 +133,15 @@ function logic.update_annunciator(self)
|
||||
self.last_heartbeat = plc_db.last_status_update
|
||||
end
|
||||
|
||||
local flow_low = util.trinary(plc_db.mek_status.ccool_type == types.FLUID.SODIUM, ANNUNC_LIMS.RCSFlowLow_NA, ANNUNC_LIMS.RCSFlowLow_H2O)
|
||||
local flow_low = ANNUNC_LIMS.RCSFlowLow_H2O
|
||||
local high_temp = plc_db.max_op_temp_H2O
|
||||
|
||||
if plc_db.mek_status.ccool_type == types.FLUID.SODIUM then
|
||||
flow_low = ANNUNC_LIMS.RCSFlowLow_NA
|
||||
high_temp = plc_db.max_op_temp_Na
|
||||
end
|
||||
|
||||
self.plc_cache.high_temp_lim = math.min(high_temp + ANNUNC_LIMS.OpTempTolerance, 1200)
|
||||
|
||||
-- update other annunciator fields
|
||||
annunc.ReactorSCRAM = plc_db.rps_tripped
|
||||
@@ -122,7 +150,7 @@ function logic.update_annunciator(self)
|
||||
annunc.RCPTrip = plc_db.rps_tripped and (plc_db.rps_status.ex_hcool or plc_db.rps_status.low_cool)
|
||||
annunc.RCSFlowLow = _get_dt(DT_KEYS.ReactorCCool) < flow_low
|
||||
annunc.CoolantLevelLow = plc_db.mek_status.ccool_fill < ANNUNC_LIMS.CoolantLevelLow
|
||||
annunc.ReactorTempHigh = plc_db.mek_status.temp > ANNUNC_LIMS.ReactorTempHigh
|
||||
annunc.ReactorTempHigh = plc_db.mek_status.temp >= self.plc_cache.high_temp_lim
|
||||
annunc.ReactorHighDeltaT = _get_dt(DT_KEYS.ReactorTemp) > ANNUNC_LIMS.ReactorHighDeltaT
|
||||
annunc.FuelInputRateLow = _get_dt(DT_KEYS.ReactorFuel) < -1.0 or plc_db.mek_status.fuel_fill <= ANNUNC_LIMS.FuelLevelLow
|
||||
annunc.WasteLineOcclusion = _get_dt(DT_KEYS.ReactorWaste) > 1.0 or plc_db.mek_status.waste_fill >= ANNUNC_LIMS.WasteLevelHigh
|
||||
@@ -274,6 +302,7 @@ function logic.update_annunciator(self)
|
||||
local total_flow_rate = 0
|
||||
local total_input_rate = 0
|
||||
local max_water_return_rate = 0
|
||||
local turbines_stable = true
|
||||
|
||||
-- recompute blade count on the chance that it may have changed
|
||||
self.db.control.blade_count = 0
|
||||
@@ -282,12 +311,14 @@ function logic.update_annunciator(self)
|
||||
for i = 1, #self.turbines do
|
||||
local session = self.turbines[i] ---@type unit_session
|
||||
local turbine = session.get_db() ---@type turbinev_session_db
|
||||
local idx = session.get_device_idx()
|
||||
|
||||
annunc.RCSFault = annunc.RCSFault or (not turbine.formed) or session.is_faulted()
|
||||
annunc.TurbineOnline[idx] = true
|
||||
|
||||
-- update ready state
|
||||
-- - must be formed
|
||||
-- - must have received build, state, and tanks at least once
|
||||
-- - must be formed
|
||||
-- - must have received build, state, and tanks at least once
|
||||
turbines_ready = turbines_ready and turbine.formed and
|
||||
(turbine.build.last_update > 0) and
|
||||
(turbine.state.last_update > 0) and
|
||||
@@ -296,11 +327,56 @@ function logic.update_annunciator(self)
|
||||
total_flow_rate = total_flow_rate + turbine.state.flow_rate
|
||||
total_input_rate = total_input_rate + turbine.state.steam_input_rate
|
||||
max_water_return_rate = max_water_return_rate + turbine.build.max_water_output
|
||||
|
||||
self.db.control.blade_count = self.db.control.blade_count + turbine.build.blades
|
||||
|
||||
annunc.TurbineOnline[session.get_device_idx()] = true
|
||||
local last = self.turbine_stability_data[i]
|
||||
|
||||
if (not self.turbine_flow_stable) and (turbine.state.steam_input_rate > 0) then
|
||||
local rotation = turbine_rotation(turbine)
|
||||
local rotation_stable = false
|
||||
|
||||
-- see if data updated, and if so, check rotation speed change
|
||||
-- minimal change indicates the turbine is converging on a flow rate
|
||||
if last.time_tanks < turbine.tanks.last_update then
|
||||
if last.time_tanks > 0 then
|
||||
rotation_stable = math.abs(rotation - last.rotation) < 0.00000003
|
||||
end
|
||||
|
||||
last.time_tanks = turbine.tanks.last_update
|
||||
last.rotation = rotation
|
||||
end
|
||||
|
||||
-- flow is stable if the flow rate is at the input rate or at the max (±1 mB/t)
|
||||
local flow_stable = false
|
||||
if last.time_state < turbine.state.last_update then
|
||||
if (last.time_state > 0) and (turbine.state.flow_rate > 0) then
|
||||
flow_stable = math.abs(turbine.state.flow_rate - math.min(turbine.state.steam_input_rate, turbine.build.max_flow_rate)) < 2
|
||||
end
|
||||
|
||||
last.time_state = turbine.state.last_update
|
||||
end
|
||||
|
||||
if rotation_stable then
|
||||
log.debug(util.c("UNIT ", self.r_id, ": turbine ", idx, " reached rotational stability (", rotation, ")"))
|
||||
end
|
||||
|
||||
if flow_stable then
|
||||
log.debug(util.c("UNIT ", self.r_id, ": turbine ", idx, " reached flow stability (", turbine.state.flow_rate, " mB/t)"))
|
||||
end
|
||||
|
||||
turbines_stable = turbines_stable and (rotation_stable or flow_stable)
|
||||
else
|
||||
last.time_state = 0
|
||||
last.time_tanks = 0
|
||||
last.rotation = 1
|
||||
|
||||
turbines_stable = false
|
||||
end
|
||||
end
|
||||
|
||||
self.turbine_flow_stable = self.turbine_flow_stable or turbines_stable
|
||||
|
||||
-- check for boil rate mismatch (> 4% error) either between reactor and turbine or boiler and turbine
|
||||
annunc.BoilRateMismatch = math.abs(total_boil_rate - total_input_rate) > (0.04 * total_boil_rate)
|
||||
|
||||
@@ -474,7 +550,8 @@ function logic.update_alarms(self)
|
||||
end
|
||||
|
||||
-- High Temperature
|
||||
_update_alarm_state(self, plc_cache.temp >= ALARM_LIMS.HIGH_TEMP, self.alarms.ReactorHighTemp)
|
||||
local high_temp = math.min(math.max(self.plc_cache.high_temp_lim, 1100), 1199.995)
|
||||
_update_alarm_state(self, plc_cache.temp >= high_temp, self.alarms.ReactorHighTemp)
|
||||
|
||||
-- Waste Leak
|
||||
_update_alarm_state(self, plc_cache.waste >= 1.0, self.alarms.ReactorWasteLeak)
|
||||
@@ -508,11 +585,25 @@ function logic.update_alarms(self)
|
||||
|
||||
local rcs_trans = any_low or any_over or gen_trip or annunc.RCPTrip or annunc.MaxWaterReturnFeed
|
||||
|
||||
-- annunciator indicators for these states may not indicate a real issue when:
|
||||
-- > flow is ramping up right after reactor start
|
||||
-- > flow is ramping down after reactor shutdown
|
||||
if ((util.time_ms() - self.last_rate_change_ms) > FLOW_STABILITY_DELAY_MS) and plc_cache.active then
|
||||
rcs_trans = rcs_trans or annunc.RCSFlowLow or annunc.BoilRateMismatch or annunc.CoolantFeedMismatch or annunc.SteamFeedMismatch
|
||||
if plc_cache.active then
|
||||
-- these conditions may not indicate an issue when flow is changing after a burn rate change
|
||||
if self.num_boilers == 0 then
|
||||
if (util.time_ms() - self.last_rate_change_ms) > FLOW_STABILITY_DELAY_MS then
|
||||
rcs_trans = rcs_trans or annunc.BoilRateMismatch
|
||||
end
|
||||
|
||||
if self.turbine_flow_stable then
|
||||
rcs_trans = rcs_trans or annunc.RCSFlowLow or annunc.CoolantFeedMismatch or annunc.SteamFeedMismatch
|
||||
end
|
||||
else
|
||||
if (util.time_ms() - self.last_rate_change_ms) > FLOW_STABILITY_DELAY_MS then
|
||||
rcs_trans = rcs_trans or annunc.RCSFlowLow or annunc.BoilRateMismatch or annunc.CoolantFeedMismatch
|
||||
end
|
||||
|
||||
if self.turbine_flow_stable then
|
||||
rcs_trans = rcs_trans or annunc.SteamFeedMismatch
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if _update_alarm_state(self, rcs_trans, self.alarms.RCSTransient) then
|
||||
@@ -636,11 +727,11 @@ function logic.update_status_text(self)
|
||||
self.status_text[2] = "elevated level of radiation"
|
||||
end
|
||||
elseif is_active(self.alarms.ReactorOverTemp) then
|
||||
self.status_text = { "CORE OVER TEMP", "reactor core temperature >=1200K" }
|
||||
self.status_text = { "CORE OVER TEMP", "reactor core temp damaging" }
|
||||
elseif is_active(self.alarms.ReactorWasteLeak) then
|
||||
self.status_text = { "WASTE LEAK", "radioactive waste leak detected" }
|
||||
elseif is_active(self.alarms.ReactorHighTemp) then
|
||||
self.status_text = { "CORE TEMP HIGH", "reactor core temperature >1150K" }
|
||||
self.status_text = { "CORE TEMP HIGH", "reactor core temperature high" }
|
||||
elseif is_active(self.alarms.ReactorHighWaste) then
|
||||
self.status_text = { "WASTE LEVEL HIGH", "waste accumulating in reactor" }
|
||||
elseif is_active(self.alarms.TurbineTrip) then
|
||||
@@ -666,7 +757,9 @@ function logic.update_status_text(self)
|
||||
elseif annunc.WasteLineOcclusion then
|
||||
self.status_text[2] = "insufficient waste output rate"
|
||||
elseif (util.time_ms() - self.last_rate_change_ms) <= FLOW_STABILITY_DELAY_MS then
|
||||
self.status_text[2] = "awaiting flow stability"
|
||||
self.status_text[2] = "awaiting coolant flow stability"
|
||||
elseif not self.turbine_flow_stable then
|
||||
self.status_text[2] = "awaiting turbine flow stability"
|
||||
else
|
||||
self.status_text[2] = "system nominal"
|
||||
end
|
||||
@@ -730,6 +823,8 @@ end
|
||||
function logic.handle_redstone(self)
|
||||
local AISTATE = self.types.AISTATE
|
||||
local annunc = self.db.annunciator
|
||||
local cache = self.plc_cache
|
||||
local rps = cache.rps_status
|
||||
|
||||
-- check if an alarm is active (tripped or ack'd)
|
||||
---@nodiscard
|
||||
@@ -741,18 +836,18 @@ function logic.handle_redstone(self)
|
||||
|
||||
-- reactor controls
|
||||
if self.plc_s ~= nil then
|
||||
if (not self.plc_cache.rps_status.manual) and self.io_ctl.digital_read(IO.R_SCRAM) then
|
||||
if (not rps.manual) and self.io_ctl.digital_read(IO.R_SCRAM) then
|
||||
-- reactor SCRAM requested but not yet done; perform it
|
||||
self.plc_s.in_queue.push_command(PLC_S_CMDS.SCRAM)
|
||||
end
|
||||
|
||||
if self.plc_cache.rps_trip and self.io_ctl.digital_read(IO.R_RESET) then
|
||||
if cache.rps_trip and self.io_ctl.digital_read(IO.R_RESET) then
|
||||
-- reactor RPS reset requested but not yet done; perform it
|
||||
self.plc_s.in_queue.push_command(PLC_S_CMDS.RPS_RESET)
|
||||
end
|
||||
|
||||
if (not self.auto_engaged) and (not self.plc_cache.active) and
|
||||
(not self.plc_cache.rps_trip) and self.io_ctl.digital_read(IO.R_ENABLE) then
|
||||
if (not self.auto_engaged) and (not cache.active) and
|
||||
(not cache.rps_trip) and self.io_ctl.digital_read(IO.R_ENABLE) then
|
||||
-- reactor enable requested and allowable, but not yet done; perform it
|
||||
self.plc_s.in_queue.push_command(PLC_S_CMDS.ENABLE)
|
||||
end
|
||||
@@ -761,25 +856,23 @@ function logic.handle_redstone(self)
|
||||
-- check for request to ack all alarms
|
||||
if self.io_ctl.digital_read(IO.U_ACK) then
|
||||
for i = 1, #self.db.alarm_states do
|
||||
if self.db.alarm_states[i] == ALARM_STATE.TRIPPED then
|
||||
self.db.alarm_states[i] = ALARM_STATE.ACKED
|
||||
end
|
||||
if self.db.alarm_states[i] == ALARM_STATE.TRIPPED then self.db.alarm_states[i] = ALARM_STATE.ACKED end
|
||||
end
|
||||
end
|
||||
|
||||
-- write reactor status outputs
|
||||
self.io_ctl.digital_write(IO.R_ACTIVE, self.plc_cache.active)
|
||||
self.io_ctl.digital_write(IO.R_ACTIVE, cache.active)
|
||||
self.io_ctl.digital_write(IO.R_AUTO_CTRL, self.auto_engaged)
|
||||
self.io_ctl.digital_write(IO.R_SCRAMMED, self.plc_cache.rps_trip)
|
||||
self.io_ctl.digital_write(IO.R_AUTO_SCRAM, self.plc_cache.rps_status.automatic)
|
||||
self.io_ctl.digital_write(IO.R_HIGH_DMG, self.plc_cache.rps_status.high_dmg)
|
||||
self.io_ctl.digital_write(IO.R_HIGH_TEMP, self.plc_cache.rps_status.high_temp)
|
||||
self.io_ctl.digital_write(IO.R_LOW_COOLANT, self.plc_cache.rps_status.low_cool)
|
||||
self.io_ctl.digital_write(IO.R_EXCESS_HC, self.plc_cache.rps_status.ex_hcool)
|
||||
self.io_ctl.digital_write(IO.R_EXCESS_WS, self.plc_cache.rps_status.ex_waste)
|
||||
self.io_ctl.digital_write(IO.R_INSUFF_FUEL, self.plc_cache.rps_status.no_fuel)
|
||||
self.io_ctl.digital_write(IO.R_PLC_FAULT, self.plc_cache.rps_status.fault)
|
||||
self.io_ctl.digital_write(IO.R_PLC_TIMEOUT, self.plc_cache.rps_status.timeout)
|
||||
self.io_ctl.digital_write(IO.R_SCRAMMED, cache.rps_trip)
|
||||
self.io_ctl.digital_write(IO.R_AUTO_SCRAM, rps.automatic)
|
||||
self.io_ctl.digital_write(IO.R_HIGH_DMG, rps.high_dmg)
|
||||
self.io_ctl.digital_write(IO.R_HIGH_TEMP, rps.high_temp)
|
||||
self.io_ctl.digital_write(IO.R_LOW_COOLANT, rps.low_cool)
|
||||
self.io_ctl.digital_write(IO.R_EXCESS_HC, rps.ex_hcool)
|
||||
self.io_ctl.digital_write(IO.R_EXCESS_WS, rps.ex_waste)
|
||||
self.io_ctl.digital_write(IO.R_INSUFF_FUEL, rps.no_fuel)
|
||||
self.io_ctl.digital_write(IO.R_PLC_FAULT, rps.fault)
|
||||
self.io_ctl.digital_write(IO.R_PLC_TIMEOUT, rps.timeout)
|
||||
|
||||
-- write unit outputs
|
||||
|
||||
@@ -797,13 +890,28 @@ function logic.handle_redstone(self)
|
||||
-- Emergency Coolant --
|
||||
-----------------------
|
||||
|
||||
local enable_emer_cool = self.plc_cache.rps_status.low_cool or
|
||||
(self.auto_engaged and annunc.CoolantLevelLow and is_active(self.alarms.ReactorOverTemp))
|
||||
local boiler_water_low = false
|
||||
for i = 1, #annunc.WaterLevelLow do boiler_water_low = boiler_water_low or annunc.WaterLevelLow[i] end
|
||||
|
||||
local enable_emer_cool = rps.low_cool or
|
||||
(self.auto_engaged and
|
||||
(annunc.CoolantLevelLow or (boiler_water_low and rps.ex_hcool)) and
|
||||
is_active(self.alarms.ReactorOverTemp))
|
||||
|
||||
if enable_emer_cool and not self.emcool_opened then
|
||||
log.debug(util.c(">> Emergency Coolant Enable Detail Report (Unit ", self.r_id, ") <<"))
|
||||
log.debug(util.c("| CoolantLevelLow[", annunc.CoolantLevelLow, "] CoolantLevelLowLow[", rps.low_cool, "] ExcessHeatedCoolant[", rps.ex_hcool, "]"))
|
||||
log.debug(util.c("| ReactorOverTemp[", AISTATE_NAMES[self.alarms.ReactorOverTemp.state], "]"))
|
||||
|
||||
for i = 1, #annunc.WaterLevelLow do
|
||||
log.debug(util.c("| WaterLevelLow(", i, ")[", annunc.WaterLevelLow[i], "]"))
|
||||
end
|
||||
end
|
||||
|
||||
-- don't turn off emergency coolant on sufficient coolant level since it might drop again
|
||||
-- turn off once system is OK again
|
||||
-- if auto control is engaged, alarm check will SCRAM on reactor over temp so that's covered
|
||||
if not self.plc_cache.rps_trip then
|
||||
if not cache.rps_trip then
|
||||
-- set turbines to not dump steam
|
||||
for i = 1, #self.turbines do
|
||||
local session = self.turbines[i] ---@type unit_session
|
||||
|
||||
@@ -1,16 +1,28 @@
|
||||
require("/initenv").init_env()
|
||||
|
||||
local rsio = require("scada-common.rsio")
|
||||
local util = require("scada-common.util")
|
||||
local rsio = require("scada-common.rsio")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local testutils = require("test.testutils")
|
||||
|
||||
local IO = rsio.IO
|
||||
local IO_LVL = rsio.IO_LVL
|
||||
local IO_MODE = rsio.IO_MODE
|
||||
|
||||
local print = util.print
|
||||
local println = util.println
|
||||
|
||||
local IO = rsio.IO
|
||||
local IO_LVL = rsio.IO_LVL
|
||||
local IO_MODE = rsio.IO_MODE
|
||||
-- list of inverted digital signals<br>
|
||||
-- only using the key for a quick lookup, value just can't be nil
|
||||
local DIG_INV = {
|
||||
[IO.F_SCRAM] = 0,
|
||||
[IO.R_SCRAM] = 0,
|
||||
[IO.WASTE_PU] = 0,
|
||||
[IO.WASTE_PO] = 0,
|
||||
[IO.WASTE_POPL] = 0,
|
||||
[IO.WASTE_AM] = 0,
|
||||
[IO.U_EMER_COOL] = 0
|
||||
}
|
||||
|
||||
println("starting RSIO tester")
|
||||
println("")
|
||||
@@ -50,8 +62,8 @@ testutils.pause()
|
||||
|
||||
println(">>> checking invalid ports:")
|
||||
|
||||
testutils.test_func("rsio.to_string", rsio.to_string, { -1, 100, false }, "")
|
||||
testutils.test_func_nil("rsio.to_string", rsio.to_string, "")
|
||||
testutils.test_func("rsio.to_string", rsio.to_string, { -1, 100, false }, "UNKNOWN")
|
||||
testutils.test_func_nil("rsio.to_string", rsio.to_string, "UNKNOWN")
|
||||
testutils.test_func("rsio.get_io_mode", rsio.get_io_mode, { -1, 100, false }, IO_MODE.ANALOG_IN)
|
||||
testutils.test_func_nil("rsio.get_io_mode", rsio.get_io_mode, IO_MODE.ANALOG_IN)
|
||||
|
||||
@@ -100,46 +112,35 @@ println(">>> checking port I/O:")
|
||||
|
||||
print("rsio.digital_is_active(...): ")
|
||||
|
||||
-- check input ports
|
||||
assert(rsio.digital_is_active(IO.F_SCRAM, IO_LVL.LOW) == true, "IO_F_SCRAM_HIGH")
|
||||
assert(rsio.digital_is_active(IO.F_SCRAM, IO_LVL.HIGH) == false, "IO_F_SCRAM_LOW")
|
||||
assert(rsio.digital_is_active(IO.R_SCRAM, IO_LVL.LOW) == true, "IO_R_SCRAM_HIGH")
|
||||
assert(rsio.digital_is_active(IO.R_SCRAM, IO_LVL.HIGH) == false, "IO_R_SCRAM_LOW")
|
||||
assert(rsio.digital_is_active(IO.R_ENABLE, IO_LVL.LOW) == false, "IO_R_ENABLE_HIGH")
|
||||
assert(rsio.digital_is_active(IO.R_ENABLE, IO_LVL.HIGH) == true, "IO_R_ENABLE_LOW")
|
||||
-- check all digital ports
|
||||
for i = 1, rsio.NUM_PORTS do
|
||||
if rsio.get_io_mode(i) == IO_MODE.DIGITAL_IN or rsio.get_io_mode(i) == IO_MODE.DIGITAL_OUT then
|
||||
local high = DIG_INV[i] == nil
|
||||
assert(rsio.digital_is_active(i, IO_LVL.LOW) == not high, "IO_" .. rsio.to_string(i) .. "_LOW")
|
||||
assert(rsio.digital_is_active(i, IO_LVL.HIGH) == high, "IO_" .. rsio.to_string(i) .. "_HIGH")
|
||||
end
|
||||
end
|
||||
|
||||
-- non-inputs should always return LOW
|
||||
assert(rsio.digital_is_active(IO.F_ALARM, IO_LVL.LOW) == false, "IO_OUT_READ_LOW")
|
||||
assert(rsio.digital_is_active(IO.F_ALARM, IO_LVL.HIGH) == false, "IO_OUT_READ_HIGH")
|
||||
assert(rsio.digital_is_active(IO.F_MATRIX_CHG, IO_LVL.LOW) == nil, "ANA_DIG_READ_LOW")
|
||||
assert(rsio.digital_is_active(IO.F_MATRIX_CHG, IO_LVL.HIGH) == nil, "ANA_DIG_READ_HIGH")
|
||||
|
||||
println("PASS")
|
||||
|
||||
-- check output ports
|
||||
-- check digital write
|
||||
|
||||
print("rsio.digital_write(...): ")
|
||||
print("rsio.digital_write_active(...): ")
|
||||
|
||||
-- check output ports
|
||||
assert(rsio.digital_write_active(IO.F_ALARM, true) == IO_LVL.LOW, "IO_F_ALARM_LOW")
|
||||
assert(rsio.digital_write_active(IO.F_ALARM, true) == IO_LVL.HIGH, "IO_F_ALARM_HIGH")
|
||||
assert(rsio.digital_write_active(IO.WASTE_PU, true) == IO_LVL.HIGH, "IO_WASTE_PU_HIGH")
|
||||
assert(rsio.digital_write_active(IO.WASTE_PU, true) == IO_LVL.LOW, "IO_WASTE_PU_LOW")
|
||||
assert(rsio.digital_write_active(IO.WASTE_PO, true) == IO_LVL.HIGH, "IO_WASTE_PO_HIGH")
|
||||
assert(rsio.digital_write_active(IO.WASTE_PO, true) == IO_LVL.LOW, "IO_WASTE_PO_LOW")
|
||||
assert(rsio.digital_write_active(IO.WASTE_POPL, true) == IO_LVL.HIGH, "IO_WASTE_POPL_HIGH")
|
||||
assert(rsio.digital_write_active(IO.WASTE_POPL, true) == IO_LVL.LOW, "IO_WASTE_POPL_LOW")
|
||||
assert(rsio.digital_write_active(IO.WASTE_AM, true) == IO_LVL.HIGH, "IO_WASTE_AM_HIGH")
|
||||
assert(rsio.digital_write_active(IO.WASTE_AM, true) == IO_LVL.LOW, "IO_WASTE_AM_LOW")
|
||||
|
||||
-- check all reactor output ports (all are active high)
|
||||
for i = IO.R_ALARM, (IO.R_PLC_TIMEOUT - IO.R_ALARM + 1) do
|
||||
assert(rsio.to_string(i) ~= "", "REACTOR_IO_BAD_PORT")
|
||||
assert(rsio.digital_write_active(i, false) == IO_LVL.LOW, "IO_" .. rsio.to_string(i) .. "_LOW")
|
||||
assert(rsio.digital_write_active(i, true) == IO_LVL.HIGH, "IO_" .. rsio.to_string(i) .. "_HIGH")
|
||||
-- check all digital ports
|
||||
for i = 1, rsio.NUM_PORTS do
|
||||
if rsio.get_io_mode(i) == IO_MODE.DIGITAL_IN or rsio.get_io_mode(i) == IO_MODE.DIGITAL_OUT then
|
||||
local high = DIG_INV[i] == nil
|
||||
assert(rsio.digital_write_active(i, not high) == IO_LVL.LOW, "IO_" .. rsio.to_string(i) .. "_LOW")
|
||||
assert(rsio.digital_write_active(i, high) == IO_LVL.HIGH, "IO_" .. rsio.to_string(i) .. "_HIGH")
|
||||
end
|
||||
end
|
||||
|
||||
-- non-outputs should always return false
|
||||
assert(rsio.digital_write_active(IO.F_SCRAM, false) == IO_LVL.LOW, "IO_IN_WRITE_FALSE")
|
||||
assert(rsio.digital_write_active(IO.F_SCRAM, true) == IO_LVL.LOW, "IO_IN_WRITE_TRUE")
|
||||
assert(rsio.digital_write_active(IO.F_MATRIX_CHG, true) == false, "ANA_DIG_WRITE_TRUE")
|
||||
assert(rsio.digital_write_active(IO.F_MATRIX_CHG, false) == false, "ANA_DIG_WRITE_FALSE")
|
||||
|
||||
println("PASS")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user