Compare commits
211 Commits
v1.9.1-bet
...
devel
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fb102502dd | ||
|
|
2b015759fd | ||
|
|
82aff3b30b | ||
|
|
12d688daec | ||
|
|
d4c5140003 | ||
|
|
21c36c70be | ||
|
|
b020dde122 | ||
|
|
fa551f0b4f | ||
|
|
29513bac38 | ||
|
|
1254d668a9 | ||
|
|
3987d337c4 | ||
|
|
76fc5751c9 | ||
|
|
6e26ca4fac | ||
|
|
32af935e9e | ||
|
|
46b23414b0 | ||
|
|
3f1cf217ac | ||
|
|
6dea501946 | ||
|
|
cc36aafccd | ||
|
|
569358a4e1 | ||
|
|
504dce64c2 | ||
|
|
699ba9f71e | ||
|
|
c0f9ba6ba6 | ||
|
|
138c10ad1f | ||
|
|
6774f60605 | ||
|
|
2ecb662b0a | ||
|
|
50dedaa7c8 | ||
|
|
55e4fed9d8 | ||
|
|
b9a9c018a1 | ||
|
|
e0a0c34b54 | ||
|
|
25207f39c0 | ||
|
|
8c8d3faf72 | ||
|
|
9ff183b17d | ||
|
|
212e1f8fe8 | ||
|
|
b2baaa2090 | ||
|
|
44340f42d4 | ||
|
|
61305621c3 | ||
|
|
6123d5dad7 | ||
|
|
5abe687f69 | ||
|
|
299c6bcf7a | ||
|
|
7745e94fbe | ||
|
|
9a58bf1bb7 | ||
|
|
6bfa26407a | ||
|
|
645c2bacbd | ||
|
|
802ef149c5 | ||
|
|
cc9d5fe2d6 | ||
|
|
d9001090c2 | ||
|
|
0417986c15 | ||
|
|
4c8d5bc4a0 | ||
|
|
8bbf385c41 | ||
|
|
d7a280bb04 | ||
|
|
f88dc0b5b9 | ||
|
|
34eb16df00 | ||
|
|
0cc62b3447 | ||
|
|
cddd9f7437 | ||
|
|
5acc6470e3 | ||
|
|
e57c6205e2 | ||
|
|
96acb03f73 | ||
|
|
2fefe4fbd6 | ||
|
|
1f9e86f6ea | ||
|
|
a48c8c1efe | ||
|
|
deeeb612b1 | ||
|
|
c62eaeb5a2 | ||
|
|
1a7cb9eaa8 | ||
|
|
3139dc2176 | ||
|
|
25fc0050c3 | ||
|
|
b57aceff15 | ||
|
|
8fd04e44f3 | ||
|
|
869b342db2 | ||
|
|
f0251efec6 | ||
|
|
c7e02efbc7 | ||
|
|
db8bed583f | ||
|
|
2d44014e2e | ||
|
|
452fe71ab8 | ||
|
|
22208e91aa | ||
|
|
d412f61a5f | ||
|
|
c62ec1e786 | ||
|
|
1890f0a983 | ||
|
|
390cf98b0a | ||
|
|
7ddd6f32c5 | ||
|
|
18a488f1b9 | ||
|
|
4c7ad0c539 | ||
|
|
a083f8983b | ||
|
|
cb11ece73d | ||
|
|
fc24f39991 | ||
|
|
4d6c388f37 | ||
|
|
9e3922a972 | ||
|
|
1fcc91e98b | ||
|
|
fe9ee313f9 | ||
|
|
194a266730 | ||
|
|
88862726e3 | ||
|
|
2aa5c93404 | ||
|
|
859e04712f | ||
|
|
f7fe9754fe | ||
|
|
ff68eeae1a | ||
|
|
20f949a9dd | ||
|
|
99c9fec195 | ||
|
|
e7a859438e | ||
|
|
c81c83f432 | ||
|
|
6ad63aedeb | ||
|
|
a1bb5ce50b | ||
|
|
afc89ac727 | ||
|
|
55685fb6a6 | ||
|
|
61f1af7f4e | ||
|
|
16f62bc32a | ||
|
|
a2ec418d53 | ||
|
|
384ebb461f | ||
|
|
0392385037 | ||
|
|
9e020b2852 | ||
|
|
4f7285573f | ||
|
|
59f99f70a4 | ||
|
|
df9f1195e3 | ||
|
|
aba79e88cf | ||
|
|
d0276e149b | ||
|
|
eb95f2331d | ||
|
|
28150042cc | ||
|
|
4bc5af46ab | ||
|
|
2cee1ea895 | ||
|
|
7d0bbafd6c | ||
|
|
dc19127836 | ||
|
|
6db6a7d7b7 | ||
|
|
017deec06e | ||
|
|
d0401fe51f | ||
|
|
92113671ff | ||
|
|
e3d0692dcc | ||
|
|
83e29abea7 | ||
|
|
4a1730ec47 | ||
|
|
691b781c52 | ||
|
|
3b856655c3 | ||
|
|
415cb71294 | ||
|
|
a678b8dbe0 | ||
|
|
9bb3f59496 | ||
|
|
170cba702c | ||
|
|
fe78360948 | ||
|
|
e29a88eeea | ||
|
|
f3eb6d0464 | ||
|
|
f4d4de659c | ||
|
|
1ce0bbfc65 | ||
|
|
0bfe767710 | ||
|
|
4fb39213f2 | ||
|
|
acaa9369f4 | ||
|
|
9591668f87 | ||
|
|
4a38ca7dd1 | ||
|
|
250db00794 | ||
|
|
391b68d357 | ||
|
|
2998371b89 | ||
|
|
12664c6190 | ||
|
|
abe0c45534 | ||
|
|
a104d8ba83 | ||
|
|
a629c04d11 | ||
|
|
6d3b35a41d | ||
|
|
e1ac42f5f8 | ||
|
|
4a7fc6200e | ||
|
|
9e59883a84 | ||
|
|
79d63fce78 | ||
|
|
bee1cdf01c | ||
|
|
c6143934d8 | ||
|
|
c319039a4e | ||
|
|
4b61037170 | ||
|
|
ce92fd15ef | ||
|
|
264edc0030 | ||
|
|
fcb17ae5e7 | ||
|
|
35f82af2e2 | ||
|
|
1a2ecd0599 | ||
|
|
5f8c947105 | ||
|
|
41e6d89a4b | ||
|
|
f01fb62863 | ||
|
|
8f6425b814 | ||
|
|
069a7ce0ad | ||
|
|
8eff1c0d76 | ||
|
|
7404e6da31 | ||
|
|
12ead136a3 | ||
|
|
e3dbda3c54 | ||
|
|
028a161af0 | ||
|
|
454d166ac9 | ||
|
|
41b6a558d5 | ||
|
|
07bb0f13e3 | ||
|
|
f1014ce941 | ||
|
|
0debbdc167 | ||
|
|
0a26629e20 | ||
|
|
9393b1830d | ||
|
|
0df1e48780 | ||
|
|
b8c30ba8a4 | ||
|
|
eafd39fa35 | ||
|
|
86dc92f09a | ||
|
|
e6f5ab8ef4 | ||
|
|
be462db50b | ||
|
|
1dc3d82e59 | ||
|
|
fa2a6d7786 | ||
|
|
04c53c7074 | ||
|
|
1af2cdba8d | ||
|
|
0d7302dc8e | ||
|
|
48ec973695 | ||
|
|
ee868eb607 | ||
|
|
e4da9a62d9 | ||
|
|
c8910bfc40 | ||
|
|
d6e3a67562 | ||
|
|
f7c0a1d97d | ||
|
|
13509136b8 | ||
|
|
bfab2d6af2 | ||
|
|
ae055a7d99 | ||
|
|
592f1110ed | ||
|
|
97875f4e52 | ||
|
|
657261642c | ||
|
|
0da944c3ea | ||
|
|
1b692b5b9a | ||
|
|
b4a9366f73 | ||
|
|
2b3099ac59 | ||
|
|
cd654fb9b8 | ||
|
|
ad834218c2 | ||
|
|
c6a7de2669 | ||
|
|
d374967cb7 |
494
coordinator/backplane.lua
Normal file
494
coordinator/backplane.lua
Normal file
@@ -0,0 +1,494 @@
|
|||||||
|
--
|
||||||
|
-- Coordinator System Core Peripheral Backplane
|
||||||
|
--
|
||||||
|
|
||||||
|
local log = require("scada-common.log")
|
||||||
|
local network = require("scada-common.network")
|
||||||
|
local ppm = require("scada-common.ppm")
|
||||||
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
|
local coordinator = require("coordinator.coordinator")
|
||||||
|
local iocontrol = require("coordinator.iocontrol")
|
||||||
|
local sounder = require("coordinator.sounder")
|
||||||
|
|
||||||
|
local println = util.println
|
||||||
|
|
||||||
|
local log_sys = coordinator.log_sys
|
||||||
|
local log_boot = coordinator.log_boot
|
||||||
|
local log_comms = coordinator.log_comms
|
||||||
|
|
||||||
|
---@class crd_backplane
|
||||||
|
local backplane = {}
|
||||||
|
|
||||||
|
local _bp = {
|
||||||
|
smem = nil, ---@type crd_shared_memory
|
||||||
|
|
||||||
|
wlan_pref = true,
|
||||||
|
lan_iface = "",
|
||||||
|
|
||||||
|
act_nic = nil, ---@type nic
|
||||||
|
wd_nic = nil, ---@type nic|nil
|
||||||
|
wl_nic = nil, ---@type nic|nil
|
||||||
|
|
||||||
|
speaker = nil, ---@type Speaker|nil
|
||||||
|
|
||||||
|
---@class crd_displays
|
||||||
|
displays = {
|
||||||
|
main = nil, ---@type Monitor|nil
|
||||||
|
main_iface = "",
|
||||||
|
flow = nil, ---@type Monitor|nil
|
||||||
|
flow_iface = "",
|
||||||
|
unit_displays = {}, ---@type Monitor[]
|
||||||
|
unit_ifaces = {} ---@type string[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
-- initialize the display peripheral backplane
|
||||||
|
---@param config crd_config
|
||||||
|
---@return boolean success, string error_msg
|
||||||
|
function backplane.init_displays(config)
|
||||||
|
local displays = _bp.displays
|
||||||
|
|
||||||
|
local w, h, _
|
||||||
|
|
||||||
|
log.info("BKPLN: DISPLAY INIT")
|
||||||
|
|
||||||
|
-- monitor configuration verification
|
||||||
|
|
||||||
|
local mon_cfv = util.new_validator()
|
||||||
|
|
||||||
|
mon_cfv.assert_type_str(config.MainDisplay)
|
||||||
|
if not config.DisableFlowView then mon_cfv.assert_type_str(config.FlowDisplay) end
|
||||||
|
|
||||||
|
mon_cfv.assert_eq(#config.UnitDisplays, config.UnitCount)
|
||||||
|
for i = 1, #config.UnitDisplays do
|
||||||
|
mon_cfv.assert_type_str(config.UnitDisplays[i])
|
||||||
|
end
|
||||||
|
|
||||||
|
if not mon_cfv.valid() then
|
||||||
|
return false, "Monitor configuration invalid."
|
||||||
|
end
|
||||||
|
|
||||||
|
-- setup and check display peripherals
|
||||||
|
|
||||||
|
-- main display
|
||||||
|
|
||||||
|
local disp, iface = ppm.get_periph(config.MainDisplay), config.MainDisplay
|
||||||
|
|
||||||
|
displays.main = disp
|
||||||
|
displays.main_iface = iface
|
||||||
|
|
||||||
|
log.info("BKPLN: DISPLAY LINK_" .. util.trinary(disp, "UP", "DOWN") .. " MAIN/" .. iface)
|
||||||
|
|
||||||
|
iocontrol.fp_monitor_state("main", util.trinary(disp, 2, 1))
|
||||||
|
|
||||||
|
if not disp then
|
||||||
|
return false, "Main monitor is not connected."
|
||||||
|
end
|
||||||
|
|
||||||
|
disp.setTextScale(0.5)
|
||||||
|
w, _ = ppm.monitor_block_size(disp.getSize())
|
||||||
|
if w ~= 8 then
|
||||||
|
log.info("BKPLN: DISPLAY MAIN/" .. iface .. " BAD RESOLUTION")
|
||||||
|
return false, util.c("Main monitor width is incorrect (was ", w, ", must be 8).")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- flow display
|
||||||
|
|
||||||
|
if not config.DisableFlowView then
|
||||||
|
disp, iface = ppm.get_periph(config.FlowDisplay), config.FlowDisplay
|
||||||
|
|
||||||
|
displays.flow = disp
|
||||||
|
displays.flow_iface = iface
|
||||||
|
|
||||||
|
log.info("BKPLN: DISPLAY LINK_" .. util.trinary(disp, "UP", "DOWN") .. " FLOW/" .. iface)
|
||||||
|
|
||||||
|
iocontrol.fp_monitor_state("flow", util.trinary(disp, 2, 1))
|
||||||
|
|
||||||
|
if not disp then
|
||||||
|
return false, "Flow monitor is not connected."
|
||||||
|
end
|
||||||
|
|
||||||
|
disp.setTextScale(0.5)
|
||||||
|
w, _ = ppm.monitor_block_size(disp.getSize())
|
||||||
|
if w ~= 8 then
|
||||||
|
log.info("BKPLN: DISPLAY FLOW/" .. iface .. " BAD RESOLUTION")
|
||||||
|
return false, util.c("Flow monitor width is incorrect (was ", w, ", must be 8).")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- unit display(s)
|
||||||
|
|
||||||
|
for i = 1, config.UnitCount do
|
||||||
|
disp, iface = ppm.get_periph(config.UnitDisplays[i]), config.UnitDisplays[i]
|
||||||
|
|
||||||
|
displays.unit_displays[i] = disp
|
||||||
|
displays.unit_ifaces[i] = iface
|
||||||
|
|
||||||
|
log.info("BKPLN: DISPLAY LINK_" .. util.trinary(disp, "UP", "DOWN") .. " UNIT_" .. i .. "/" .. iface)
|
||||||
|
|
||||||
|
iocontrol.fp_monitor_state(i, util.trinary(disp, 2, 1))
|
||||||
|
|
||||||
|
if not disp then
|
||||||
|
return false, "Unit " .. i .. " monitor is not connected."
|
||||||
|
end
|
||||||
|
|
||||||
|
disp.setTextScale(0.5)
|
||||||
|
w, h = ppm.monitor_block_size(disp.getSize())
|
||||||
|
if w ~= 4 or h ~= 4 then
|
||||||
|
log.info("BKPLN: DISPLAY UNIT_" .. i .. "/" .. iface .. " BAD RESOLUTION")
|
||||||
|
return false, util.c("Unit ", i, " monitor size is incorrect (was ", w, " by ", h,", must be 4 by 4).")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
log.info("BKPLN: DISPLAY INIT OK")
|
||||||
|
|
||||||
|
return true, ""
|
||||||
|
end
|
||||||
|
|
||||||
|
-- initialize the system peripheral backplane
|
||||||
|
---@param config crd_config
|
||||||
|
---@param __shared_memory crd_shared_memory
|
||||||
|
---@return boolean success
|
||||||
|
function backplane.init(config, __shared_memory)
|
||||||
|
_bp.smem = __shared_memory
|
||||||
|
_bp.wlan_pref = config.PreferWireless
|
||||||
|
_bp.lan_iface = config.WiredModem
|
||||||
|
|
||||||
|
-- Modem Init
|
||||||
|
|
||||||
|
-- init wired NIC
|
||||||
|
if type(_bp.lan_iface) == "string" then
|
||||||
|
local modem = ppm.get_modem(_bp.lan_iface)
|
||||||
|
local wd_nic = network.nic(modem)
|
||||||
|
|
||||||
|
log.info("BKPLN: WIRED PHY_" .. util.trinary(modem, "UP ", "DOWN ") .. _bp.lan_iface)
|
||||||
|
log_comms("wired comms modem " .. util.trinary(modem, "connected", "not found"))
|
||||||
|
|
||||||
|
_bp.wd_nic = wd_nic
|
||||||
|
_bp.act_nic = wd_nic -- set this as active for now
|
||||||
|
|
||||||
|
wd_nic.closeAll()
|
||||||
|
wd_nic.open(config.CRD_Channel)
|
||||||
|
|
||||||
|
iocontrol.fp_has_wd_modem(modem ~= nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- init wireless NIC(s)
|
||||||
|
if config.WirelessModem then
|
||||||
|
local modem, iface = ppm.get_wireless_modem()
|
||||||
|
local wl_nic = network.nic(modem)
|
||||||
|
|
||||||
|
log.info("BKPLN: WIRELESS PHY_" .. util.trinary(modem, "UP ", "DOWN") .. (iface or ""))
|
||||||
|
log_comms("wireless comms modem " .. util.trinary(modem, "connected", "not found"))
|
||||||
|
|
||||||
|
-- set this as active if connected or if both modems are disconnected and this is preferred
|
||||||
|
if (modem and _bp.wlan_pref) or not (_bp.act_nic and _bp.act_nic.is_connected()) then
|
||||||
|
_bp.act_nic = wl_nic
|
||||||
|
log.info("BKPLN: switched active to preferred wireless")
|
||||||
|
end
|
||||||
|
|
||||||
|
_bp.wl_nic = wl_nic
|
||||||
|
|
||||||
|
wl_nic.closeAll()
|
||||||
|
wl_nic.open(config.CRD_Channel)
|
||||||
|
|
||||||
|
iocontrol.fp_has_wl_modem(modem ~= nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- at least one comms modem is required
|
||||||
|
if not ((_bp.wd_nic and _bp.wd_nic.is_connected()) or (_bp.wl_nic and _bp.wl_nic.is_connected())) then
|
||||||
|
log_comms("no comms modem found")
|
||||||
|
println("startup> no comms modem found")
|
||||||
|
log.warning("BKPLN: no comms modem on startup")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Speaker Init
|
||||||
|
|
||||||
|
_bp.speaker = ppm.get_device("speaker")
|
||||||
|
|
||||||
|
if not _bp.speaker then
|
||||||
|
log_boot("annunciator alarm speaker not found")
|
||||||
|
|
||||||
|
println("startup> speaker not found")
|
||||||
|
log.fatal("BKPLN: no annunciator alarm speaker found")
|
||||||
|
|
||||||
|
return false
|
||||||
|
else
|
||||||
|
log.info("BKPLN: SPEAKER LINK_UP " .. ppm.get_iface(_bp.speaker))
|
||||||
|
log_boot("annunciator alarm speaker connected")
|
||||||
|
|
||||||
|
local sounder_start = util.time_ms()
|
||||||
|
sounder.init(_bp.speaker, config.SpeakerVolume)
|
||||||
|
|
||||||
|
log_boot("tone generation took " .. (util.time_ms() - sounder_start) .. "ms")
|
||||||
|
log_sys("annunciator alarm configured")
|
||||||
|
|
||||||
|
iocontrol.fp_has_speaker(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- get the active NIC
|
||||||
|
function backplane.active_nic() return _bp.act_nic end
|
||||||
|
|
||||||
|
-- get the wireless NIC
|
||||||
|
function backplane.wireless_nic() return _bp.wl_nic end
|
||||||
|
|
||||||
|
-- get the configured displays
|
||||||
|
function backplane.displays() return _bp.displays end
|
||||||
|
|
||||||
|
-- handle a backplane peripheral attach
|
||||||
|
---@param type string
|
||||||
|
---@param device table
|
||||||
|
---@param iface string
|
||||||
|
function backplane.attach(type, device, iface)
|
||||||
|
local MQ__RENDER_CMD = _bp.smem.q_types.MQ__RENDER_CMD
|
||||||
|
local MQ__RENDER_DATA = _bp.smem.q_types.MQ__RENDER_DATA
|
||||||
|
|
||||||
|
local wl_nic, wd_nic = _bp.wl_nic, _bp.wd_nic
|
||||||
|
|
||||||
|
local comms = _bp.smem.crd_sys.coord_comms
|
||||||
|
|
||||||
|
if type == "modem" then
|
||||||
|
---@cast device Modem
|
||||||
|
|
||||||
|
local m_is_wl = device.isWireless()
|
||||||
|
|
||||||
|
log.info(util.c("BKPLN: ", util.trinary(m_is_wl, "WIRELESS", "WIRED"), " PHY_ATTACH ", iface))
|
||||||
|
|
||||||
|
if wd_nic and (_bp.lan_iface == iface) then
|
||||||
|
-- connect this as the wired NIC
|
||||||
|
wd_nic.connect(device)
|
||||||
|
|
||||||
|
log.info("BKPLN: WIRED PHY_UP " .. iface)
|
||||||
|
log_sys("wired comms modem reconnected")
|
||||||
|
|
||||||
|
iocontrol.fp_has_wd_modem(true)
|
||||||
|
|
||||||
|
if (_bp.act_nic ~= wd_nic) and not _bp.wlan_pref then
|
||||||
|
-- switch back to preferred wired
|
||||||
|
_bp.act_nic = wd_nic
|
||||||
|
|
||||||
|
_bp.smem.q.mq_render.push_command(MQ__RENDER_CMD.CLOSE_MAIN_UI)
|
||||||
|
comms.switch_nic(_bp.act_nic)
|
||||||
|
log.info("BKPLN: switched comms to wired modem (preferred)")
|
||||||
|
end
|
||||||
|
elseif wl_nic and (not wl_nic.is_connected()) and m_is_wl then
|
||||||
|
-- connect this as the wireless NIC
|
||||||
|
wl_nic.connect(device)
|
||||||
|
|
||||||
|
log.info("BKPLN: WIRELESS PHY_UP " .. iface)
|
||||||
|
log_sys("wireless comms modem reconnected")
|
||||||
|
|
||||||
|
iocontrol.fp_has_wl_modem(true)
|
||||||
|
|
||||||
|
if (_bp.act_nic ~= wl_nic) and _bp.wlan_pref then
|
||||||
|
-- switch back to preferred wireless
|
||||||
|
_bp.act_nic = wl_nic
|
||||||
|
|
||||||
|
_bp.smem.q.mq_render.push_command(MQ__RENDER_CMD.CLOSE_MAIN_UI)
|
||||||
|
comms.switch_nic(_bp.act_nic)
|
||||||
|
log.info("BKPLN: switched comms to wireless modem (preferred)")
|
||||||
|
end
|
||||||
|
elseif wl_nic and m_is_wl then
|
||||||
|
-- the wireless NIC already has a modem
|
||||||
|
device.closeAll()
|
||||||
|
|
||||||
|
log_sys("standby wireless modem connected")
|
||||||
|
log.info("BKPLN: standby wireless modem connected")
|
||||||
|
else
|
||||||
|
device.closeAll()
|
||||||
|
|
||||||
|
log_sys("unassigned modem connected")
|
||||||
|
log.warning("BKPLN: unassigned modem connected")
|
||||||
|
end
|
||||||
|
elseif type == "monitor" then
|
||||||
|
---@cast device Monitor
|
||||||
|
|
||||||
|
local is_used = false
|
||||||
|
|
||||||
|
log.info("BKPLN: DISPLAY LINK_UP " .. iface)
|
||||||
|
|
||||||
|
if _bp.displays.main_iface == iface then
|
||||||
|
is_used = true
|
||||||
|
|
||||||
|
_bp.displays.main = device
|
||||||
|
|
||||||
|
log.info("BKPLN: main display reconnected")
|
||||||
|
iocontrol.fp_monitor_state("main", 2)
|
||||||
|
elseif _bp.displays.flow_iface == iface then
|
||||||
|
is_used = true
|
||||||
|
|
||||||
|
_bp.displays.flow = device
|
||||||
|
|
||||||
|
log.info("BKPLN: flow display reconnected")
|
||||||
|
iocontrol.fp_monitor_state("flow", 2)
|
||||||
|
else
|
||||||
|
for idx, monitor in ipairs(_bp.displays.unit_ifaces) do
|
||||||
|
if monitor == iface then
|
||||||
|
is_used = true
|
||||||
|
|
||||||
|
_bp.displays.unit_displays[idx] = device
|
||||||
|
|
||||||
|
log.info("BKPLN: unit " .. idx .. " display reconnected")
|
||||||
|
iocontrol.fp_monitor_state(idx, 2)
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- notify renderer if it is using it
|
||||||
|
if is_used then
|
||||||
|
log_sys(util.c("configured monitor ", iface, " reconnected"))
|
||||||
|
_bp.smem.q.mq_render.push_data(MQ__RENDER_DATA.MON_CONNECT, iface)
|
||||||
|
else
|
||||||
|
log_sys(util.c("unused monitor ", iface, " connected"))
|
||||||
|
end
|
||||||
|
elseif type == "speaker" then
|
||||||
|
---@cast device Speaker
|
||||||
|
|
||||||
|
log.info("BKPLN: SPEAKER LINK_UP " .. iface)
|
||||||
|
|
||||||
|
sounder.reconnect(device)
|
||||||
|
|
||||||
|
log_sys("alarm sounder speaker reconnected")
|
||||||
|
|
||||||
|
iocontrol.fp_has_speaker(true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- handle a backplane peripheral detach
|
||||||
|
---@param type string
|
||||||
|
---@param device table
|
||||||
|
---@param iface string
|
||||||
|
function backplane.detach(type, device, iface)
|
||||||
|
local MQ__RENDER_CMD = _bp.smem.q_types.MQ__RENDER_CMD
|
||||||
|
local MQ__RENDER_DATA = _bp.smem.q_types.MQ__RENDER_DATA
|
||||||
|
|
||||||
|
local wl_nic, wd_nic = _bp.wl_nic, _bp.wd_nic
|
||||||
|
|
||||||
|
local comms = _bp.smem.crd_sys.coord_comms
|
||||||
|
|
||||||
|
if type == "modem" then
|
||||||
|
---@cast device Modem
|
||||||
|
|
||||||
|
log.info(util.c("BKPLN: PHY_DETACH ", iface))
|
||||||
|
|
||||||
|
if wd_nic and wd_nic.is_modem(device) then
|
||||||
|
wd_nic.disconnect()
|
||||||
|
log.info("BKPLN: WIRED PHY_DOWN " .. iface)
|
||||||
|
|
||||||
|
iocontrol.fp_has_wd_modem(false)
|
||||||
|
elseif wl_nic and wl_nic.is_modem(device) then
|
||||||
|
wl_nic.disconnect()
|
||||||
|
log.info("BKPLN: WIRELESS PHY_DOWN " .. iface)
|
||||||
|
|
||||||
|
iocontrol.fp_has_wl_modem(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- we only care if this is our active comms modem
|
||||||
|
if _bp.act_nic.is_modem(device) then
|
||||||
|
log_sys("active comms modem disconnected")
|
||||||
|
log.warning("BKPLN: active comms modem disconnected")
|
||||||
|
|
||||||
|
-- failover and try to find a new comms modem
|
||||||
|
if _bp.act_nic == wl_nic then
|
||||||
|
-- wireless active disconnected
|
||||||
|
-- try to find another wireless modem, otherwise switch to wired
|
||||||
|
local modem, m_iface = ppm.get_wireless_modem()
|
||||||
|
if wl_nic and modem then
|
||||||
|
log_sys("found another wireless modem, using it for comms")
|
||||||
|
log.info("BKPLN: found another wireless modem, using it for comms")
|
||||||
|
|
||||||
|
wl_nic.connect(modem)
|
||||||
|
|
||||||
|
log.info("BKPLN: WIRELESS PHY_UP " .. m_iface)
|
||||||
|
|
||||||
|
iocontrol.fp_has_wl_modem(true)
|
||||||
|
elseif wd_nic and wd_nic.is_connected() then
|
||||||
|
_bp.act_nic = wd_nic
|
||||||
|
|
||||||
|
_bp.smem.q.mq_render.push_command(MQ__RENDER_CMD.CLOSE_MAIN_UI)
|
||||||
|
comms.switch_nic(_bp.act_nic)
|
||||||
|
log.info("BKPLN: switched comms to wired modem")
|
||||||
|
else
|
||||||
|
-- close out main UI
|
||||||
|
_bp.smem.q.mq_render.push_command(MQ__RENDER_CMD.CLOSE_MAIN_UI)
|
||||||
|
|
||||||
|
-- alert user to status
|
||||||
|
log_sys("awaiting comms modem reconnect...")
|
||||||
|
end
|
||||||
|
elseif wl_nic and wl_nic.is_connected() then
|
||||||
|
-- wired active disconnected, wireless available
|
||||||
|
_bp.act_nic = wl_nic
|
||||||
|
|
||||||
|
_bp.smem.q.mq_render.push_command(MQ__RENDER_CMD.CLOSE_MAIN_UI)
|
||||||
|
comms.switch_nic(_bp.act_nic)
|
||||||
|
log.info("BKPLN: switched comms to wireless modem")
|
||||||
|
else
|
||||||
|
-- wired active disconnected, wireless unavailable
|
||||||
|
end
|
||||||
|
elseif wd_nic and wd_nic.is_modem(device) then
|
||||||
|
-- wired, but not active
|
||||||
|
log_sys("standby wired modem disconnected")
|
||||||
|
log.info("BKPLN: standby wired modem disconnected")
|
||||||
|
elseif wl_nic and wl_nic.is_modem(device) then
|
||||||
|
-- wireless, but not active
|
||||||
|
log_sys("standby wireless modem disconnected")
|
||||||
|
log.info("BKPLN: standby wireless modem disconnected")
|
||||||
|
else
|
||||||
|
log_sys("unassigned modem disconnected")
|
||||||
|
log.warning("BKPLN: unassigned modem disconnected")
|
||||||
|
end
|
||||||
|
elseif type == "monitor" then
|
||||||
|
---@cast device Monitor
|
||||||
|
|
||||||
|
local is_used = false
|
||||||
|
|
||||||
|
log.info("BKPLN: DISPLAY LINK_DOWN " .. iface)
|
||||||
|
|
||||||
|
if _bp.displays.main == device then
|
||||||
|
is_used = true
|
||||||
|
|
||||||
|
log.info("BKPLN: main display disconnected")
|
||||||
|
iocontrol.fp_monitor_state("main", 1)
|
||||||
|
elseif _bp.displays.flow == device then
|
||||||
|
is_used = true
|
||||||
|
|
||||||
|
log.info("BKPLN: flow display disconnected")
|
||||||
|
iocontrol.fp_monitor_state("flow", 1)
|
||||||
|
else
|
||||||
|
for idx, monitor in pairs(_bp.displays.unit_displays) do
|
||||||
|
if monitor == device then
|
||||||
|
is_used = true
|
||||||
|
|
||||||
|
log.info("BKPLN: unit " .. idx .. " display disconnected")
|
||||||
|
iocontrol.fp_monitor_state(idx, 1)
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- notify renderer if it was using it
|
||||||
|
if is_used then
|
||||||
|
log_sys("lost a configured monitor")
|
||||||
|
_bp.smem.q.mq_render.push_data(MQ__RENDER_DATA.MON_DISCONNECT, iface)
|
||||||
|
else
|
||||||
|
log_sys("lost an unused monitor")
|
||||||
|
end
|
||||||
|
elseif type == "speaker" then
|
||||||
|
---@cast device Speaker
|
||||||
|
|
||||||
|
log.info("BKPLN: SPEAKER LINK_DOWN " .. iface)
|
||||||
|
|
||||||
|
log_sys("alarm sounder speaker disconnected")
|
||||||
|
|
||||||
|
iocontrol.fp_has_speaker(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
return backplane
|
||||||
@@ -149,18 +149,39 @@ local function handle_timeout()
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- attempt a connection to the supervisor to get cooling info
|
-- attempt a connection to the supervisor to get cooling info
|
||||||
local function sv_connect()
|
---@param cfg crd_config current configuration for modem settings
|
||||||
|
local function sv_connect(cfg)
|
||||||
self.sv_conn_button.disable()
|
self.sv_conn_button.disable()
|
||||||
self.sv_conn_detail.set_value("")
|
self.sv_conn_detail.set_value("")
|
||||||
|
|
||||||
local modem = ppm.get_wireless_modem()
|
local modem = nil
|
||||||
|
|
||||||
|
if cfg.WirelessModem then
|
||||||
|
modem = ppm.get_wireless_modem()
|
||||||
|
|
||||||
|
if cfg.WiredModem then
|
||||||
|
local wd_modem = ppm.get_modem(cfg.WiredModem)
|
||||||
|
|
||||||
|
if cfg.PreferWireless then
|
||||||
|
if not modem then
|
||||||
|
modem = wd_modem
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if wd_modem then
|
||||||
|
modem = wd_modem
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif cfg.WiredModem then
|
||||||
|
modem = ppm.get_modem(cfg.WiredModem)
|
||||||
|
end
|
||||||
|
|
||||||
if modem == nil then
|
if modem == nil then
|
||||||
self.sv_conn_status.set_value("Please connect an ender/wireless modem.")
|
self.sv_conn_status.set_value("Could not find configured modem(s).")
|
||||||
else
|
else
|
||||||
self.sv_conn_status.set_value("Modem found, connecting...")
|
self.sv_conn_status.set_value("Modem found, connecting...")
|
||||||
if self.nic == nil then self.nic = network.nic(modem) end
|
|
||||||
|
|
||||||
self.nic.closeAll()
|
self.nic = network.nic(modem)
|
||||||
self.nic.open(self.tmp_cfg.CRD_Channel)
|
self.nic.open(self.tmp_cfg.CRD_Channel)
|
||||||
|
|
||||||
self.sv_addr = comms.BROADCAST
|
self.sv_addr = comms.BROADCAST
|
||||||
@@ -209,7 +230,7 @@ function facility.create(tool_ctl, main_pane, cfg_sys, fac_cfg, style)
|
|||||||
self.sv_conn_status = TextBox{parent=fac_c_1,x=11,y=9,text=""}
|
self.sv_conn_status = TextBox{parent=fac_c_1,x=11,y=9,text=""}
|
||||||
self.sv_conn_detail = TextBox{parent=fac_c_1,x=1,y=11,height=2,text=""}
|
self.sv_conn_detail = TextBox{parent=fac_c_1,x=1,y=11,height=2,text=""}
|
||||||
|
|
||||||
self.sv_conn_button = PushButton{parent=fac_c_1,x=1,y=9,text="Connect",min_width=9,callback=function()sv_connect()end,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
self.sv_conn_button = PushButton{parent=fac_c_1,x=1,y=9,text="Connect",min_width=9,callback=function()sv_connect(tmp_cfg)end,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
||||||
|
|
||||||
local function sv_skip()
|
local function sv_skip()
|
||||||
tcd.abort(handle_timeout)
|
tcd.abort(handle_timeout)
|
||||||
|
|||||||
@@ -234,19 +234,23 @@ function hmi.create(tool_ctl, main_pane, cfg_sys, divs, style)
|
|||||||
|
|
||||||
TextBox{parent=crd_cfg,x=1,y=2,text=" Coordinator UI Configuration",fg_bg=cpair(colors.black,colors.lime)}
|
TextBox{parent=crd_cfg,x=1,y=2,text=" Coordinator UI Configuration",fg_bg=cpair(colors.black,colors.lime)}
|
||||||
|
|
||||||
TextBox{parent=crd_c_1,x=1,y=1,height=3,text="Configure the UI interface options below if you wish to customize formats."}
|
TextBox{parent=crd_c_1,x=1,y=1,height=2,text="You can customize the UI with the interface options below."}
|
||||||
|
|
||||||
TextBox{parent=crd_c_1,x=1,y=4,text="Clock Time Format"}
|
TextBox{parent=crd_c_1,x=1,y=4,text="Clock Time Format"}
|
||||||
tool_ctl.clock_fmt = RadioButton{parent=crd_c_1,x=1,y=5,default=util.trinary(ini_cfg.Time24Hour,1,2),options={"24-Hour","12-Hour"},callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime}
|
tool_ctl.clock_fmt = RadioButton{parent=crd_c_1,x=1,y=5,default=util.trinary(ini_cfg.Time24Hour,1,2),options={"24-Hour","12-Hour"},radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime}
|
||||||
|
|
||||||
|
TextBox{parent=crd_c_1,x=20,y=4,text="Po/Pu Pellet Color"}
|
||||||
|
tool_ctl.pellet_color = RadioButton{parent=crd_c_1,x=20,y=5,default=util.trinary(ini_cfg.GreenPuPellet,1,2),options={"Green Pu/Cyan Po","Cyan Pu/Green Po (Mek 10.4+)"},radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime}
|
||||||
|
|
||||||
TextBox{parent=crd_c_1,x=1,y=8,text="Temperature Scale"}
|
TextBox{parent=crd_c_1,x=1,y=8,text="Temperature Scale"}
|
||||||
tool_ctl.temp_scale = RadioButton{parent=crd_c_1,x=1,y=9,default=ini_cfg.TempScale,options=types.TEMP_SCALE_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime}
|
tool_ctl.temp_scale = RadioButton{parent=crd_c_1,x=1,y=9,default=ini_cfg.TempScale,options=types.TEMP_SCALE_NAMES,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime}
|
||||||
|
|
||||||
TextBox{parent=crd_c_1,x=24,y=8,text="Energy Scale"}
|
TextBox{parent=crd_c_1,x=20,y=8,text="Energy Scale"}
|
||||||
tool_ctl.energy_scale = RadioButton{parent=crd_c_1,x=24,y=9,default=ini_cfg.EnergyScale,options=types.ENERGY_SCALE_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime}
|
tool_ctl.energy_scale = RadioButton{parent=crd_c_1,x=20,y=9,default=ini_cfg.EnergyScale,options=types.ENERGY_SCALE_NAMES,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime}
|
||||||
|
|
||||||
local function submit_ui_opts()
|
local function submit_ui_opts()
|
||||||
tmp_cfg.Time24Hour = tool_ctl.clock_fmt.get_value() == 1
|
tmp_cfg.Time24Hour = tool_ctl.clock_fmt.get_value() == 1
|
||||||
|
tmp_cfg.GreenPuPellet = tool_ctl.pellet_color.get_value() == 1
|
||||||
tmp_cfg.TempScale = tool_ctl.temp_scale.get_value()
|
tmp_cfg.TempScale = tool_ctl.temp_scale.get_value()
|
||||||
tmp_cfg.EnergyScale = tool_ctl.energy_scale.get_value()
|
tmp_cfg.EnergyScale = tool_ctl.energy_scale.get_value()
|
||||||
main_pane.set_value(7)
|
main_pane.set_value(7)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
local comms = require("scada-common.comms")
|
local comms = require("scada-common.comms")
|
||||||
local log = require("scada-common.log")
|
local log = require("scada-common.log")
|
||||||
local network = require("scada-common.network")
|
local network = require("scada-common.network")
|
||||||
|
local ppm = require("scada-common.ppm")
|
||||||
local types = require("scada-common.types")
|
local types = require("scada-common.types")
|
||||||
local util = require("scada-common.util")
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
@@ -31,6 +32,9 @@ local RIGHT = core.ALIGN.RIGHT
|
|||||||
local self = {
|
local self = {
|
||||||
importing_legacy = false,
|
importing_legacy = false,
|
||||||
|
|
||||||
|
api_en = nil, ---@type Checkbox
|
||||||
|
pkt_chan = nil, ---@type NumberField
|
||||||
|
api_timeout = nil, ---@type NumberField
|
||||||
show_auth_key = nil, ---@type function
|
show_auth_key = nil, ---@type function
|
||||||
show_key_btn = nil, ---@type PushButton
|
show_key_btn = nil, ---@type PushButton
|
||||||
auth_key_textbox = nil, ---@type TextBox
|
auth_key_textbox = nil, ---@type TextBox
|
||||||
@@ -63,100 +67,204 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style)
|
|||||||
local net_c_2 = Div{parent=net_cfg,x=2,y=4,width=49}
|
local net_c_2 = Div{parent=net_cfg,x=2,y=4,width=49}
|
||||||
local net_c_3 = Div{parent=net_cfg,x=2,y=4,width=49}
|
local net_c_3 = Div{parent=net_cfg,x=2,y=4,width=49}
|
||||||
local net_c_4 = Div{parent=net_cfg,x=2,y=4,width=49}
|
local net_c_4 = Div{parent=net_cfg,x=2,y=4,width=49}
|
||||||
|
local net_c_5 = Div{parent=net_cfg,x=2,y=4,width=49}
|
||||||
|
local net_c_6 = Div{parent=net_cfg,x=2,y=4,width=49}
|
||||||
|
|
||||||
local net_pane = MultiPane{parent=net_cfg,x=1,y=4,panes={net_c_1,net_c_2,net_c_3,net_c_4}}
|
local net_pane = MultiPane{parent=net_cfg,x=1,y=4,panes={net_c_1,net_c_2,net_c_3,net_c_4,net_c_5,net_c_6}}
|
||||||
|
|
||||||
TextBox{parent=net_cfg,x=1,y=2,text=" Network Configuration",fg_bg=cpair(colors.black,colors.lightBlue)}
|
TextBox{parent=net_cfg,x=1,y=2,text=" Network Configuration",fg_bg=cpair(colors.black,colors.lightBlue)}
|
||||||
|
|
||||||
TextBox{parent=net_c_1,x=1,y=1,text="Please set the network channels below."}
|
TextBox{parent=net_c_1,x=1,y=1,text="Please select the network interface(s)."}
|
||||||
TextBox{parent=net_c_1,x=1,y=3,height=4,text="Each of the 5 uniquely named channels, including the 3 below, must be the same for each device in this SCADA network. For multiplayer servers, it is recommended to not use the default channels.",fg_bg=g_lg_fg_bg}
|
TextBox{parent=net_c_1,x=41,y=1,text="new!",fg_bg=cpair(colors.red,colors._INHERIT)} ---@todo remove NEW tag on next revision
|
||||||
|
|
||||||
TextBox{parent=net_c_1,x=1,y=8,width=18,text="Supervisor Channel"}
|
local function on_wired_change(_) tool_ctl.gen_modem_list() end
|
||||||
local svr_chan = NumberField{parent=net_c_1,x=21,y=8,width=7,default=ini_cfg.SVR_Channel,min=1,max=65535,fg_bg=bw_fg_bg}
|
|
||||||
TextBox{parent=net_c_1,x=29,y=8,height=4,text="[SVR_CHANNEL]",fg_bg=g_lg_fg_bg}
|
|
||||||
|
|
||||||
TextBox{parent=net_c_1,x=1,y=10,width=19,text="Coordinator Channel"}
|
local wireless = Checkbox{parent=net_c_1,x=1,y=3,label="Wireless/Ender Modem",default=ini_cfg.WirelessModem,box_fg_bg=cpair(colors.lightBlue,colors.black)}
|
||||||
local crd_chan = NumberField{parent=net_c_1,x=21,y=10,width=7,default=ini_cfg.CRD_Channel,min=1,max=65535,fg_bg=bw_fg_bg}
|
TextBox{parent=net_c_1,x=24,y=3,text="(required for Pocket)",fg_bg=g_lg_fg_bg}
|
||||||
TextBox{parent=net_c_1,x=29,y=10,height=4,text="[CRD_CHANNEL]",fg_bg=g_lg_fg_bg}
|
local wired = Checkbox{parent=net_c_1,x=1,y=5,label="Wired Modem",default=ini_cfg.WiredModem~=false,box_fg_bg=cpair(colors.lightBlue,colors.black),callback=on_wired_change}
|
||||||
|
TextBox{parent=net_c_1,x=3,y=6,text="this one MUST ONLY connect to SCADA computers",fg_bg=cpair(colors.red,colors._INHERIT)}
|
||||||
|
TextBox{parent=net_c_1,x=3,y=7,text="connecting it to peripherals will cause issues",fg_bg=g_lg_fg_bg}
|
||||||
|
local modem_list = ListBox{parent=net_c_1,x=1,y=8,height=5,width=49,scroll_height=100,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
|
||||||
|
|
||||||
TextBox{parent=net_c_1,x=1,y=12,width=14,text="Pocket Channel"}
|
local modem_err = TextBox{parent=net_c_1,x=8,y=14,width=35,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||||
local pkt_chan = NumberField{parent=net_c_1,x=21,y=12,width=7,default=ini_cfg.PKT_Channel,min=1,max=65535,fg_bg=bw_fg_bg}
|
|
||||||
TextBox{parent=net_c_1,x=29,y=12,height=4,text="[PKT_CHANNEL]",fg_bg=g_lg_fg_bg}
|
|
||||||
|
|
||||||
local chan_err = TextBox{parent=net_c_1,x=8,y=14,width=35,text="Please set all channels.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
local function submit_interfaces()
|
||||||
|
tmp_cfg.WirelessModem = wireless.get_value()
|
||||||
|
|
||||||
|
if not wired.get_value() then
|
||||||
|
tmp_cfg.WiredModem = false
|
||||||
|
tool_ctl.gen_modem_list()
|
||||||
|
end
|
||||||
|
|
||||||
|
if not (wired.get_value() or wireless.get_value()) then
|
||||||
|
modem_err.set_value("Please select a modem type.")
|
||||||
|
modem_err.show()
|
||||||
|
elseif wired.get_value() and type(tmp_cfg.WiredModem) ~= "string" then
|
||||||
|
modem_err.set_value("Please select a wired modem.")
|
||||||
|
modem_err.show()
|
||||||
|
else
|
||||||
|
if tmp_cfg.WirelessModem and tmp_cfg.WiredModem then
|
||||||
|
self.wl_pref.enable()
|
||||||
|
else
|
||||||
|
self.wl_pref.set_value(tmp_cfg.WirelessModem)
|
||||||
|
self.wl_pref.disable()
|
||||||
|
end
|
||||||
|
|
||||||
|
if not tmp_cfg.WirelessModem then
|
||||||
|
self.api_en.set_value(false)
|
||||||
|
self.api_en.disable()
|
||||||
|
else
|
||||||
|
self.api_en.enable()
|
||||||
|
end
|
||||||
|
|
||||||
|
net_pane.set_value(2)
|
||||||
|
modem_err.hide(true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
PushButton{parent=net_c_1,x=1,y=14,text="\x1b Back",callback=function()main_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
PushButton{parent=net_c_1,x=44,y=14,text="Next \x1a",callback=submit_interfaces,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
|
TextBox{parent=net_c_2,text="If you selected multiple interfaces, please specify if this device should prefer wireless or otherwise wired. The preferred interface is switched too when reconnected even if failover has succeeded onto the fallback interface."}
|
||||||
|
self.wl_pref = Checkbox{parent=net_c_2,y=7,label="Prefer Wireless",default=ini_cfg.PreferWireless,box_fg_bg=cpair(colors.lightBlue,colors.black),disable_fg_bg=g_lg_fg_bg}
|
||||||
|
TextBox{parent=net_c_2,x=19,y=7,text="new!",fg_bg=cpair(colors.red,colors._INHERIT)} ---@todo remove NEW tag on next revision
|
||||||
|
|
||||||
|
TextBox{parent=net_c_2,y=9,text="With a wireless modem, configure Pocket access."}
|
||||||
|
self.api_en = Checkbox{parent=net_c_2,y=11,label="Enable Pocket Access",default=ini_cfg.API_Enabled,box_fg_bg=cpair(colors.lightBlue,colors.black),disable_fg_bg=g_lg_fg_bg}
|
||||||
|
TextBox{parent=net_c_2,x=24,y=11,text="new!",fg_bg=cpair(colors.red,colors._INHERIT)} ---@todo remove NEW tag on next revision
|
||||||
|
|
||||||
|
local function submit_net_cfg_opts()
|
||||||
|
if tmp_cfg.WirelessModem and tmp_cfg.WiredModem then
|
||||||
|
tmp_cfg.PreferWireless = self.wl_pref.get_value()
|
||||||
|
else
|
||||||
|
tmp_cfg.PreferWireless = tmp_cfg.WirelessModem
|
||||||
|
end
|
||||||
|
|
||||||
|
tmp_cfg.API_Enabled = tri(tmp_cfg.WirelessModem, self.api_en.get_value(), false)
|
||||||
|
|
||||||
|
if tmp_cfg.API_Enabled then
|
||||||
|
self.pkt_chan.enable()
|
||||||
|
self.api_timeout.enable()
|
||||||
|
else
|
||||||
|
self.pkt_chan.disable()
|
||||||
|
self.api_timeout.disable()
|
||||||
|
end
|
||||||
|
|
||||||
|
net_pane.set_value(3)
|
||||||
|
end
|
||||||
|
|
||||||
|
PushButton{parent=net_c_2,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
PushButton{parent=net_c_2,x=44,y=14,text="Next \x1a",callback=submit_net_cfg_opts,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
|
TextBox{parent=net_c_3,x=1,y=1,text="Please set the network channels below."}
|
||||||
|
TextBox{parent=net_c_3,x=1,y=3,height=4,text="Each of the 5 uniquely named channels, including the 3 below, must be the same for each device in this SCADA network. For multiplayer servers, it is recommended to not use the default channels.",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
|
TextBox{parent=net_c_3,x=1,y=8,width=18,text="Supervisor Channel"}
|
||||||
|
local svr_chan = NumberField{parent=net_c_3,x=21,y=8,width=7,default=ini_cfg.SVR_Channel,min=1,max=65535,fg_bg=bw_fg_bg}
|
||||||
|
TextBox{parent=net_c_3,x=29,y=8,height=4,text="[SVR_CHANNEL]",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
|
TextBox{parent=net_c_3,x=1,y=10,width=19,text="Coordinator Channel"}
|
||||||
|
local crd_chan = NumberField{parent=net_c_3,x=21,y=10,width=7,default=ini_cfg.CRD_Channel,min=1,max=65535,fg_bg=bw_fg_bg}
|
||||||
|
TextBox{parent=net_c_3,x=29,y=10,height=4,text="[CRD_CHANNEL]",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
|
TextBox{parent=net_c_3,x=1,y=12,width=14,text="Pocket Channel"}
|
||||||
|
self.pkt_chan = NumberField{parent=net_c_3,x=21,y=12,width=7,default=ini_cfg.PKT_Channel,min=1,max=65535,fg_bg=bw_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)}
|
||||||
|
TextBox{parent=net_c_3,x=29,y=12,height=4,text="[PKT_CHANNEL]",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
|
local chan_err = TextBox{parent=net_c_3,x=8,y=14,width=35,text="Please set all channels.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||||
|
|
||||||
local function submit_channels()
|
local function submit_channels()
|
||||||
local svr_c, crd_c, pkt_c = tonumber(svr_chan.get_value()), tonumber(crd_chan.get_value()), tonumber(pkt_chan.get_value())
|
local svr_c, crd_c, pkt_c = tonumber(svr_chan.get_value()), tonumber(crd_chan.get_value()), tonumber(self.pkt_chan.get_value())
|
||||||
|
|
||||||
|
if not tmp_cfg.API_Enabled then pkt_c = tmp_cfg.PKT_Channel or 16244 end
|
||||||
|
|
||||||
if svr_c ~= nil and crd_c ~= nil and pkt_c ~= nil then
|
if svr_c ~= nil and crd_c ~= nil and pkt_c ~= nil then
|
||||||
tmp_cfg.SVR_Channel, tmp_cfg.CRD_Channel, tmp_cfg.PKT_Channel = svr_c, crd_c, pkt_c
|
tmp_cfg.SVR_Channel, tmp_cfg.CRD_Channel, tmp_cfg.PKT_Channel = svr_c, crd_c, pkt_c
|
||||||
net_pane.set_value(2)
|
net_pane.set_value(4)
|
||||||
chan_err.hide(true)
|
chan_err.hide(true)
|
||||||
else chan_err.show() end
|
else chan_err.show() end
|
||||||
end
|
end
|
||||||
|
|
||||||
PushButton{parent=net_c_1,x=1,y=14,text="\x1b Back",callback=function()main_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=net_c_3,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
PushButton{parent=net_c_1,x=44,y=14,text="Next \x1a",callback=submit_channels,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=net_c_3,x=44,y=14,text="Next \x1a",callback=submit_channels,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=net_c_2,x=1,y=1,text="Please set the connection timeouts below."}
|
TextBox{parent=net_c_4,x=1,y=1,text="Please set the connection timeouts below."}
|
||||||
TextBox{parent=net_c_2,x=1,y=3,height=4,text="You generally should not need to modify these. On slow servers, you can try to increase this to make the system wait longer before assuming a disconnection. The default for all is 5 seconds.",fg_bg=g_lg_fg_bg}
|
TextBox{parent=net_c_4,x=1,y=3,height=4,text="You generally should not need to modify these. On slow servers, you can try to increase this to make the system wait longer before assuming a disconnection. The default for all is 5 seconds.",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=net_c_2,x=1,y=8,width=19,text="Supervisor Timeout"}
|
TextBox{parent=net_c_4,x=1,y=8,width=19,text="Supervisor Timeout"}
|
||||||
local svr_timeout = NumberField{parent=net_c_2,x=20,y=8,width=7,default=ini_cfg.SVR_Timeout,min=2,max=25,max_chars=6,max_frac_digits=2,allow_decimal=true,fg_bg=bw_fg_bg}
|
local svr_timeout = NumberField{parent=net_c_4,x=20,y=8,width=7,default=ini_cfg.SVR_Timeout,min=2,max=25,max_chars=6,max_frac_digits=2,allow_decimal=true,fg_bg=bw_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=net_c_2,x=1,y=10,width=14,text="Pocket Timeout"}
|
TextBox{parent=net_c_4,x=1,y=10,width=14,text="Pocket Timeout"}
|
||||||
local api_timeout = NumberField{parent=net_c_2,x=20,y=10,width=7,default=ini_cfg.API_Timeout,min=2,max=25,max_chars=6,max_frac_digits=2,allow_decimal=true,fg_bg=bw_fg_bg}
|
self.api_timeout = NumberField{parent=net_c_4,x=20,y=10,width=7,default=ini_cfg.API_Timeout,min=2,max=25,max_chars=6,max_frac_digits=2,allow_decimal=true,fg_bg=bw_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)}
|
||||||
|
|
||||||
TextBox{parent=net_c_2,x=28,y=8,height=4,width=7,text="seconds\n\nseconds",fg_bg=g_lg_fg_bg}
|
TextBox{parent=net_c_4,x=28,y=8,height=4,width=7,text="seconds\n\nseconds",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
local ct_err = TextBox{parent=net_c_2,x=8,y=14,width=35,text="Please set all connection timeouts.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
local ct_err = TextBox{parent=net_c_4,x=8,y=14,width=35,text="Please set all connection timeouts.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||||
|
|
||||||
local function submit_timeouts()
|
local function submit_timeouts()
|
||||||
local svr_cto, api_cto = tonumber(svr_timeout.get_value()), tonumber(api_timeout.get_value())
|
local svr_cto, api_cto = tonumber(svr_timeout.get_value()), tonumber(self.api_timeout.get_value())
|
||||||
|
|
||||||
|
if not tmp_cfg.API_Enabled then api_cto = tmp_cfg.API_Timeout or 5 end
|
||||||
|
|
||||||
if svr_cto ~= nil and api_cto ~= nil then
|
if svr_cto ~= nil and api_cto ~= nil then
|
||||||
tmp_cfg.SVR_Timeout, tmp_cfg.API_Timeout = svr_cto, api_cto
|
tmp_cfg.SVR_Timeout, tmp_cfg.API_Timeout = svr_cto, api_cto
|
||||||
net_pane.set_value(3)
|
|
||||||
|
if tmp_cfg.WirelessModem then
|
||||||
|
net_pane.set_value(5)
|
||||||
|
else
|
||||||
|
tmp_cfg.TrustedRange = 0
|
||||||
|
tmp_cfg.AuthKey = ""
|
||||||
|
|
||||||
|
network.deinit_mac()
|
||||||
|
|
||||||
|
-- prep supervisor connection screen
|
||||||
|
tool_ctl.init_sv_connect_ui()
|
||||||
|
|
||||||
|
main_pane.set_value(3)
|
||||||
|
end
|
||||||
|
|
||||||
ct_err.hide(true)
|
ct_err.hide(true)
|
||||||
else ct_err.show() end
|
else ct_err.show() end
|
||||||
end
|
end
|
||||||
|
|
||||||
PushButton{parent=net_c_2,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=net_c_4,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(3)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
PushButton{parent=net_c_2,x=44,y=14,text="Next \x1a",callback=submit_timeouts,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=net_c_4,x=44,y=14,text="Next \x1a",callback=submit_timeouts,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=net_c_3,x=1,y=1,text="Please set the trusted range below."}
|
TextBox{parent=net_c_5,x=1,y=1,text="Please set the wireless trusted range below."}
|
||||||
TextBox{parent=net_c_3,x=1,y=3,height=3,text="Setting this to a value larger than 0 prevents connections with devices that many meters (blocks) away in any direction.",fg_bg=g_lg_fg_bg}
|
TextBox{parent=net_c_5,x=1,y=3,height=3,text="Setting this to a value larger than 0 prevents wireless connections with devices that many meters (blocks) away in any direction.",fg_bg=g_lg_fg_bg}
|
||||||
TextBox{parent=net_c_3,x=1,y=7,height=2,text="This is optional. You can disable this functionality by setting the value to 0.",fg_bg=g_lg_fg_bg}
|
TextBox{parent=net_c_5,x=1,y=7,height=2,text="This is optional. You can disable this functionality by setting the value to 0.",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
local range = NumberField{parent=net_c_3,x=1,y=10,width=10,default=ini_cfg.TrustedRange,min=0,max_chars=20,allow_decimal=true,fg_bg=bw_fg_bg}
|
local range = NumberField{parent=net_c_5,x=1,y=10,width=10,default=ini_cfg.TrustedRange,min=0,max_chars=20,allow_decimal=true,fg_bg=bw_fg_bg}
|
||||||
|
|
||||||
local tr_err = TextBox{parent=net_c_3,x=8,y=14,width=35,text="Please set the trusted range.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
local tr_err = TextBox{parent=net_c_5,x=8,y=14,width=35,text="Please set the trusted range.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||||
|
|
||||||
local function submit_tr()
|
local function submit_tr()
|
||||||
local range_val = tonumber(range.get_value())
|
local range_val = tonumber(range.get_value())
|
||||||
if range_val ~= nil then
|
if range_val ~= nil then
|
||||||
tmp_cfg.TrustedRange = range_val
|
tmp_cfg.TrustedRange = range_val
|
||||||
comms.set_trusted_range(range_val)
|
comms.set_trusted_range(range_val)
|
||||||
net_pane.set_value(4)
|
net_pane.set_value(6)
|
||||||
tr_err.hide(true)
|
tr_err.hide(true)
|
||||||
else tr_err.show() end
|
else tr_err.show() end
|
||||||
end
|
end
|
||||||
|
|
||||||
PushButton{parent=net_c_3,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=net_c_5,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(4)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
PushButton{parent=net_c_3,x=44,y=14,text="Next \x1a",callback=submit_tr,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=net_c_5,x=44,y=14,text="Next \x1a",callback=submit_tr,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=net_c_4,x=1,y=1,height=2,text="Optionally, set the facility authentication key below. Do NOT use one of your passwords."}
|
TextBox{parent=net_c_6,x=1,y=1,height=2,text="Optionally, set the facility authentication key below. Do NOT use one of your passwords."}
|
||||||
TextBox{parent=net_c_4,x=1,y=4,height=6,text="This enables verifying that messages are authentic, so it is intended for security on multiplayer servers. All devices on the same network MUST use the same key if any device has a key. This does result in some extra computation (can slow things down).",fg_bg=g_lg_fg_bg}
|
TextBox{parent=net_c_6,x=1,y=4,height=6,text="This enables verifying that messages are authentic, so it is intended for wireless security on multiplayer servers. All devices on the same wireless network MUST use the same key if any device has a key. This does result in some extra computation (can slow things down).",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=net_c_4,x=1,y=11,text="Facility Auth Key"}
|
TextBox{parent=net_c_6,x=1,y=11,text="Auth Key (Wireless Only, Not Used for Wired)"}
|
||||||
local key, _ = TextField{parent=net_c_4,x=1,y=12,max_len=64,value=ini_cfg.AuthKey,width=32,height=1,fg_bg=bw_fg_bg}
|
local key, _ = TextField{parent=net_c_6,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) key.censor(tri(enable, "*", nil)) end
|
local function censor_key(enable) key.censor(tri(enable, "*", nil)) end
|
||||||
|
|
||||||
local hide_key = Checkbox{parent=net_c_4,x=34,y=12,label="Hide",box_fg_bg=cpair(colors.lightBlue,colors.black),callback=censor_key}
|
local hide_key = Checkbox{parent=net_c_6,x=34,y=12,label="Hide",box_fg_bg=cpair(colors.lightBlue,colors.black),callback=censor_key}
|
||||||
|
|
||||||
hide_key.set_value(true)
|
hide_key.set_value(true)
|
||||||
censor_key(true)
|
censor_key(true)
|
||||||
|
|
||||||
local key_err = TextBox{parent=net_c_4,x=8,y=14,width=35,text="Key must be at least 8 characters.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
local key_err = TextBox{parent=net_c_6,x=8,y=14,width=35,text="Key must be at least 8 characters.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||||
|
|
||||||
local function submit_auth()
|
local function submit_auth()
|
||||||
local v = key.get_value()
|
local v = key.get_value()
|
||||||
@@ -174,8 +282,8 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style)
|
|||||||
else key_err.show() end
|
else key_err.show() end
|
||||||
end
|
end
|
||||||
|
|
||||||
PushButton{parent=net_c_4,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(3)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=net_c_6,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(5)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
PushButton{parent=net_c_4,x=44,y=14,text="Next \x1a",callback=submit_auth,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=net_c_6,x=44,y=14,text="Next \x1a",callback=submit_auth,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
--#endregion
|
--#endregion
|
||||||
|
|
||||||
@@ -188,7 +296,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style)
|
|||||||
TextBox{parent=log_c_1,x=1,y=1,text="Please configure logging below."}
|
TextBox{parent=log_c_1,x=1,y=1,text="Please configure logging below."}
|
||||||
|
|
||||||
TextBox{parent=log_c_1,x=1,y=3,text="Log File Mode"}
|
TextBox{parent=log_c_1,x=1,y=3,text="Log File Mode"}
|
||||||
local mode = RadioButton{parent=log_c_1,x=1,y=4,default=ini_cfg.LogMode+1,options={"Append on Startup","Replace on Startup"},callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.pink}
|
local mode = RadioButton{parent=log_c_1,x=1,y=4,default=ini_cfg.LogMode+1,options={"Append on Startup","Replace on Startup"},radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.pink}
|
||||||
|
|
||||||
TextBox{parent=log_c_1,x=1,y=7,text="Log File Path"}
|
TextBox{parent=log_c_1,x=1,y=7,text="Log File Path"}
|
||||||
local path = TextField{parent=log_c_1,x=1,y=8,width=49,height=1,value=ini_cfg.LogPath,max_len=128,fg_bg=bw_fg_bg}
|
local path = TextField{parent=log_c_1,x=1,y=8,width=49,height=1,value=ini_cfg.LogPath,max_len=128,fg_bg=bw_fg_bg}
|
||||||
@@ -230,10 +338,10 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style)
|
|||||||
TextBox{parent=clr_c_1,x=1,y=4,height=2,text="Click 'Accessibility' below to access colorblind assistive options.",fg_bg=g_lg_fg_bg}
|
TextBox{parent=clr_c_1,x=1,y=4,height=2,text="Click 'Accessibility' below to access colorblind assistive options.",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=clr_c_1,x=1,y=7,text="Main UI Theme"}
|
TextBox{parent=clr_c_1,x=1,y=7,text="Main UI Theme"}
|
||||||
local main_theme = RadioButton{parent=clr_c_1,x=1,y=8,default=ini_cfg.MainTheme,options=themes.UI_THEME_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.magenta}
|
local main_theme = RadioButton{parent=clr_c_1,x=1,y=8,default=ini_cfg.MainTheme,options=themes.UI_THEME_NAMES,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.magenta}
|
||||||
|
|
||||||
TextBox{parent=clr_c_1,x=18,y=7,text="Front Panel Theme"}
|
TextBox{parent=clr_c_1,x=18,y=7,text="Front Panel Theme"}
|
||||||
local fp_theme = RadioButton{parent=clr_c_1,x=18,y=8,default=ini_cfg.FrontPanelTheme,options=themes.FP_THEME_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.magenta}
|
local fp_theme = RadioButton{parent=clr_c_1,x=18,y=8,default=ini_cfg.FrontPanelTheme,options=themes.FP_THEME_NAMES,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 usually be split up."}
|
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 usually be split up."}
|
||||||
|
|
||||||
@@ -370,16 +478,21 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style)
|
|||||||
load_settings(settings_cfg, true)
|
load_settings(settings_cfg, true)
|
||||||
load_settings(ini_cfg)
|
load_settings(ini_cfg)
|
||||||
|
|
||||||
|
try_set(wireless, ini_cfg.WirelessModem)
|
||||||
|
try_set(wired, ini_cfg.WiredModem ~= false)
|
||||||
|
try_set(self.wl_pref, ini_cfg.PreferWireless)
|
||||||
|
try_set(self.api_en, ini_cfg.API_Enabled)
|
||||||
try_set(svr_chan, ini_cfg.SVR_Channel)
|
try_set(svr_chan, ini_cfg.SVR_Channel)
|
||||||
try_set(crd_chan, ini_cfg.CRD_Channel)
|
try_set(crd_chan, ini_cfg.CRD_Channel)
|
||||||
try_set(pkt_chan, ini_cfg.PKT_Channel)
|
try_set(self.pkt_chan, ini_cfg.PKT_Channel)
|
||||||
try_set(svr_timeout, ini_cfg.SVR_Timeout)
|
try_set(svr_timeout, ini_cfg.SVR_Timeout)
|
||||||
try_set(api_timeout, ini_cfg.API_Timeout)
|
try_set(self.api_timeout, ini_cfg.API_Timeout)
|
||||||
try_set(range, ini_cfg.TrustedRange)
|
try_set(range, ini_cfg.TrustedRange)
|
||||||
try_set(key, ini_cfg.AuthKey)
|
try_set(key, ini_cfg.AuthKey)
|
||||||
try_set(tool_ctl.num_units, ini_cfg.UnitCount)
|
try_set(tool_ctl.num_units, ini_cfg.UnitCount)
|
||||||
try_set(tool_ctl.dis_flow_view, ini_cfg.DisableFlowView)
|
try_set(tool_ctl.dis_flow_view, ini_cfg.DisableFlowView)
|
||||||
try_set(tool_ctl.s_vol, ini_cfg.SpeakerVolume)
|
try_set(tool_ctl.s_vol, ini_cfg.SpeakerVolume)
|
||||||
|
try_set(tool_ctl.pellet_color, ini_cfg.GreenPuPellet)
|
||||||
try_set(tool_ctl.clock_fmt, tri(ini_cfg.Time24Hour, 1, 2))
|
try_set(tool_ctl.clock_fmt, tri(ini_cfg.Time24Hour, 1, 2))
|
||||||
try_set(tool_ctl.temp_scale, ini_cfg.TempScale)
|
try_set(tool_ctl.temp_scale, ini_cfg.TempScale)
|
||||||
try_set(tool_ctl.energy_scale, ini_cfg.EnergyScale)
|
try_set(tool_ctl.energy_scale, ini_cfg.EnergyScale)
|
||||||
@@ -528,6 +641,8 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style)
|
|||||||
|
|
||||||
if f[1] == "AuthKey" then val = string.rep("*", string.len(val))
|
if f[1] == "AuthKey" then val = string.rep("*", string.len(val))
|
||||||
elseif f[1] == "LogMode" then val = util.trinary(raw == log.MODE.APPEND, "append", "replace")
|
elseif f[1] == "LogMode" then val = util.trinary(raw == log.MODE.APPEND, "append", "replace")
|
||||||
|
elseif f[1] == "GreenPuPellet" then
|
||||||
|
val = tri(raw, "Green Pu/Cyan Po", "Cyan Pu/Green Po")
|
||||||
elseif f[1] == "TempScale" then
|
elseif f[1] == "TempScale" then
|
||||||
val = util.strval(types.TEMP_SCALE_NAMES[raw])
|
val = util.strval(types.TEMP_SCALE_NAMES[raw])
|
||||||
elseif f[1] == "EnergyScale" then
|
elseif f[1] == "EnergyScale" then
|
||||||
@@ -571,6 +686,59 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- generate the list of available/assigned wired modems
|
||||||
|
function tool_ctl.gen_modem_list()
|
||||||
|
modem_list.remove_all()
|
||||||
|
|
||||||
|
local enable = wired.get_value()
|
||||||
|
|
||||||
|
local function select(iface)
|
||||||
|
tmp_cfg.WiredModem = iface
|
||||||
|
tool_ctl.gen_modem_list()
|
||||||
|
end
|
||||||
|
|
||||||
|
local modems = ppm.get_wired_modem_list()
|
||||||
|
local missing = { tmp = true, ini = true }
|
||||||
|
|
||||||
|
for iface, _ in pairs(modems) do
|
||||||
|
if ini_cfg.WiredModem == iface then missing.ini = false end
|
||||||
|
if tmp_cfg.WiredModem == iface then missing.tmp = false end
|
||||||
|
end
|
||||||
|
|
||||||
|
if missing.tmp and tmp_cfg.WiredModem then
|
||||||
|
local line = Div{parent=modem_list,x=1,y=1,height=1}
|
||||||
|
|
||||||
|
TextBox{parent=line,x=1,y=1,width=4,text="Used",fg_bg=cpair(tri(enable,colors.blue,colors.gray),colors.white)}
|
||||||
|
PushButton{parent=line,x=6,y=1,min_width=8,height=1,text="SELECT",callback=function()end,fg_bg=cpair(colors.black,colors.lightBlue),active_fg_bg=btn_act_fg_bg,dis_fg_bg=g_lg_fg_bg}.disable()
|
||||||
|
TextBox{parent=line,x=15,y=1,text="[missing]",fg_bg=cpair(colors.red,colors.white)}
|
||||||
|
TextBox{parent=line,x=25,y=1,text=tmp_cfg.WiredModem}
|
||||||
|
end
|
||||||
|
|
||||||
|
if missing.ini and ini_cfg.WiredModem and (tmp_cfg.WiredModem ~= ini_cfg.WiredModem) then
|
||||||
|
local line = Div{parent=modem_list,x=1,y=1,height=1}
|
||||||
|
local used = tmp_cfg.WiredModem == ini_cfg.WiredModem
|
||||||
|
|
||||||
|
TextBox{parent=line,x=1,y=1,width=4,text=tri(used,"Used","----"),fg_bg=cpair(tri(used and enable,colors.blue,colors.gray),colors.white)}
|
||||||
|
local select_btn = PushButton{parent=line,x=6,y=1,min_width=8,height=1,text="SELECT",callback=function()select(ini_cfg.WiredModem)end,fg_bg=cpair(colors.black,colors.lightBlue),active_fg_bg=btn_act_fg_bg,dis_fg_bg=g_lg_fg_bg}
|
||||||
|
TextBox{parent=line,x=15,y=1,text="[missing]",fg_bg=cpair(colors.red,colors.white)}
|
||||||
|
TextBox{parent=line,x=25,y=1,text=ini_cfg.WiredModem}
|
||||||
|
|
||||||
|
if used or not enable then select_btn.disable() end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- list wired modems
|
||||||
|
for iface, _ in pairs(modems) do
|
||||||
|
local line = Div{parent=modem_list,x=1,y=1,height=1}
|
||||||
|
local used = tmp_cfg.WiredModem == iface
|
||||||
|
|
||||||
|
TextBox{parent=line,x=1,y=1,width=4,text=tri(used,"Used","----"),fg_bg=cpair(tri(used and enable,colors.blue,colors.gray),colors.white)}
|
||||||
|
local select_btn = PushButton{parent=line,x=6,y=1,min_width=8,height=1,text="SELECT",callback=function()select(iface)end,fg_bg=cpair(colors.black,colors.lightBlue),active_fg_bg=btn_act_fg_bg,dis_fg_bg=g_lg_fg_bg}
|
||||||
|
TextBox{parent=line,x=15,y=1,text=iface}
|
||||||
|
|
||||||
|
if used or not enable then select_btn.disable() end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
--#endregion
|
--#endregion
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,9 @@ local changes = {
|
|||||||
{ "v1.2.4", { "Added temperature scale options" } },
|
{ "v1.2.4", { "Added temperature scale options" } },
|
||||||
{ "v1.2.12", { "Added main UI theme", "Added front panel UI theme", "Added color accessibility modes" } },
|
{ "v1.2.12", { "Added main UI theme", "Added front panel UI theme", "Added color accessibility modes" } },
|
||||||
{ "v1.3.3", { "Added standard with black off state color mode", "Added blue indicator color modes" } },
|
{ "v1.3.3", { "Added standard with black off state color mode", "Added blue indicator color modes" } },
|
||||||
{ "v1.5.1", { "Added energy scale options" } }
|
{ "v1.5.1", { "Added energy scale options" } },
|
||||||
|
{ "v1.6.13", { "Added option for Po/Pu pellet green/cyan pairing" } },
|
||||||
|
{ "v1.7.0", { "Added support for wired communications modems", "Added option for allowing Pocket connections" } }
|
||||||
}
|
}
|
||||||
|
|
||||||
---@class crd_configurator
|
---@class crd_configurator
|
||||||
@@ -77,6 +79,7 @@ local tool_ctl = {
|
|||||||
-- settings elements from hmi
|
-- settings elements from hmi
|
||||||
dis_flow_view = nil, ---@type Checkbox
|
dis_flow_view = nil, ---@type Checkbox
|
||||||
s_vol = nil, ---@type NumberField
|
s_vol = nil, ---@type NumberField
|
||||||
|
pellet_color = nil, ---@type RadioButton
|
||||||
clock_fmt = nil, ---@type RadioButton
|
clock_fmt = nil, ---@type RadioButton
|
||||||
temp_scale = nil, ---@type RadioButton
|
temp_scale = nil, ---@type RadioButton
|
||||||
energy_scale = nil, ---@type RadioButton
|
energy_scale = nil, ---@type RadioButton
|
||||||
@@ -87,7 +90,9 @@ local tool_ctl = {
|
|||||||
is_int_min_max = nil, ---@type function
|
is_int_min_max = nil, ---@type function
|
||||||
|
|
||||||
update_mon_reqs = nil, ---@type function
|
update_mon_reqs = nil, ---@type function
|
||||||
gen_mon_list = function () end
|
|
||||||
|
gen_mon_list = function () end,
|
||||||
|
gen_modem_list = function () end
|
||||||
}
|
}
|
||||||
|
|
||||||
---@class crd_config
|
---@class crd_config
|
||||||
@@ -95,12 +100,17 @@ local tmp_cfg = {
|
|||||||
UnitCount = 1,
|
UnitCount = 1,
|
||||||
SpeakerVolume = 1.0,
|
SpeakerVolume = 1.0,
|
||||||
Time24Hour = true,
|
Time24Hour = true,
|
||||||
|
GreenPuPellet = false,
|
||||||
TempScale = 1, ---@type TEMP_SCALE
|
TempScale = 1, ---@type TEMP_SCALE
|
||||||
EnergyScale = 1, ---@type ENERGY_SCALE
|
EnergyScale = 1, ---@type ENERGY_SCALE
|
||||||
DisableFlowView = false,
|
DisableFlowView = false,
|
||||||
MainDisplay = nil, ---@type string
|
MainDisplay = nil, ---@type string
|
||||||
FlowDisplay = nil, ---@type string
|
FlowDisplay = nil, ---@type string
|
||||||
UnitDisplays = {}, ---@type string[]
|
UnitDisplays = {}, ---@type string[]
|
||||||
|
WirelessModem = true,
|
||||||
|
WiredModem = false, ---@type string|false
|
||||||
|
PreferWireless = true,
|
||||||
|
API_Enabled = true,
|
||||||
SVR_Channel = nil, ---@type integer
|
SVR_Channel = nil, ---@type integer
|
||||||
CRD_Channel = nil, ---@type integer
|
CRD_Channel = nil, ---@type integer
|
||||||
PKT_Channel = nil, ---@type integer
|
PKT_Channel = nil, ---@type integer
|
||||||
@@ -129,9 +139,14 @@ local fields = {
|
|||||||
{ "UnitDisplays", "Unit Monitors", {} },
|
{ "UnitDisplays", "Unit Monitors", {} },
|
||||||
{ "SpeakerVolume", "Speaker Volume", 1.0 },
|
{ "SpeakerVolume", "Speaker Volume", 1.0 },
|
||||||
{ "Time24Hour", "Use 24-hour Time Format", true },
|
{ "Time24Hour", "Use 24-hour Time Format", true },
|
||||||
|
{ "GreenPuPellet", "Pellet Colors", false },
|
||||||
{ "TempScale", "Temperature Scale", types.TEMP_SCALE.KELVIN },
|
{ "TempScale", "Temperature Scale", types.TEMP_SCALE.KELVIN },
|
||||||
{ "EnergyScale", "Energy Scale", types.ENERGY_SCALE.FE },
|
{ "EnergyScale", "Energy Scale", types.ENERGY_SCALE.FE },
|
||||||
{ "DisableFlowView", "Disable Flow Monitor (legacy, discouraged)", false },
|
{ "DisableFlowView", "Disable Flow Monitor (legacy, discouraged)", false },
|
||||||
|
{ "WirelessModem", "Wireless/Ender Comms Modem", true },
|
||||||
|
{ "WiredModem", "Wired Comms Modem", false },
|
||||||
|
{ "PreferWireless", "Prefer Wireless Modem", true },
|
||||||
|
{ "API_Enabled", "Pocket API Connectivity", true },
|
||||||
{ "SVR_Channel", "SVR Channel", 16240 },
|
{ "SVR_Channel", "SVR Channel", 16240 },
|
||||||
{ "CRD_Channel", "CRD Channel", 16243 },
|
{ "CRD_Channel", "CRD Channel", 16243 },
|
||||||
{ "PKT_Channel", "PKT Channel", 16244 },
|
{ "PKT_Channel", "PKT Channel", 16244 },
|
||||||
@@ -323,6 +338,9 @@ function configurator.configure(start_code, message)
|
|||||||
-- copy in some important values to start with
|
-- copy in some important values to start with
|
||||||
preset_monitor_fields()
|
preset_monitor_fields()
|
||||||
|
|
||||||
|
-- this needs to be initialized as it is used before being set
|
||||||
|
tmp_cfg.WiredModem = ini_cfg.WiredModem
|
||||||
|
|
||||||
reset_term()
|
reset_term()
|
||||||
|
|
||||||
ppm.mount_all()
|
ppm.mount_all()
|
||||||
@@ -337,6 +355,7 @@ function configurator.configure(start_code, message)
|
|||||||
config_view(display)
|
config_view(display)
|
||||||
|
|
||||||
tool_ctl.gen_mon_list()
|
tool_ctl.gen_mon_list()
|
||||||
|
tool_ctl.gen_modem_list()
|
||||||
|
|
||||||
while true do
|
while true do
|
||||||
local event, param1, param2, param3, param4, param5 = util.pull_event()
|
local event, param1, param2, param3, param4, param5 = util.pull_event()
|
||||||
@@ -360,8 +379,10 @@ function configurator.configure(start_code, message)
|
|||||||
---@diagnostic disable-next-line: discard-returns
|
---@diagnostic disable-next-line: discard-returns
|
||||||
ppm.mount(param1)
|
ppm.mount(param1)
|
||||||
tool_ctl.gen_mon_list()
|
tool_ctl.gen_mon_list()
|
||||||
|
tool_ctl.gen_modem_list()
|
||||||
elseif event == "monitor_resize" then
|
elseif event == "monitor_resize" then
|
||||||
tool_ctl.gen_mon_list()
|
tool_ctl.gen_mon_list()
|
||||||
|
tool_ctl.gen_modem_list()
|
||||||
elseif event == "modem_message" then
|
elseif event == "modem_message" then
|
||||||
facility.receive_sv(param1, param2, param3, param4, param5)
|
facility.receive_sv(param1, param2, param3, param4, param5)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
local comms = require("scada-common.comms")
|
local comms = require("scada-common.comms")
|
||||||
local log = require("scada-common.log")
|
local log = require("scada-common.log")
|
||||||
local ppm = require("scada-common.ppm")
|
|
||||||
local util = require("scada-common.util")
|
local util = require("scada-common.util")
|
||||||
local types = require("scada-common.types")
|
local types = require("scada-common.types")
|
||||||
|
|
||||||
@@ -29,15 +28,14 @@ local config = {}
|
|||||||
|
|
||||||
coordinator.config = config
|
coordinator.config = config
|
||||||
|
|
||||||
-- load the coordinator configuration<br>
|
-- load the coordinator configuration
|
||||||
-- status of 0 is OK, 1 is bad config, 2 is bad monitor config
|
|
||||||
---@return 0|1|2 status, nil|monitors_struct|string monitors (or error message)
|
|
||||||
function coordinator.load_config()
|
function coordinator.load_config()
|
||||||
if not settings.load("/coordinator.settings") then return 1 end
|
if not settings.load("/coordinator.settings") then return false end
|
||||||
|
|
||||||
config.UnitCount = settings.get("UnitCount")
|
config.UnitCount = settings.get("UnitCount")
|
||||||
config.SpeakerVolume = settings.get("SpeakerVolume")
|
config.SpeakerVolume = settings.get("SpeakerVolume")
|
||||||
config.Time24Hour = settings.get("Time24Hour")
|
config.Time24Hour = settings.get("Time24Hour")
|
||||||
|
config.GreenPuPellet = settings.get("GreenPuPellet")
|
||||||
config.TempScale = settings.get("TempScale")
|
config.TempScale = settings.get("TempScale")
|
||||||
config.EnergyScale = settings.get("EnergyScale")
|
config.EnergyScale = settings.get("EnergyScale")
|
||||||
|
|
||||||
@@ -46,6 +44,10 @@ function coordinator.load_config()
|
|||||||
config.FlowDisplay = settings.get("FlowDisplay")
|
config.FlowDisplay = settings.get("FlowDisplay")
|
||||||
config.UnitDisplays = settings.get("UnitDisplays")
|
config.UnitDisplays = settings.get("UnitDisplays")
|
||||||
|
|
||||||
|
config.WirelessModem = settings.get("WirelessModem")
|
||||||
|
config.WiredModem = settings.get("WiredModem")
|
||||||
|
config.PreferWireless = settings.get("PreferWireless")
|
||||||
|
config.API_Enabled = settings.get("API_Enabled")
|
||||||
config.SVR_Channel = settings.get("SVR_Channel")
|
config.SVR_Channel = settings.get("SVR_Channel")
|
||||||
config.CRD_Channel = settings.get("CRD_Channel")
|
config.CRD_Channel = settings.get("CRD_Channel")
|
||||||
config.PKT_Channel = settings.get("PKT_Channel")
|
config.PKT_Channel = settings.get("PKT_Channel")
|
||||||
@@ -67,6 +69,7 @@ function coordinator.load_config()
|
|||||||
cfv.assert_type_int(config.UnitCount)
|
cfv.assert_type_int(config.UnitCount)
|
||||||
cfv.assert_range(config.UnitCount, 1, 4)
|
cfv.assert_range(config.UnitCount, 1, 4)
|
||||||
cfv.assert_type_bool(config.Time24Hour)
|
cfv.assert_type_bool(config.Time24Hour)
|
||||||
|
cfv.assert_type_bool(config.GreenPuPellet)
|
||||||
cfv.assert_type_int(config.TempScale)
|
cfv.assert_type_int(config.TempScale)
|
||||||
cfv.assert_range(config.TempScale, 1, 4)
|
cfv.assert_range(config.TempScale, 1, 4)
|
||||||
cfv.assert_type_int(config.EnergyScale)
|
cfv.assert_type_int(config.EnergyScale)
|
||||||
@@ -78,6 +81,13 @@ function coordinator.load_config()
|
|||||||
cfv.assert_type_num(config.SpeakerVolume)
|
cfv.assert_type_num(config.SpeakerVolume)
|
||||||
cfv.assert_range(config.SpeakerVolume, 0, 3)
|
cfv.assert_range(config.SpeakerVolume, 0, 3)
|
||||||
|
|
||||||
|
cfv.assert_type_bool(config.WirelessModem)
|
||||||
|
cfv.assert((config.WiredModem == false) or (type(config.WiredModem) == "string"))
|
||||||
|
cfv.assert(config.WirelessModem or (type(config.WiredModem) == "string"))
|
||||||
|
cfv.assert_type_bool(config.PreferWireless)
|
||||||
|
|
||||||
|
cfv.assert_type_bool(config.API_Enabled)
|
||||||
|
|
||||||
cfv.assert_channel(config.SVR_Channel)
|
cfv.assert_channel(config.SVR_Channel)
|
||||||
cfv.assert_channel(config.CRD_Channel)
|
cfv.assert_channel(config.CRD_Channel)
|
||||||
cfv.assert_channel(config.PKT_Channel)
|
cfv.assert_channel(config.PKT_Channel)
|
||||||
@@ -108,85 +118,7 @@ function coordinator.load_config()
|
|||||||
cfv.assert_type_int(config.ColorMode)
|
cfv.assert_type_int(config.ColorMode)
|
||||||
cfv.assert_range(config.ColorMode, 1, themes.COLOR_MODE.NUM_MODES)
|
cfv.assert_range(config.ColorMode, 1, themes.COLOR_MODE.NUM_MODES)
|
||||||
|
|
||||||
-- Monitor Setup
|
return cfv.valid()
|
||||||
|
|
||||||
---@class monitors_struct
|
|
||||||
local monitors = {
|
|
||||||
main = nil, ---@type Monitor|nil
|
|
||||||
main_name = "",
|
|
||||||
flow = nil, ---@type Monitor|nil
|
|
||||||
flow_name = "",
|
|
||||||
unit_displays = {}, ---@type Monitor[]
|
|
||||||
unit_name_map = {} ---@type string[]
|
|
||||||
}
|
|
||||||
|
|
||||||
local mon_cfv = util.new_validator()
|
|
||||||
|
|
||||||
-- get all interface names
|
|
||||||
local names = {}
|
|
||||||
for iface, _ in pairs(ppm.get_monitor_list()) do table.insert(names, iface) end
|
|
||||||
|
|
||||||
local function setup_monitors()
|
|
||||||
mon_cfv.assert_type_str(config.MainDisplay)
|
|
||||||
if not config.DisableFlowView then mon_cfv.assert_type_str(config.FlowDisplay) end
|
|
||||||
mon_cfv.assert_eq(#config.UnitDisplays, config.UnitCount)
|
|
||||||
|
|
||||||
if mon_cfv.valid() then
|
|
||||||
local w, h, _
|
|
||||||
|
|
||||||
if not util.table_contains(names, config.MainDisplay) then
|
|
||||||
return 2, "Main monitor is not connected."
|
|
||||||
end
|
|
||||||
|
|
||||||
monitors.main = ppm.get_periph(config.MainDisplay)
|
|
||||||
monitors.main_name = config.MainDisplay
|
|
||||||
|
|
||||||
monitors.main.setTextScale(0.5)
|
|
||||||
w, _ = ppm.monitor_block_size(monitors.main.getSize())
|
|
||||||
if w ~= 8 then
|
|
||||||
return 2, util.c("Main monitor width is incorrect (was ", w, ", must be 8).")
|
|
||||||
end
|
|
||||||
|
|
||||||
if not config.DisableFlowView then
|
|
||||||
if not util.table_contains(names, config.FlowDisplay) then
|
|
||||||
return 2, "Flow monitor is not connected."
|
|
||||||
end
|
|
||||||
|
|
||||||
monitors.flow = ppm.get_periph(config.FlowDisplay)
|
|
||||||
monitors.flow_name = config.FlowDisplay
|
|
||||||
|
|
||||||
monitors.flow.setTextScale(0.5)
|
|
||||||
w, _ = ppm.monitor_block_size(monitors.flow.getSize())
|
|
||||||
if w ~= 8 then
|
|
||||||
return 2, util.c("Flow monitor width is incorrect (was ", w, ", must be 8).")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
for i = 1, config.UnitCount do
|
|
||||||
local display = config.UnitDisplays[i]
|
|
||||||
if type(display) ~= "string" or not util.table_contains(names, display) then
|
|
||||||
return 2, "Unit " .. i .. " monitor is not connected."
|
|
||||||
end
|
|
||||||
|
|
||||||
monitors.unit_displays[i] = ppm.get_periph(display)
|
|
||||||
monitors.unit_name_map[i] = display
|
|
||||||
|
|
||||||
monitors.unit_displays[i].setTextScale(0.5)
|
|
||||||
w, h = ppm.monitor_block_size(monitors.unit_displays[i].getSize())
|
|
||||||
if w ~= 4 or h ~= 4 then
|
|
||||||
return 2, util.c("Unit ", i, " monitor size is incorrect (was ", w, " by ", h,", must be 4 by 4).")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else return 2, "Monitor configuration invalid." end
|
|
||||||
end
|
|
||||||
|
|
||||||
if cfv.valid() then
|
|
||||||
local ok, result, message = pcall(setup_monitors)
|
|
||||||
assert(ok, util.c("fatal error while trying to verify monitors: ", result))
|
|
||||||
if result == 2 then return 2, message end
|
|
||||||
else return 1 end
|
|
||||||
|
|
||||||
return 0, monitors
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- dmesg print wrapper
|
-- dmesg print wrapper
|
||||||
@@ -230,9 +162,10 @@ end
|
|||||||
-- coordinator communications
|
-- coordinator communications
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
---@param version string coordinator version
|
---@param version string coordinator version
|
||||||
---@param nic nic network interface device
|
---@param nic nic active network interface device
|
||||||
|
---@param wl_nic nic|nil pocket wireless network interface device
|
||||||
---@param sv_watchdog watchdog
|
---@param sv_watchdog watchdog
|
||||||
function coordinator.comms(version, nic, sv_watchdog)
|
function coordinator.comms(version, nic, wl_nic, sv_watchdog)
|
||||||
local self = {
|
local self = {
|
||||||
sv_linked = false,
|
sv_linked = false,
|
||||||
sv_addr = comms.BROADCAST,
|
sv_addr = comms.BROADCAST,
|
||||||
@@ -247,16 +180,16 @@ function coordinator.comms(version, nic, sv_watchdog)
|
|||||||
est_task_done = nil
|
est_task_done = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
comms.set_trusted_range(config.TrustedRange)
|
if config.WirelessModem then
|
||||||
|
comms.set_trusted_range(config.TrustedRange)
|
||||||
-- configure network channels
|
end
|
||||||
nic.closeAll()
|
|
||||||
nic.open(config.CRD_Channel)
|
|
||||||
|
|
||||||
-- pass config to apisessions
|
-- pass config to apisessions
|
||||||
apisessions.init(nic, config)
|
if config.API_Enabled and wl_nic then
|
||||||
|
apisessions.init(wl_nic, config)
|
||||||
|
end
|
||||||
|
|
||||||
-- PRIVATE FUNCTIONS --
|
--#region PRIVATE FUNCTIONS --
|
||||||
|
|
||||||
-- send a packet to the supervisor
|
-- send a packet to the supervisor
|
||||||
---@param msg_type MGMT_TYPE|CRDN_TYPE
|
---@param msg_type MGMT_TYPE|CRDN_TYPE
|
||||||
@@ -291,7 +224,8 @@ function coordinator.comms(version, nic, sv_watchdog)
|
|||||||
m_pkt.make(MGMT_TYPE.ESTABLISH, { ack, data })
|
m_pkt.make(MGMT_TYPE.ESTABLISH, { ack, data })
|
||||||
s_pkt.make(packet.src_addr(), packet.seq_num() + 1, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable())
|
s_pkt.make(packet.src_addr(), packet.seq_num() + 1, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable())
|
||||||
|
|
||||||
nic.transmit(config.PKT_Channel, config.CRD_Channel, s_pkt)
|
---@diagnostic disable-next-line: need-check-nil
|
||||||
|
wl_nic.transmit(config.PKT_Channel, config.CRD_Channel, s_pkt)
|
||||||
self.last_api_est_acks[packet.src_addr()] = ack
|
self.last_api_est_acks[packet.src_addr()] = ack
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -307,11 +241,20 @@ function coordinator.comms(version, nic, sv_watchdog)
|
|||||||
_send_sv(PROTOCOL.SCADA_MGMT, MGMT_TYPE.KEEP_ALIVE, { srv_time, util.time() })
|
_send_sv(PROTOCOL.SCADA_MGMT, MGMT_TYPE.KEEP_ALIVE, { srv_time, util.time() })
|
||||||
end
|
end
|
||||||
|
|
||||||
-- PUBLIC FUNCTIONS --
|
--#endregion
|
||||||
|
|
||||||
|
--#region PUBLIC FUNCTIONS --
|
||||||
|
|
||||||
---@class coord_comms
|
---@class coord_comms
|
||||||
local public = {}
|
local public = {}
|
||||||
|
|
||||||
|
-- switch the current active NIC
|
||||||
|
---@param act_nic nic
|
||||||
|
function public.switch_nic(act_nic)
|
||||||
|
public.close()
|
||||||
|
nic = act_nic
|
||||||
|
end
|
||||||
|
|
||||||
-- try to connect to the supervisor if not already linked
|
-- try to connect to the supervisor if not already linked
|
||||||
---@param abort boolean? true to print out cancel info if not linked (use on program terminate)
|
---@param abort boolean? true to print out cancel info if not linked (use on program terminate)
|
||||||
---@return boolean ok, boolean start_ui
|
---@return boolean ok, boolean start_ui
|
||||||
@@ -466,7 +409,9 @@ function coordinator.comms(version, nic, sv_watchdog)
|
|||||||
if l_chan ~= config.CRD_Channel then
|
if l_chan ~= config.CRD_Channel then
|
||||||
log.debug("received packet on unconfigured channel " .. l_chan, true)
|
log.debug("received packet on unconfigured channel " .. l_chan, true)
|
||||||
elseif r_chan == config.PKT_Channel then
|
elseif r_chan == config.PKT_Channel then
|
||||||
if not self.sv_linked then
|
if not config.API_Enabled then
|
||||||
|
-- log.debug("discarding pocket API packet due to the API being disabled")
|
||||||
|
elseif not self.sv_linked then
|
||||||
log.debug("discarding pocket API packet before linked to supervisor")
|
log.debug("discarding pocket API packet before linked to supervisor")
|
||||||
elseif protocol == PROTOCOL.SCADA_CRDN then
|
elseif protocol == PROTOCOL.SCADA_CRDN then
|
||||||
---@cast packet crdn_frame
|
---@cast packet crdn_frame
|
||||||
@@ -782,6 +727,8 @@ function coordinator.comms(version, nic, sv_watchdog)
|
|||||||
---@nodiscard
|
---@nodiscard
|
||||||
function public.is_linked() return self.sv_linked end
|
function public.is_linked() return self.sv_linked end
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
return public
|
return public
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -34,18 +34,10 @@ local HIGH_RTT = 1500 -- 3.33x as long as expected w/ 0 ping
|
|||||||
local iocontrol = {}
|
local iocontrol = {}
|
||||||
|
|
||||||
---@class ioctl
|
---@class ioctl
|
||||||
local io = {}
|
local io = {
|
||||||
|
|
||||||
-- initialize front panel PSIL
|
|
||||||
---@param firmware_v string coordinator version
|
|
||||||
---@param comms_v string comms version
|
|
||||||
function iocontrol.init_fp(firmware_v, comms_v)
|
|
||||||
---@class ioctl_front_panel
|
---@class ioctl_front_panel
|
||||||
io.fp = { ps = psil.create() }
|
fp = { ps = psil.create() }
|
||||||
|
}
|
||||||
io.fp.ps.publish("version", firmware_v)
|
|
||||||
io.fp.ps.publish("comms_version", comms_v)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- initialize the coordinator IO controller
|
-- initialize the coordinator IO controller
|
||||||
---@param conf facility_conf configuration
|
---@param conf facility_conf configuration
|
||||||
@@ -132,7 +124,9 @@ function iocontrol.init(conf, comms, temp_scale, energy_scale)
|
|||||||
sps_data_tbl = {}, ---@type sps_session_db[]
|
sps_data_tbl = {}, ---@type sps_session_db[]
|
||||||
|
|
||||||
tank_ps_tbl = {}, ---@type psil[]
|
tank_ps_tbl = {}, ---@type psil[]
|
||||||
tank_data_tbl = {} ---@type dynamicv_session_db[]
|
tank_data_tbl = {}, ---@type dynamicv_session_db[]
|
||||||
|
|
||||||
|
rad_monitors = {} ---@type { radiation: radiation_reading, raw: number }[]
|
||||||
}
|
}
|
||||||
|
|
||||||
-- create induction and SPS tables (currently only 1 of each is supported)
|
-- create induction and SPS tables (currently only 1 of each is supported)
|
||||||
@@ -242,7 +236,9 @@ function iocontrol.init(conf, comms, temp_scale, energy_scale)
|
|||||||
turbine_data_tbl = {}, ---@type turbinev_session_db[]
|
turbine_data_tbl = {}, ---@type turbinev_session_db[]
|
||||||
|
|
||||||
tank_ps_tbl = {}, ---@type psil[]
|
tank_ps_tbl = {}, ---@type psil[]
|
||||||
tank_data_tbl = {} ---@type dynamicv_session_db[]
|
tank_data_tbl = {}, ---@type dynamicv_session_db[]
|
||||||
|
|
||||||
|
rad_monitors = {} ---@type { radiation: radiation_reading, raw: number }[]
|
||||||
}
|
}
|
||||||
|
|
||||||
-- on other facility modes, overwrite unit TANK option with facility tank defs
|
-- on other facility modes, overwrite unit TANK option with facility tank defs
|
||||||
@@ -286,9 +282,21 @@ end
|
|||||||
-- toggle heartbeat indicator
|
-- toggle heartbeat indicator
|
||||||
function iocontrol.heartbeat() io.fp.ps.toggle("heartbeat") end
|
function iocontrol.heartbeat() io.fp.ps.toggle("heartbeat") end
|
||||||
|
|
||||||
-- report presence of the wireless modem
|
-- report versions to front panel
|
||||||
|
---@param firmware_v string coordinator version
|
||||||
|
---@param comms_v string comms version
|
||||||
|
function iocontrol.fp_versions(firmware_v, comms_v)
|
||||||
|
io.fp.ps.publish("version", firmware_v)
|
||||||
|
io.fp.ps.publish("comms_version", comms_v)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- report presence of the wired comms modem
|
||||||
---@param has_modem boolean
|
---@param has_modem boolean
|
||||||
function iocontrol.fp_has_modem(has_modem) io.fp.ps.publish("has_modem", has_modem) end
|
function iocontrol.fp_has_wd_modem(has_modem) io.fp.ps.publish("has_wd_modem", has_modem) end
|
||||||
|
|
||||||
|
-- report presence of the wireless comms modem
|
||||||
|
---@param has_modem boolean
|
||||||
|
function iocontrol.fp_has_wl_modem(has_modem) io.fp.ps.publish("has_wl_modem", has_modem) end
|
||||||
|
|
||||||
-- report presence of the speaker
|
-- report presence of the speaker
|
||||||
---@param has_speaker boolean
|
---@param has_speaker boolean
|
||||||
@@ -300,6 +308,7 @@ function iocontrol.fp_link_state(state) io.fp.ps.publish("link_state", state) en
|
|||||||
|
|
||||||
-- report monitor connection state
|
-- report monitor connection state
|
||||||
---@param id string|integer unit ID for unit monitor, "main" for main monitor, or "flow" for flow monitor
|
---@param id string|integer unit ID for unit monitor, "main" for main monitor, or "flow" for flow monitor
|
||||||
|
---@param connected 1|2|3 1 for disconnected, 2 for connected but no view (may not fit), 3 for connected with view rendered
|
||||||
function iocontrol.fp_monitor_state(id, connected)
|
function iocontrol.fp_monitor_state(id, connected)
|
||||||
local name = nil
|
local name = nil
|
||||||
|
|
||||||
@@ -797,7 +806,9 @@ function iocontrol.update_facility_status(status)
|
|||||||
if type(rtu_statuses.envds) == "table" then
|
if type(rtu_statuses.envds) == "table" then
|
||||||
local max_rad, max_reading, any_conn, any_faulted = 0, types.new_zero_radiation_reading(), false, false
|
local max_rad, max_reading, any_conn, any_faulted = 0, types.new_zero_radiation_reading(), false, false
|
||||||
|
|
||||||
for _, envd in pairs(rtu_statuses.envds) do
|
fac.rad_monitors = {}
|
||||||
|
|
||||||
|
for id, envd in pairs(rtu_statuses.envds) do
|
||||||
local rtu_faulted = envd[1] ---@type boolean
|
local rtu_faulted = envd[1] ---@type boolean
|
||||||
local radiation = envd[2] ---@type radiation_reading
|
local radiation = envd[2] ---@type radiation_reading
|
||||||
local rad_raw = envd[3] ---@type number
|
local rad_raw = envd[3] ---@type number
|
||||||
@@ -809,6 +820,10 @@ function iocontrol.update_facility_status(status)
|
|||||||
max_rad = rad_raw
|
max_rad = rad_raw
|
||||||
max_reading = radiation
|
max_reading = radiation
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if not rtu_faulted then
|
||||||
|
fac.rad_monitors[id] = { radiation = radiation, raw = rad_raw }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if any_conn then
|
if any_conn then
|
||||||
@@ -1099,9 +1114,12 @@ function iocontrol.update_unit_statuses(statuses)
|
|||||||
if type(rtu_statuses.envds) == "table" then
|
if type(rtu_statuses.envds) == "table" then
|
||||||
local max_rad, max_reading, any_conn = 0, types.new_zero_radiation_reading(), false
|
local max_rad, max_reading, any_conn = 0, types.new_zero_radiation_reading(), false
|
||||||
|
|
||||||
for _, envd in pairs(rtu_statuses.envds) do
|
unit.rad_monitors = {}
|
||||||
local radiation = envd[2] ---@type radiation_reading
|
|
||||||
local rad_raw = envd[3] ---@type number
|
for id, envd in pairs(rtu_statuses.envds) do
|
||||||
|
local rtu_faulted = envd[1] ---@type boolean
|
||||||
|
local radiation = envd[2] ---@type radiation_reading
|
||||||
|
local rad_raw = envd[3] ---@type number
|
||||||
|
|
||||||
any_conn = true
|
any_conn = true
|
||||||
|
|
||||||
@@ -1109,6 +1127,10 @@ function iocontrol.update_unit_statuses(statuses)
|
|||||||
max_rad = rad_raw
|
max_rad = rad_raw
|
||||||
max_reading = radiation
|
max_reading = radiation
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if not rtu_faulted then
|
||||||
|
unit.rad_monitors[id] = { radiation = radiation, raw = rad_raw }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if any_conn then
|
if any_conn then
|
||||||
|
|||||||
@@ -28,8 +28,9 @@ local renderer = {}
|
|||||||
|
|
||||||
-- render engine
|
-- render engine
|
||||||
local engine = {
|
local engine = {
|
||||||
|
config = nil, ---@type crd_config
|
||||||
color_mode = 1, ---@type COLOR_MODE
|
color_mode = 1, ---@type COLOR_MODE
|
||||||
monitors = nil, ---@type monitors_struct|nil
|
monitors = nil, ---@type crd_displays|nil
|
||||||
dmesg_window = nil, ---@type Window|nil
|
dmesg_window = nil, ---@type Window|nil
|
||||||
ui_ready = false,
|
ui_ready = false,
|
||||||
fp_ready = false,
|
fp_ready = false,
|
||||||
@@ -76,25 +77,18 @@ end
|
|||||||
-- apply renderer configurations
|
-- apply renderer configurations
|
||||||
---@param config crd_config
|
---@param config crd_config
|
||||||
function renderer.configure(config)
|
function renderer.configure(config)
|
||||||
style.set_themes(config.MainTheme, config.FrontPanelTheme, config.ColorMode)
|
engine.config = config
|
||||||
|
|
||||||
engine.color_mode = config.ColorMode
|
engine.color_mode = config.ColorMode
|
||||||
engine.disable_flow_view = config.DisableFlowView
|
engine.disable_flow_view = config.DisableFlowView
|
||||||
end
|
|
||||||
|
|
||||||
-- link to the monitor peripherals
|
style.set_themes(config.MainTheme, config.FrontPanelTheme, config.ColorMode)
|
||||||
---@param monitors monitors_struct
|
|
||||||
function renderer.set_displays(monitors)
|
|
||||||
engine.monitors = monitors
|
|
||||||
|
|
||||||
-- report to front panel as connected
|
|
||||||
iocontrol.fp_monitor_state("main", engine.monitors.main ~= nil)
|
|
||||||
iocontrol.fp_monitor_state("flow", engine.monitors.flow ~= nil)
|
|
||||||
for i = 1, #engine.monitors.unit_displays do iocontrol.fp_monitor_state(i, true) end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- init all displays in use by the renderer
|
-- init all displays in use by the renderer
|
||||||
function renderer.init_displays()
|
---@param monitors crd_displays
|
||||||
|
function renderer.init_displays(monitors)
|
||||||
|
engine.monitors = monitors
|
||||||
|
|
||||||
-- init main and flow monitors
|
-- init main and flow monitors
|
||||||
_init_display(engine.monitors.main)
|
_init_display(engine.monitors.main)
|
||||||
if not engine.disable_flow_view then _init_display(engine.monitors.flow) end
|
if not engine.disable_flow_view then _init_display(engine.monitors.flow) end
|
||||||
@@ -138,7 +132,7 @@ function renderer.try_start_fp()
|
|||||||
-- show front panel view on terminal
|
-- show front panel view on terminal
|
||||||
status, msg = pcall(function ()
|
status, msg = pcall(function ()
|
||||||
engine.ui.front_panel = DisplayBox{window=term.current(),fg_bg=style.fp.root}
|
engine.ui.front_panel = DisplayBox{window=term.current(),fg_bg=style.fp.root}
|
||||||
panel_view(engine.ui.front_panel, #engine.monitors.unit_displays)
|
panel_view(engine.ui.front_panel, engine.config)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
if status then
|
if status then
|
||||||
@@ -199,6 +193,7 @@ function renderer.try_start_ui()
|
|||||||
if engine.monitors.main ~= nil then
|
if engine.monitors.main ~= nil then
|
||||||
engine.ui.main_display = DisplayBox{window=engine.monitors.main,fg_bg=style.root}
|
engine.ui.main_display = DisplayBox{window=engine.monitors.main,fg_bg=style.root}
|
||||||
main_view(engine.ui.main_display)
|
main_view(engine.ui.main_display)
|
||||||
|
iocontrol.fp_monitor_state("main", 3)
|
||||||
util.nop()
|
util.nop()
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -206,6 +201,7 @@ function renderer.try_start_ui()
|
|||||||
if engine.monitors.flow ~= nil then
|
if engine.monitors.flow ~= nil then
|
||||||
engine.ui.flow_display = DisplayBox{window=engine.monitors.flow,fg_bg=style.root}
|
engine.ui.flow_display = DisplayBox{window=engine.monitors.flow,fg_bg=style.root}
|
||||||
flow_view(engine.ui.flow_display)
|
flow_view(engine.ui.flow_display)
|
||||||
|
iocontrol.fp_monitor_state("flow", 3)
|
||||||
util.nop()
|
util.nop()
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -213,6 +209,7 @@ function renderer.try_start_ui()
|
|||||||
for idx, display in pairs(engine.monitors.unit_displays) do
|
for idx, display in pairs(engine.monitors.unit_displays) do
|
||||||
engine.ui.unit_displays[idx] = DisplayBox{window=display,fg_bg=style.root}
|
engine.ui.unit_displays[idx] = DisplayBox{window=display,fg_bg=style.root}
|
||||||
unit_view(engine.ui.unit_displays[idx], idx)
|
unit_view(engine.ui.unit_displays[idx], idx)
|
||||||
|
iocontrol.fp_monitor_state(idx, 3)
|
||||||
util.nop()
|
util.nop()
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
@@ -239,9 +236,21 @@ function renderer.close_ui()
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- delete element trees
|
-- delete element trees
|
||||||
if engine.ui.main_display ~= nil then engine.ui.main_display.delete() end
|
|
||||||
if engine.ui.flow_display ~= nil then engine.ui.flow_display.delete() end
|
if engine.ui.main_display ~= nil then
|
||||||
for _, display in pairs(engine.ui.unit_displays) do display.delete() end
|
engine.ui.main_display.delete()
|
||||||
|
iocontrol.fp_monitor_state("main", 2)
|
||||||
|
end
|
||||||
|
|
||||||
|
if engine.ui.flow_display ~= nil then
|
||||||
|
engine.ui.flow_display.delete()
|
||||||
|
iocontrol.fp_monitor_state("flow", 2)
|
||||||
|
end
|
||||||
|
|
||||||
|
for idx, display in pairs(engine.ui.unit_displays) do
|
||||||
|
display.delete()
|
||||||
|
iocontrol.fp_monitor_state(idx, 2)
|
||||||
|
end
|
||||||
|
|
||||||
-- report ui as not ready
|
-- report ui as not ready
|
||||||
engine.ui_ready = false
|
engine.ui_ready = false
|
||||||
@@ -275,90 +284,51 @@ function renderer.fp_ready() return engine.fp_ready end
|
|||||||
function renderer.ui_ready() return engine.ui_ready end
|
function renderer.ui_ready() return engine.ui_ready end
|
||||||
|
|
||||||
-- handle a monitor peripheral being disconnected
|
-- handle a monitor peripheral being disconnected
|
||||||
---@param device Monitor monitor
|
---@param iface string monitor interface
|
||||||
---@return boolean is_used if the monitor is one of the configured monitors
|
function renderer.handle_disconnect(iface)
|
||||||
function renderer.handle_disconnect(device)
|
|
||||||
local is_used = false
|
|
||||||
|
|
||||||
if not engine.monitors then return false end
|
if not engine.monitors then return false end
|
||||||
|
|
||||||
if engine.monitors.main == device then
|
if engine.monitors.main_iface == iface then
|
||||||
if engine.ui.main_display ~= nil then
|
if engine.ui.main_display ~= nil then
|
||||||
-- delete element tree and clear root UI elements
|
-- delete element tree and clear root UI elements
|
||||||
engine.ui.main_display.delete()
|
engine.ui.main_display.delete()
|
||||||
|
log_render("closed main view due to monitor disconnect")
|
||||||
end
|
end
|
||||||
|
|
||||||
is_used = true
|
|
||||||
engine.monitors.main = nil
|
engine.monitors.main = nil
|
||||||
engine.ui.main_display = nil
|
engine.ui.main_display = nil
|
||||||
|
elseif engine.monitors.flow_iface == iface then
|
||||||
iocontrol.fp_monitor_state("main", false)
|
|
||||||
elseif engine.monitors.flow == device then
|
|
||||||
if engine.ui.flow_display ~= nil then
|
if engine.ui.flow_display ~= nil then
|
||||||
-- delete element tree and clear root UI elements
|
-- delete element tree and clear root UI elements
|
||||||
engine.ui.flow_display.delete()
|
engine.ui.flow_display.delete()
|
||||||
|
log_render("closed flow view due to monitor disconnect")
|
||||||
end
|
end
|
||||||
|
|
||||||
is_used = true
|
|
||||||
engine.monitors.flow = nil
|
engine.monitors.flow = nil
|
||||||
engine.ui.flow_display = nil
|
engine.ui.flow_display = nil
|
||||||
|
|
||||||
iocontrol.fp_monitor_state("flow", false)
|
|
||||||
else
|
else
|
||||||
for idx, monitor in pairs(engine.monitors.unit_displays) do
|
for idx, u_iface in pairs(engine.monitors.unit_ifaces) do
|
||||||
if monitor == device then
|
if u_iface == iface then
|
||||||
if engine.ui.unit_displays[idx] ~= nil then
|
if engine.ui.unit_displays[idx] ~= nil then
|
||||||
|
-- delete element tree and clear root UI elements
|
||||||
engine.ui.unit_displays[idx].delete()
|
engine.ui.unit_displays[idx].delete()
|
||||||
|
log_render("closed unit" .. idx .. "view due to monitor disconnect")
|
||||||
end
|
end
|
||||||
|
|
||||||
is_used = true
|
|
||||||
engine.monitors.unit_displays[idx] = nil
|
engine.monitors.unit_displays[idx] = nil
|
||||||
engine.ui.unit_displays[idx] = nil
|
engine.ui.unit_displays[idx] = nil
|
||||||
|
|
||||||
iocontrol.fp_monitor_state(idx, false)
|
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return is_used
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- handle a monitor peripheral being reconnected
|
-- handle a monitor peripheral being reconnected
|
||||||
---@param name string monitor name
|
---@param name string monitor name
|
||||||
---@param device Monitor monitor
|
function renderer.handle_reconnect(name)
|
||||||
---@return boolean is_used if the monitor is one of the configured monitors
|
|
||||||
function renderer.handle_reconnect(name, device)
|
|
||||||
local is_used = false
|
|
||||||
|
|
||||||
if not engine.monitors then return false end
|
|
||||||
|
|
||||||
-- note: handle_resize is a more adaptive way of re-initializing a connected monitor
|
-- note: handle_resize is a more adaptive way of re-initializing a connected monitor
|
||||||
-- since it can handle a monitor being reconnected that isn't the right size
|
-- since it can handle a monitor being reconnected that isn't the right size
|
||||||
|
renderer.handle_resize(name)
|
||||||
if engine.monitors.main_name == name then
|
|
||||||
is_used = true
|
|
||||||
engine.monitors.main = device
|
|
||||||
|
|
||||||
renderer.handle_resize(name)
|
|
||||||
elseif engine.monitors.flow_name == name then
|
|
||||||
is_used = true
|
|
||||||
engine.monitors.flow = device
|
|
||||||
|
|
||||||
renderer.handle_resize(name)
|
|
||||||
else
|
|
||||||
for idx, monitor in ipairs(engine.monitors.unit_name_map) do
|
|
||||||
if monitor == name then
|
|
||||||
is_used = true
|
|
||||||
engine.monitors.unit_displays[idx] = device
|
|
||||||
|
|
||||||
renderer.handle_resize(name)
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return is_used
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- handle a monitor being resized<br>
|
-- handle a monitor being resized<br>
|
||||||
@@ -372,7 +342,7 @@ function renderer.handle_resize(name)
|
|||||||
|
|
||||||
if not engine.monitors then return false, false end
|
if not engine.monitors then return false, false end
|
||||||
|
|
||||||
if engine.monitors.main_name == name and engine.monitors.main then
|
if engine.monitors.main_iface == name and engine.monitors.main then
|
||||||
local device = engine.monitors.main ---@type Monitor
|
local device = engine.monitors.main ---@type Monitor
|
||||||
|
|
||||||
-- this is necessary if the bottom left block was broken and on reconnect
|
-- this is necessary if the bottom left block was broken and on reconnect
|
||||||
@@ -390,7 +360,7 @@ function renderer.handle_resize(name)
|
|||||||
ui.main_display = nil
|
ui.main_display = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
iocontrol.fp_monitor_state("main", true)
|
iocontrol.fp_monitor_state("main", 2)
|
||||||
|
|
||||||
engine.dmesg_window.setVisible(not engine.ui_ready)
|
engine.dmesg_window.setVisible(not engine.ui_ready)
|
||||||
|
|
||||||
@@ -402,6 +372,8 @@ function renderer.handle_resize(name)
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
if ok then
|
if ok then
|
||||||
|
iocontrol.fp_monitor_state("main", 3)
|
||||||
|
|
||||||
log_render("main view re-draw completed in " .. (util.time_ms() - draw_start) .. "ms")
|
log_render("main view re-draw completed in " .. (util.time_ms() - draw_start) .. "ms")
|
||||||
else
|
else
|
||||||
if ui.main_display then
|
if ui.main_display then
|
||||||
@@ -411,11 +383,10 @@ function renderer.handle_resize(name)
|
|||||||
|
|
||||||
_print_too_small(device)
|
_print_too_small(device)
|
||||||
|
|
||||||
iocontrol.fp_monitor_state("main", false)
|
|
||||||
is_ok = false
|
is_ok = false
|
||||||
end
|
end
|
||||||
else engine.dmesg_window.redraw() end
|
else engine.dmesg_window.redraw() end
|
||||||
elseif engine.monitors.flow_name == name and engine.monitors.flow then
|
elseif engine.monitors.flow_iface == name and engine.monitors.flow then
|
||||||
local device = engine.monitors.flow ---@type Monitor
|
local device = engine.monitors.flow ---@type Monitor
|
||||||
|
|
||||||
-- this is necessary if the bottom left block was broken and on reconnect
|
-- this is necessary if the bottom left block was broken and on reconnect
|
||||||
@@ -428,7 +399,7 @@ function renderer.handle_resize(name)
|
|||||||
ui.flow_display = nil
|
ui.flow_display = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
iocontrol.fp_monitor_state("flow", true)
|
iocontrol.fp_monitor_state("flow", 2)
|
||||||
|
|
||||||
if engine.ui_ready then
|
if engine.ui_ready then
|
||||||
local draw_start = util.time_ms()
|
local draw_start = util.time_ms()
|
||||||
@@ -438,6 +409,8 @@ function renderer.handle_resize(name)
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
if ok then
|
if ok then
|
||||||
|
iocontrol.fp_monitor_state("flow", 3)
|
||||||
|
|
||||||
log_render("flow view re-draw completed in " .. (util.time_ms() - draw_start) .. "ms")
|
log_render("flow view re-draw completed in " .. (util.time_ms() - draw_start) .. "ms")
|
||||||
else
|
else
|
||||||
if ui.flow_display then
|
if ui.flow_display then
|
||||||
@@ -447,12 +420,11 @@ function renderer.handle_resize(name)
|
|||||||
|
|
||||||
_print_too_small(device)
|
_print_too_small(device)
|
||||||
|
|
||||||
iocontrol.fp_monitor_state("flow", false)
|
|
||||||
is_ok = false
|
is_ok = false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
for idx, monitor in ipairs(engine.monitors.unit_name_map) do
|
for idx, monitor in ipairs(engine.monitors.unit_ifaces) do
|
||||||
local device = engine.monitors.unit_displays[idx]
|
local device = engine.monitors.unit_displays[idx]
|
||||||
|
|
||||||
if monitor == name and device then
|
if monitor == name and device then
|
||||||
@@ -466,7 +438,7 @@ function renderer.handle_resize(name)
|
|||||||
ui.unit_displays[idx] = nil
|
ui.unit_displays[idx] = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
iocontrol.fp_monitor_state(idx, true)
|
iocontrol.fp_monitor_state(idx, 2)
|
||||||
|
|
||||||
if engine.ui_ready then
|
if engine.ui_ready then
|
||||||
local draw_start = util.time_ms()
|
local draw_start = util.time_ms()
|
||||||
@@ -476,6 +448,8 @@ function renderer.handle_resize(name)
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
if ok then
|
if ok then
|
||||||
|
iocontrol.fp_monitor_state(idx, 3)
|
||||||
|
|
||||||
log_render("unit " .. idx .. " view re-draw completed in " .. (util.time_ms() - draw_start) .. "ms")
|
log_render("unit " .. idx .. " view re-draw completed in " .. (util.time_ms() - draw_start) .. "ms")
|
||||||
else
|
else
|
||||||
if ui.unit_displays[idx] then
|
if ui.unit_displays[idx] then
|
||||||
@@ -485,7 +459,6 @@ function renderer.handle_resize(name)
|
|||||||
|
|
||||||
_print_too_small(device)
|
_print_too_small(device)
|
||||||
|
|
||||||
iocontrol.fp_monitor_state(idx, false)
|
|
||||||
is_ok = false
|
is_ok = false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -505,12 +478,12 @@ function renderer.handle_mouse(event)
|
|||||||
if engine.fp_ready and event.monitor == "terminal" then
|
if engine.fp_ready and event.monitor == "terminal" then
|
||||||
engine.ui.front_panel.handle_mouse(event)
|
engine.ui.front_panel.handle_mouse(event)
|
||||||
elseif engine.ui_ready then
|
elseif engine.ui_ready then
|
||||||
if event.monitor == engine.monitors.main_name then
|
if event.monitor == engine.monitors.main_iface then
|
||||||
if engine.ui.main_display then engine.ui.main_display.handle_mouse(event) end
|
if engine.ui.main_display then engine.ui.main_display.handle_mouse(event) end
|
||||||
elseif event.monitor == engine.monitors.flow_name then
|
elseif event.monitor == engine.monitors.flow_iface then
|
||||||
if engine.ui.flow_display then engine.ui.flow_display.handle_mouse(event) end
|
if engine.ui.flow_display then engine.ui.flow_display.handle_mouse(event) end
|
||||||
else
|
else
|
||||||
for id, monitor in ipairs(engine.monitors.unit_name_map) do
|
for id, monitor in ipairs(engine.monitors.unit_ifaces) do
|
||||||
local display = engine.ui.unit_displays[id]
|
local display = engine.ui.unit_displays[id]
|
||||||
if event.monitor == monitor and display then
|
if event.monitor == monitor and display then
|
||||||
if display then display.handle_mouse(event) end
|
if display then display.handle_mouse(event) end
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ end
|
|||||||
-- PUBLIC FUNCTIONS --
|
-- PUBLIC FUNCTIONS --
|
||||||
|
|
||||||
-- initialize apisessions
|
-- initialize apisessions
|
||||||
---@param nic nic network interface
|
---@param nic nic API network interface
|
||||||
---@param config crd_config coordinator config
|
---@param config crd_config coordinator config
|
||||||
function apisessions.init(nic, config)
|
function apisessions.init(nic, config)
|
||||||
self.nic = nic
|
self.nic = nic
|
||||||
|
|||||||
@@ -427,6 +427,13 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout)
|
|||||||
}
|
}
|
||||||
|
|
||||||
_send(CRDN_TYPE.API_GET_WASTE, data)
|
_send(CRDN_TYPE.API_GET_WASTE, data)
|
||||||
|
elseif pkt.type == CRDN_TYPE.API_GET_RAD then
|
||||||
|
local data = {}
|
||||||
|
|
||||||
|
for i = 1, #db.units do data[i] = db.units[i].rad_monitors end
|
||||||
|
data[#db.units + 1] = db.facility.rad_monitors
|
||||||
|
|
||||||
|
_send(CRDN_TYPE.API_GET_RAD, data)
|
||||||
else
|
else
|
||||||
log.debug(log_tag .. "handler received unsupported CRDN packet type " .. pkt.type)
|
log.debug(log_tag .. "handler received unsupported CRDN packet type " .. pkt.type)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ local network = require("scada-common.network")
|
|||||||
local ppm = require("scada-common.ppm")
|
local ppm = require("scada-common.ppm")
|
||||||
local util = require("scada-common.util")
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
|
local backplane = require("coordinator.backplane")
|
||||||
local configure = require("coordinator.configure")
|
local configure = require("coordinator.configure")
|
||||||
local coordinator = require("coordinator.coordinator")
|
local coordinator = require("coordinator.coordinator")
|
||||||
local iocontrol = require("coordinator.iocontrol")
|
local iocontrol = require("coordinator.iocontrol")
|
||||||
@@ -19,7 +20,7 @@ local renderer = require("coordinator.renderer")
|
|||||||
local sounder = require("coordinator.sounder")
|
local sounder = require("coordinator.sounder")
|
||||||
local threads = require("coordinator.threads")
|
local threads = require("coordinator.threads")
|
||||||
|
|
||||||
local COORDINATOR_VERSION = "v1.6.11"
|
local COORDINATOR_VERSION = "v1.7.0"
|
||||||
|
|
||||||
local CHUNK_LOAD_DELAY_S = 30.0
|
local CHUNK_LOAD_DELAY_S = 30.0
|
||||||
|
|
||||||
@@ -36,45 +37,13 @@ local log_crypto = coordinator.log_crypto
|
|||||||
-- get configuration
|
-- get configuration
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
|
|
||||||
-- mount connected devices (required for monitor setup)
|
-- first pass configuration check before validating monitors
|
||||||
ppm.mount_all()
|
if not coordinator.load_config() then
|
||||||
|
|
||||||
local wait_on_load = true
|
|
||||||
local loaded, monitors = coordinator.load_config()
|
|
||||||
|
|
||||||
-- if the computer just started, its chunk may have just loaded (...or the user rebooted)
|
|
||||||
-- if monitor config failed, maybe an adjacent chunk containing all or part of a monitor has not loaded yet, so keep trying
|
|
||||||
while wait_on_load and loaded == 2 and os.clock() < CHUNK_LOAD_DELAY_S do
|
|
||||||
term.clear()
|
|
||||||
term.setCursorPos(1, 1)
|
|
||||||
println("There was a monitor configuration problem at boot.\n")
|
|
||||||
println("Startup will keep trying every 2s in case of chunk load delays.\n")
|
|
||||||
println(util.sprintf("The configurator will be started in %ds if all attempts fail.\n", math.max(0, CHUNK_LOAD_DELAY_S - os.clock())))
|
|
||||||
println("(click to skip to the configurator)")
|
|
||||||
|
|
||||||
local timer_id = util.start_timer(2)
|
|
||||||
|
|
||||||
while true do
|
|
||||||
local event, param1 = util.pull_event()
|
|
||||||
if event == "timer" and param1 == timer_id then
|
|
||||||
-- remount and re-attempt
|
|
||||||
ppm.mount_all()
|
|
||||||
loaded, monitors = coordinator.load_config()
|
|
||||||
break
|
|
||||||
elseif event == "mouse_click" or event == "terminate" then
|
|
||||||
wait_on_load = false
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if loaded ~= 0 then
|
|
||||||
-- try to reconfigure (user action)
|
-- try to reconfigure (user action)
|
||||||
local success, error = configure.configure(loaded, monitors)
|
local success, error = configure.configure(1)
|
||||||
if success then
|
if success then
|
||||||
loaded, monitors = coordinator.load_config()
|
if not coordinator.load_config() then
|
||||||
if loaded ~= 0 then
|
println("failed to load a valid configuration, please reconfigure")
|
||||||
println(util.trinary(loaded == 2, "monitor configuration invalid", "failed to load a valid configuration") .. ", please reconfigure")
|
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
@@ -83,9 +52,6 @@ if loaded ~= 0 then
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- passed checks, good now
|
|
||||||
---@cast monitors monitors_struct
|
|
||||||
|
|
||||||
local config = coordinator.config
|
local config = coordinator.config
|
||||||
|
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
@@ -102,6 +68,65 @@ println(">> SCADA Coordinator " .. COORDINATOR_VERSION .. " <<")
|
|||||||
crash.set_env("coordinator", COORDINATOR_VERSION)
|
crash.set_env("coordinator", COORDINATOR_VERSION)
|
||||||
crash.dbg_log_env()
|
crash.dbg_log_env()
|
||||||
|
|
||||||
|
----------------------------------------
|
||||||
|
-- display init
|
||||||
|
----------------------------------------
|
||||||
|
|
||||||
|
-- mount connected devices (required for monitor setup)
|
||||||
|
ppm.mount_all()
|
||||||
|
|
||||||
|
local wait_on_load = true
|
||||||
|
|
||||||
|
local disp_ok, disp_err = backplane.init_displays(config)
|
||||||
|
|
||||||
|
-- if the computer just started, its chunk may have just loaded (...or the user rebooted)
|
||||||
|
-- if monitor config failed, maybe an adjacent chunk containing all or part of a monitor has not loaded yet, so keep trying
|
||||||
|
while wait_on_load and (not disp_ok) and os.clock() < CHUNK_LOAD_DELAY_S do
|
||||||
|
term.clear()
|
||||||
|
term.setCursorPos(1, 1)
|
||||||
|
println("There was a monitor configuration problem at boot.\n")
|
||||||
|
println("Startup will keep trying every 2s in case of chunk load delays.\n")
|
||||||
|
println(util.sprintf("The configurator will be started in %ds if all attempts fail.\n", math.max(0, CHUNK_LOAD_DELAY_S - os.clock())))
|
||||||
|
println("(click to skip to the configurator)")
|
||||||
|
|
||||||
|
local timer_id = util.start_timer(2)
|
||||||
|
|
||||||
|
while true do
|
||||||
|
local event, param1 = util.pull_event()
|
||||||
|
if event == "timer" and param1 == timer_id then
|
||||||
|
-- remount and re-attempt
|
||||||
|
ppm.mount_all()
|
||||||
|
disp_ok, disp_err = backplane.init_displays(config)
|
||||||
|
break
|
||||||
|
elseif event == "mouse_click" or event == "terminate" then
|
||||||
|
wait_on_load = false
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not disp_ok then
|
||||||
|
-- try to reconfigure (user action)
|
||||||
|
local success, error = configure.configure(2, disp_err)
|
||||||
|
if success then
|
||||||
|
if not coordinator.load_config() then
|
||||||
|
println("failed to load a valid configuration, please reconfigure")
|
||||||
|
return
|
||||||
|
else
|
||||||
|
disp_ok, disp_err = backplane.init_displays(config)
|
||||||
|
|
||||||
|
if not disp_ok then
|
||||||
|
println(disp_err)
|
||||||
|
println("please reconfigure")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
println("configuration error: " .. error)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
-- main application
|
-- main application
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
@@ -111,16 +136,12 @@ local function main()
|
|||||||
-- system startup
|
-- system startup
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
|
|
||||||
-- log mounts now since mounting was done before logging was ready
|
-- report versions
|
||||||
ppm.log_mounts()
|
iocontrol.fp_versions(COORDINATOR_VERSION, comms.version)
|
||||||
|
|
||||||
-- report versions/init fp PSIL
|
|
||||||
iocontrol.init_fp(COORDINATOR_VERSION, comms.version)
|
|
||||||
|
|
||||||
-- init renderer
|
-- init renderer
|
||||||
renderer.configure(config)
|
renderer.configure(config)
|
||||||
renderer.set_displays(monitors)
|
renderer.init_displays(backplane.displays())
|
||||||
renderer.init_displays()
|
|
||||||
renderer.init_dmesg()
|
renderer.init_dmesg()
|
||||||
|
|
||||||
-- lets get started!
|
-- lets get started!
|
||||||
@@ -130,6 +151,12 @@ local function main()
|
|||||||
log_sys("system start on " .. os.date("%c"))
|
log_sys("system start on " .. os.date("%c"))
|
||||||
log_boot("starting " .. COORDINATOR_VERSION)
|
log_boot("starting " .. COORDINATOR_VERSION)
|
||||||
|
|
||||||
|
-- message authentication init
|
||||||
|
if type(config.AuthKey) == "string" and string.len(config.AuthKey) > 0 then
|
||||||
|
local init_time = network.init_mac(config.AuthKey)
|
||||||
|
log_crypto("HMAC init took " .. init_time .. "ms")
|
||||||
|
end
|
||||||
|
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
-- memory allocation
|
-- memory allocation
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
@@ -149,15 +176,9 @@ local function main()
|
|||||||
shutdown = false
|
shutdown = false
|
||||||
},
|
},
|
||||||
|
|
||||||
-- core coordinator devices
|
|
||||||
crd_dev = {
|
|
||||||
modem = ppm.get_wireless_modem(),
|
|
||||||
speaker = ppm.get_device("speaker") ---@type Speaker|nil
|
|
||||||
},
|
|
||||||
|
|
||||||
-- system objects
|
-- system objects
|
||||||
|
---@class crd_sys
|
||||||
crd_sys = {
|
crd_sys = {
|
||||||
nic = nil, ---@type nic
|
|
||||||
coord_comms = nil, ---@type coord_comms
|
coord_comms = nil, ---@type coord_comms
|
||||||
conn_watchdog = nil ---@type watchdog
|
conn_watchdog = nil ---@type watchdog
|
||||||
},
|
},
|
||||||
@@ -165,68 +186,33 @@ local function main()
|
|||||||
-- message queues
|
-- message queues
|
||||||
q = {
|
q = {
|
||||||
mq_render = mqueue.new()
|
mq_render = mqueue.new()
|
||||||
|
},
|
||||||
|
|
||||||
|
-- message queue message types
|
||||||
|
q_types = {
|
||||||
|
MQ__RENDER_CMD = {
|
||||||
|
START_MAIN_UI = 1,
|
||||||
|
CLOSE_MAIN_UI = 2
|
||||||
|
},
|
||||||
|
MQ__RENDER_DATA = {
|
||||||
|
MON_CONNECT = 1,
|
||||||
|
MON_DISCONNECT = 2,
|
||||||
|
MON_RESIZE = 3
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
local smem_dev = __shared_memory.crd_dev
|
local smem_sys = __shared_memory.crd_sys
|
||||||
local smem_sys = __shared_memory.crd_sys
|
|
||||||
|
|
||||||
local crd_state = __shared_memory.crd_state
|
local crd_state = __shared_memory.crd_state
|
||||||
|
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
-- setup alarm sounder subsystem
|
-- init system
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
|
|
||||||
if smem_dev.speaker == nil then
|
-- modem and speaker initialization
|
||||||
log_boot("annunciator alarm speaker not found")
|
if not backplane.init(config, __shared_memory) then return end
|
||||||
println("startup> speaker not found")
|
|
||||||
log.fatal("no annunciator alarm speaker found")
|
|
||||||
return
|
|
||||||
else
|
|
||||||
local sounder_start = util.time_ms()
|
|
||||||
log_boot("annunciator alarm speaker connected")
|
|
||||||
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)
|
|
||||||
end
|
|
||||||
|
|
||||||
----------------------------------------
|
|
||||||
-- setup communications
|
|
||||||
----------------------------------------
|
|
||||||
|
|
||||||
-- message authentication init
|
|
||||||
if type(config.AuthKey) == "string" and string.len(config.AuthKey) > 0 then
|
|
||||||
local init_time = network.init_mac(config.AuthKey)
|
|
||||||
log_crypto("HMAC init took " .. init_time .. "ms")
|
|
||||||
end
|
|
||||||
|
|
||||||
-- get the communications modem
|
|
||||||
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")
|
|
||||||
return
|
|
||||||
else
|
|
||||||
log_comms("wireless modem connected")
|
|
||||||
iocontrol.fp_has_modem(true)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- create connection watchdog
|
|
||||||
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
|
|
||||||
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")
|
|
||||||
|
|
||||||
----------------------------------------
|
|
||||||
-- start front panel
|
-- start front panel
|
||||||
----------------------------------------
|
|
||||||
|
|
||||||
log_render("starting front panel UI...")
|
log_render("starting front panel UI...")
|
||||||
|
|
||||||
local fp_message
|
local fp_message
|
||||||
@@ -238,6 +224,16 @@ local function main()
|
|||||||
return
|
return
|
||||||
else log_render("front panel ready") end
|
else log_render("front panel ready") end
|
||||||
|
|
||||||
|
-- create connection watchdog
|
||||||
|
smem_sys.conn_watchdog = util.new_watchdog(config.SVR_Timeout)
|
||||||
|
smem_sys.conn_watchdog.cancel()
|
||||||
|
log.debug("startup> conn watchdog created")
|
||||||
|
|
||||||
|
-- setup comms
|
||||||
|
smem_sys.coord_comms = coordinator.comms(COORDINATOR_VERSION, backplane.active_nic(), backplane.wireless_nic(), smem_sys.conn_watchdog)
|
||||||
|
log.debug("startup> comms init")
|
||||||
|
log_comms("comms initialized")
|
||||||
|
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
-- start system
|
-- start system
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ local ppm = require("scada-common.ppm")
|
|||||||
local tcd = require("scada-common.tcd")
|
local tcd = require("scada-common.tcd")
|
||||||
local util = require("scada-common.util")
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
|
local backplane = require("coordinator.backplane")
|
||||||
local coordinator = require("coordinator.coordinator")
|
local coordinator = require("coordinator.coordinator")
|
||||||
local iocontrol = require("coordinator.iocontrol")
|
local iocontrol = require("coordinator.iocontrol")
|
||||||
local process = require("coordinator.process")
|
local process = require("coordinator.process")
|
||||||
@@ -20,19 +21,8 @@ local log_comms = coordinator.log_comms
|
|||||||
|
|
||||||
local threads = {}
|
local threads = {}
|
||||||
|
|
||||||
local MAIN_CLOCK = 0.5 -- (2Hz, 10 ticks)
|
local MAIN_CLOCK = 0.5 -- 2Hz, 10 ticks
|
||||||
local RENDER_SLEEP = 100 -- (100ms, 2 ticks)
|
local RENDER_SLEEP = 100 -- 100ms, 2 ticks
|
||||||
|
|
||||||
local MQ__RENDER_CMD = {
|
|
||||||
START_MAIN_UI = 1,
|
|
||||||
CLOSE_MAIN_UI = 2
|
|
||||||
}
|
|
||||||
|
|
||||||
local MQ__RENDER_DATA = {
|
|
||||||
MON_CONNECT = 1,
|
|
||||||
MON_DISCONNECT = 2,
|
|
||||||
MON_RESIZE = 3
|
|
||||||
}
|
|
||||||
|
|
||||||
-- main thread
|
-- main thread
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
@@ -44,7 +34,7 @@ function threads.thread__main(smem)
|
|||||||
-- execute thread
|
-- execute thread
|
||||||
function public.exec()
|
function public.exec()
|
||||||
iocontrol.fp_rt_status("main", true)
|
iocontrol.fp_rt_status("main", true)
|
||||||
log.debug("main thread start")
|
log.debug("OS: main thread start")
|
||||||
|
|
||||||
local loop_clock = util.new_clock(MAIN_CLOCK)
|
local loop_clock = util.new_clock(MAIN_CLOCK)
|
||||||
|
|
||||||
@@ -54,10 +44,12 @@ function threads.thread__main(smem)
|
|||||||
log_sys("system started successfully")
|
log_sys("system started successfully")
|
||||||
|
|
||||||
-- load in from shared memory
|
-- load in from shared memory
|
||||||
local crd_state = smem.crd_state
|
local crd_state = smem.crd_state
|
||||||
local nic = smem.crd_sys.nic
|
local coord_comms = smem.crd_sys.coord_comms
|
||||||
local coord_comms = smem.crd_sys.coord_comms
|
local conn_watchdog = smem.crd_sys.conn_watchdog
|
||||||
local conn_watchdog = smem.crd_sys.conn_watchdog
|
|
||||||
|
local MQ__RENDER_CMD = smem.q_types.MQ__RENDER_CMD
|
||||||
|
local MQ__RENDER_DATA = smem.q_types.MQ__RENDER_DATA
|
||||||
|
|
||||||
-- event loop
|
-- event loop
|
||||||
while true do
|
while true do
|
||||||
@@ -66,66 +58,13 @@ function threads.thread__main(smem)
|
|||||||
-- handle event
|
-- handle event
|
||||||
if event == "peripheral_detach" then
|
if event == "peripheral_detach" then
|
||||||
local type, device = ppm.handle_unmount(param1)
|
local type, device = ppm.handle_unmount(param1)
|
||||||
|
|
||||||
if type ~= nil and device ~= nil then
|
if type ~= nil and device ~= nil then
|
||||||
if type == "modem" then
|
backplane.detach(type, device, param1)
|
||||||
---@cast device Modem
|
|
||||||
-- 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
|
|
||||||
smem.q.mq_render.push_command(MQ__RENDER_CMD.CLOSE_MAIN_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
|
|
||||||
---@cast device Monitor
|
|
||||||
smem.q.mq_render.push_data(MQ__RENDER_DATA.MON_DISCONNECT, device)
|
|
||||||
elseif type == "speaker" then
|
|
||||||
---@cast device Speaker
|
|
||||||
log_sys("lost alarm sounder speaker")
|
|
||||||
iocontrol.fp_has_speaker(false)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
elseif event == "peripheral" then
|
elseif event == "peripheral" then
|
||||||
local type, device = ppm.mount(param1)
|
local type, device = ppm.mount(param1)
|
||||||
|
|
||||||
if type ~= nil and device ~= nil then
|
if type ~= nil and device ~= nil then
|
||||||
if type == "modem" then
|
backplane.attach(type, device, param1)
|
||||||
---@cast device Modem
|
|
||||||
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
|
|
||||||
---@cast device Monitor
|
|
||||||
smem.q.mq_render.push_data(MQ__RENDER_DATA.MON_CONNECT, { name = param1, device = device })
|
|
||||||
elseif type == "speaker" then
|
|
||||||
---@cast device Speaker
|
|
||||||
log_sys("alarm sounder speaker reconnected")
|
|
||||||
sounder.reconnect(device)
|
|
||||||
iocontrol.fp_has_speaker(true)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
elseif event == "monitor_resize" then
|
elseif event == "monitor_resize" then
|
||||||
smem.q.mq_render.push_data(MQ__RENDER_DATA.MON_RESIZE, param1)
|
smem.q.mq_render.push_data(MQ__RENDER_DATA.MON_RESIZE, param1)
|
||||||
@@ -137,18 +76,16 @@ function threads.thread__main(smem)
|
|||||||
iocontrol.heartbeat()
|
iocontrol.heartbeat()
|
||||||
|
|
||||||
-- maintain connection
|
-- maintain connection
|
||||||
if nic.is_connected() then
|
local ok, start_ui = coord_comms.try_connect()
|
||||||
local ok, start_ui = coord_comms.try_connect()
|
if not ok then
|
||||||
if not ok then
|
crd_state.link_fail = true
|
||||||
crd_state.link_fail = true
|
crd_state.shutdown = true
|
||||||
crd_state.shutdown = true
|
log_sys("supervisor connection failed, shutting down...")
|
||||||
log_sys("supervisor connection failed, shutting down...")
|
log.fatal("failed to connect to supervisor")
|
||||||
log.fatal("failed to connect to supervisor")
|
break
|
||||||
break
|
elseif start_ui then
|
||||||
elseif start_ui then
|
log_sys("supervisor connected, dispatching main UI start")
|
||||||
log_sys("supervisor connected, dispatching main UI start")
|
smem.q.mq_render.push_command(MQ__RENDER_CMD.START_MAIN_UI)
|
||||||
smem.q.mq_render.push_command(MQ__RENDER_CMD.START_MAIN_UI)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- iterate sessions and free any closed ones
|
-- iterate sessions and free any closed ones
|
||||||
@@ -206,10 +143,10 @@ function threads.thread__main(smem)
|
|||||||
-- check for termination request or UI crash
|
-- check for termination request or UI crash
|
||||||
if event == "terminate" or ppm.should_terminate() then
|
if event == "terminate" or ppm.should_terminate() then
|
||||||
crd_state.shutdown = true
|
crd_state.shutdown = true
|
||||||
log.info("terminate requested, main thread exiting")
|
log.info("OS: terminate requested, main thread exiting")
|
||||||
elseif not crd_state.ui_ok then
|
elseif not crd_state.ui_ok then
|
||||||
crd_state.shutdown = true
|
crd_state.shutdown = true
|
||||||
log.info("terminating due to fatal UI error")
|
log.info("OS: terminating due to fatal UI error")
|
||||||
end
|
end
|
||||||
|
|
||||||
if crd_state.shutdown then
|
if crd_state.shutdown then
|
||||||
@@ -247,7 +184,7 @@ function threads.thread__main(smem)
|
|||||||
-- if status is true, then we are probably exiting, so this won't matter
|
-- 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")
|
-- this thread cannot be slept because it will miss events (namely "terminate")
|
||||||
if not crd_state.shutdown then
|
if not crd_state.shutdown then
|
||||||
log.info("main thread restarting now...")
|
log.info("OS: main thread restarting now...")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -265,11 +202,14 @@ function threads.thread__render(smem)
|
|||||||
-- execute thread
|
-- execute thread
|
||||||
function public.exec()
|
function public.exec()
|
||||||
iocontrol.fp_rt_status("render", true)
|
iocontrol.fp_rt_status("render", true)
|
||||||
log.debug("render thread start")
|
log.debug("OS: render thread start")
|
||||||
|
|
||||||
-- load in from shared memory
|
-- load in from shared memory
|
||||||
local crd_state = smem.crd_state
|
local crd_state = smem.crd_state
|
||||||
local render_queue = smem.q.mq_render
|
local render_queue = smem.q.mq_render
|
||||||
|
|
||||||
|
local MQ__RENDER_CMD = smem.q_types.MQ__RENDER_CMD
|
||||||
|
local MQ__RENDER_DATA = smem.q_types.MQ__RENDER_DATA
|
||||||
|
|
||||||
local last_update = util.time()
|
local last_update = util.time()
|
||||||
|
|
||||||
@@ -317,18 +257,10 @@ function threads.thread__render(smem)
|
|||||||
|
|
||||||
if cmd.key == MQ__RENDER_DATA.MON_CONNECT then
|
if cmd.key == MQ__RENDER_DATA.MON_CONNECT then
|
||||||
-- monitor connected
|
-- monitor connected
|
||||||
if renderer.handle_reconnect(cmd.val.name, cmd.val.device) then
|
renderer.handle_reconnect(cmd.val)
|
||||||
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
|
elseif cmd.key == MQ__RENDER_DATA.MON_DISCONNECT then
|
||||||
-- monitor disconnected
|
-- monitor disconnected
|
||||||
if renderer.handle_disconnect(cmd.val) then
|
renderer.handle_disconnect(cmd.val)
|
||||||
log_sys("lost a configured monitor")
|
|
||||||
else
|
|
||||||
log_sys("lost an unused monitor")
|
|
||||||
end
|
|
||||||
elseif cmd.key == MQ__RENDER_DATA.MON_RESIZE then
|
elseif cmd.key == MQ__RENDER_DATA.MON_RESIZE then
|
||||||
-- monitor resized
|
-- monitor resized
|
||||||
local is_used, is_ok = renderer.handle_resize(cmd.val)
|
local is_used, is_ok = renderer.handle_resize(cmd.val)
|
||||||
@@ -347,7 +279,7 @@ function threads.thread__render(smem)
|
|||||||
|
|
||||||
-- check for termination request
|
-- check for termination request
|
||||||
if crd_state.shutdown then
|
if crd_state.shutdown then
|
||||||
log.info("render thread exiting")
|
log.info("OS: render thread exiting")
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -369,7 +301,7 @@ function threads.thread__render(smem)
|
|||||||
iocontrol.fp_rt_status("render", false)
|
iocontrol.fp_rt_status("render", false)
|
||||||
|
|
||||||
if not crd_state.shutdown then
|
if not crd_state.shutdown then
|
||||||
log.info("render thread restarting in 5 seconds...")
|
log.info("OS: render thread restarting in 5 seconds...")
|
||||||
util.psleep(5)
|
util.psleep(5)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -246,7 +246,7 @@ local function new_view(root, x, y)
|
|||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
local ctl_opts = { "Monitored Max Burn", "Combined Burn Rate", "Charge Level", "Generation Rate" }
|
local ctl_opts = { "Monitored Max Burn", "Combined Burn Rate", "Charge Level", "Generation Rate" }
|
||||||
local mode = RadioButton{parent=proc,x=34,y=1,options=ctl_opts,callback=function()end,radio_colors=cpair(style.theme.accent_dark,style.theme.accent_light),select_color=colors.purple}
|
local mode = RadioButton{parent=proc,x=34,y=1,options=ctl_opts,radio_colors=cpair(style.theme.accent_dark,style.theme.accent_light),select_color=colors.purple}
|
||||||
|
|
||||||
mode.register(facility.ps, "process_mode", mode.set_value)
|
mode.register(facility.ps, "process_mode", mode.set_value)
|
||||||
|
|
||||||
@@ -325,7 +325,7 @@ local function new_view(root, x, y)
|
|||||||
|
|
||||||
TextBox{parent=waste_status,y=i,text="U"..i.." Waste",width=8}
|
TextBox{parent=waste_status,y=i,text="U"..i.." Waste",width=8}
|
||||||
local a_waste = IndicatorLight{parent=waste_status,x=10,y=i,label="Auto",colors=ind_wht}
|
local a_waste = IndicatorLight{parent=waste_status,x=10,y=i,label="Auto",colors=ind_wht}
|
||||||
local waste_m = StateIndicator{parent=waste_status,x=17,y=i,states=style.waste.states_abbrv,value=1,min_width=6}
|
local waste_m = StateIndicator{parent=waste_status,x=17,y=i,states=style.get_waste().states_abbrv,value=1,min_width=6}
|
||||||
|
|
||||||
a_waste.register(unit.unit_ps, "U_AutoWaste", a_waste.update)
|
a_waste.register(unit.unit_ps, "U_AutoWaste", a_waste.update)
|
||||||
waste_m.register(unit.unit_ps, "U_WasteProduct", waste_m.update)
|
waste_m.register(unit.unit_ps, "U_WasteProduct", waste_m.update)
|
||||||
@@ -339,11 +339,11 @@ local function new_view(root, x, y)
|
|||||||
TextBox{parent=waste_sel,text="WASTE PRODUCTION",alignment=ALIGN.CENTER,width=21,x=1,y=2,fg_bg=cutout_fg_bg}
|
TextBox{parent=waste_sel,text="WASTE PRODUCTION",alignment=ALIGN.CENTER,width=21,x=1,y=2,fg_bg=cutout_fg_bg}
|
||||||
|
|
||||||
local rect = Rectangle{parent=waste_sel,border=border(1,colors.brown,true),width=21,height=22,x=1,y=3}
|
local rect = Rectangle{parent=waste_sel,border=border(1,colors.brown,true),width=21,height=22,x=1,y=3}
|
||||||
local status = StateIndicator{parent=rect,x=2,y=1,states=style.waste.states,value=1,min_width=17}
|
local status = StateIndicator{parent=rect,x=2,y=1,states=style.get_waste().states,value=1,min_width=17}
|
||||||
|
|
||||||
status.register(facility.ps, "current_waste_product", status.update)
|
status.register(facility.ps, "current_waste_product", status.update)
|
||||||
|
|
||||||
local waste_prod = RadioButton{parent=rect,x=2,y=3,options=style.waste.options,callback=process.set_process_waste,radio_colors=cpair(style.theme.accent_dark,style.theme.accent_light),select_color=colors.brown}
|
local waste_prod = RadioButton{parent=rect,x=2,y=3,options=style.get_waste().options,callback=process.set_process_waste,radio_colors=cpair(style.theme.accent_dark,style.theme.accent_light),select_color=colors.brown}
|
||||||
|
|
||||||
waste_prod.register(facility.ps, "process_waste_product", waste_prod.set_value)
|
waste_prod.register(facility.ps, "process_waste_product", waste_prod.set_value)
|
||||||
|
|
||||||
|
|||||||
@@ -398,7 +398,7 @@ local function init(parent, id)
|
|||||||
local waste_proc = Rectangle{parent=main,border=border(1,colors.brown,true),thin=true,width=33,height=3,x=46,y=49}
|
local waste_proc = Rectangle{parent=main,border=border(1,colors.brown,true),thin=true,width=33,height=3,x=46,y=49}
|
||||||
local waste_div = Div{parent=waste_proc,x=2,y=1,width=31,height=1}
|
local waste_div = Div{parent=waste_proc,x=2,y=1,width=31,height=1}
|
||||||
|
|
||||||
local waste_mode = MultiButton{parent=waste_div,x=1,y=1,options=style.waste.unit_opts,callback=unit.set_waste,min_width=6}
|
local waste_mode = MultiButton{parent=waste_div,x=1,y=1,options=style.get_waste().unit_opts,callback=unit.set_waste,min_width=6}
|
||||||
|
|
||||||
waste_mode.register(u_ps, "U_WasteMode", waste_mode.set_value)
|
waste_mode.register(u_ps, "U_WasteMode", waste_mode.set_value)
|
||||||
|
|
||||||
@@ -486,7 +486,7 @@ local function init(parent, id)
|
|||||||
local auto_ctl = Rectangle{parent=main,border=border(1,colors.purple,true),thin=true,width=13,height=15,x=32,y=37}
|
local auto_ctl = Rectangle{parent=main,border=border(1,colors.purple,true),thin=true,width=13,height=15,x=32,y=37}
|
||||||
local auto_div = Div{parent=auto_ctl,width=13,height=15,x=1,y=1}
|
local auto_div = Div{parent=auto_ctl,width=13,height=15,x=1,y=1}
|
||||||
|
|
||||||
local group = RadioButton{parent=auto_div,options=types.AUTO_GROUP_NAMES,callback=function()end,radio_colors=cpair(style.theme.accent_dark,style.theme.accent_light),select_color=colors.purple}
|
local group = RadioButton{parent=auto_div,options=types.AUTO_GROUP_NAMES,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)
|
group.register(u_ps, "auto_group_id", function (gid) group.set_value(gid + 1) end)
|
||||||
|
|
||||||
|
|||||||
@@ -179,12 +179,12 @@ local function make(parent, x, y, wide, unit_id)
|
|||||||
pipe(_wide(22, 19), 1, _wide(49, 45), 1, colors.brown, true),
|
pipe(_wide(22, 19), 1, _wide(49, 45), 1, colors.brown, true),
|
||||||
pipe(_wide(22, 19), 5, _wide(28, 24), 5, colors.brown, true),
|
pipe(_wide(22, 19), 5, _wide(28, 24), 5, colors.brown, true),
|
||||||
|
|
||||||
pipe(_wide(64, 53), 1, _wide(95, 81), 1, colors.green, true),
|
pipe(_wide(64, 53), 1, _wide(95, 81), 1, colors.cyan, true),
|
||||||
|
|
||||||
pipe(_wide(48, 43), 4, _wide(71, 61), 4, colors.cyan, true),
|
pipe(_wide(48, 43), 4, _wide(71, 61), 4, colors.green, true),
|
||||||
pipe(_wide(66, 57), 4, _wide(71, 61), 8, colors.cyan, true),
|
pipe(_wide(66, 57), 4, _wide(71, 61), 8, colors.green, true),
|
||||||
pipe(_wide(74, 63), 4, _wide(95, 81), 4, colors.cyan, true),
|
pipe(_wide(74, 63), 4, _wide(95, 81), 4, colors.green, true),
|
||||||
pipe(_wide(74, 63), 8, _wide(133, 111), 8, colors.cyan, true),
|
pipe(_wide(74, 63), 8, _wide(133, 111), 8, colors.green, true),
|
||||||
|
|
||||||
pipe(_wide(108, 94), 1, _wide(132, 110), 6, waste_c, true, true),
|
pipe(_wide(108, 94), 1, _wide(132, 110), 6, waste_c, true, true),
|
||||||
pipe(_wide(108, 94), 4, _wide(111, 95), 1, waste_c, true, true),
|
pipe(_wide(108, 94), 4, _wide(111, 95), 1, waste_c, true, true),
|
||||||
|
|||||||
@@ -268,7 +268,7 @@ local function init(main)
|
|||||||
for i = 1, facility.num_units do
|
for i = 1, facility.num_units do
|
||||||
local y_offset = y_ofs(i)
|
local y_offset = y_ofs(i)
|
||||||
unit_flow(main, flow_x, 5 + y_offset, #emcool_pipes == 0, i)
|
unit_flow(main, flow_x, 5 + y_offset, #emcool_pipes == 0, i)
|
||||||
table.insert(po_pipes, pipe(0, 3 + y_offset, 4, 0, colors.cyan, true, true))
|
table.insert(po_pipes, pipe(0, 3 + y_offset, 4, 0, colors.green, true, true))
|
||||||
util.nop()
|
util.nop()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ local core = require("graphics.core")
|
|||||||
local Div = require("graphics.elements.Div")
|
local Div = require("graphics.elements.Div")
|
||||||
local ListBox = require("graphics.elements.ListBox")
|
local ListBox = require("graphics.elements.ListBox")
|
||||||
local MultiPane = require("graphics.elements.MultiPane")
|
local MultiPane = require("graphics.elements.MultiPane")
|
||||||
|
local Rectangle = require("graphics.elements.Rectangle")
|
||||||
local TextBox = require("graphics.elements.TextBox")
|
local TextBox = require("graphics.elements.TextBox")
|
||||||
|
|
||||||
local TabBar = require("graphics.elements.controls.TabBar")
|
local TabBar = require("graphics.elements.controls.TabBar")
|
||||||
@@ -30,13 +31,16 @@ local LINK_STATE = types.PANEL_LINK_STATE
|
|||||||
local ALIGN = core.ALIGN
|
local ALIGN = core.ALIGN
|
||||||
|
|
||||||
local cpair = core.cpair
|
local cpair = core.cpair
|
||||||
|
local border = core.border
|
||||||
|
|
||||||
local led_grn = style.led_grn
|
local led_grn = style.led_grn
|
||||||
|
|
||||||
-- create new front panel view
|
-- create new front panel view
|
||||||
---@param panel DisplayBox main displaybox
|
---@param panel DisplayBox main displaybox
|
||||||
---@param num_units integer number of units (number of unit monitors)
|
---@param config crd_config configuration
|
||||||
local function init(panel, num_units)
|
local function init(panel, config)
|
||||||
|
local s_hi_box = style.fp_theme.highlight_box
|
||||||
|
|
||||||
local ps = iocontrol.get_db().fp.ps
|
local ps = iocontrol.get_db().fp.ps
|
||||||
|
|
||||||
local term_w, term_h = term.getSize()
|
local term_w, term_h = term.getSize()
|
||||||
@@ -60,7 +64,15 @@ local function init(panel, num_units)
|
|||||||
|
|
||||||
heartbeat.register(ps, "heartbeat", heartbeat.update)
|
heartbeat.register(ps, "heartbeat", heartbeat.update)
|
||||||
|
|
||||||
local modem = LED{parent=system,label="MODEM",colors=led_grn}
|
if config.WirelessModem and config.WiredModem then
|
||||||
|
local wd_modem = LED{parent=system,label="WD MODEM",colors=led_grn}
|
||||||
|
local wl_modem = LED{parent=system,label="WL MODEM",colors=led_grn}
|
||||||
|
wd_modem.register(ps, "has_wd_modem", wd_modem.update)
|
||||||
|
wl_modem.register(ps, "has_wl_modem", wl_modem.update)
|
||||||
|
else
|
||||||
|
local modem = LED{parent=system,label="MODEM",colors=led_grn}
|
||||||
|
modem.register(ps, util.trinary(config.WirelessModem, "has_wl_modem", "has_wd_modem"), modem.update)
|
||||||
|
end
|
||||||
|
|
||||||
if not style.colorblind then
|
if not style.colorblind then
|
||||||
local network = RGBLED{parent=system,label="NETWORK",colors={colors.green,colors.red,colors.yellow,colors.orange,style.fp_ind_bkg}}
|
local network = RGBLED{parent=system,label="NETWORK",colors={colors.green,colors.red,colors.yellow,colors.orange,style.fp_ind_bkg}}
|
||||||
@@ -97,48 +109,44 @@ local function init(panel, num_units)
|
|||||||
|
|
||||||
system.line_break()
|
system.line_break()
|
||||||
|
|
||||||
modem.register(ps, "has_modem", modem.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_main = LED{parent=system,label="RT MAIN",colors=led_grn}
|
||||||
local rt_render = LED{parent=system,label="RT RENDER",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_main.register(ps, "routine__main", rt_main.update)
|
||||||
rt_render.register(ps, "routine__render", rt_render.update)
|
rt_render.register(ps, "routine__render", rt_render.update)
|
||||||
|
|
||||||
---@diagnostic disable-next-line: undefined-field
|
local hmi_devs = Div{parent=main_page,width=16,height=17,x=18,y=2}
|
||||||
local comp_id = util.sprintf("(%d)", os.getComputerID())
|
|
||||||
TextBox{parent=system,x=9,y=4,width=6,text=comp_id,fg_bg=style.fp.disabled_fg}
|
|
||||||
|
|
||||||
local monitors = Div{parent=main_page,width=16,height=17,x=18,y=2}
|
local speaker = LED{parent=hmi_devs,label="SPEAKER",colors=led_grn}
|
||||||
|
speaker.register(ps, "has_speaker", speaker.update)
|
||||||
|
|
||||||
local main_monitor = LED{parent=monitors,label="MAIN MONITOR",colors=led_grn}
|
hmi_devs.line_break()
|
||||||
main_monitor.register(ps, "main_monitor", main_monitor.update)
|
|
||||||
|
|
||||||
local flow_monitor = LED{parent=monitors,label="FLOW MONITOR",colors=led_grn}
|
local main_disp = LEDPair{parent=hmi_devs,label="MAIN DISPLAY",off=style.fp_ind_bkg,c1=colors.red,c2=colors.green}
|
||||||
flow_monitor.register(ps, "flow_monitor", flow_monitor.update)
|
main_disp.register(ps, "main_monitor", main_disp.update)
|
||||||
|
|
||||||
monitors.line_break()
|
local flow_disp = LEDPair{parent=hmi_devs,label="FLOW DISPLAY",off=style.fp_ind_bkg,c1=colors.red,c2=colors.green}
|
||||||
|
flow_disp.register(ps, "flow_monitor", flow_disp.update)
|
||||||
|
|
||||||
for i = 1, num_units do
|
hmi_devs.line_break()
|
||||||
local unit_monitor = LED{parent=monitors,label="UNIT "..i.." MONITOR",colors=led_grn}
|
|
||||||
unit_monitor.register(ps, "unit_monitor_" .. i, unit_monitor.update)
|
for i = 1, config.UnitCount do
|
||||||
|
local unit_disp = LEDPair{parent=hmi_devs,label="UNIT "..i.." DISPLAY",off=style.fp_ind_bkg,c1=colors.red,c2=colors.green}
|
||||||
|
unit_disp.register(ps, "unit_monitor_" .. i, unit_disp.update)
|
||||||
end
|
end
|
||||||
|
|
||||||
--
|
--
|
||||||
-- about footer
|
-- hardware labeling
|
||||||
--
|
--
|
||||||
|
|
||||||
local about = Div{parent=main_page,width=15,height=2,y=term_h-3,fg_bg=style.fp.disabled_fg}
|
local hw_labels = Rectangle{parent=main_page,x=2,y=term_h-7,width=14,height=5,border=border(1,s_hi_box.bkg,true),even_inner=true}
|
||||||
local fw_v = TextBox{parent=about,text="FW: v00.00.00"}
|
|
||||||
local comms_v = TextBox{parent=about,text="NT: v00.00.00"}
|
|
||||||
|
|
||||||
fw_v.register(ps, "version", function (version) fw_v.set_value(util.c("FW: ", version)) end)
|
---@diagnostic disable-next-line: undefined-field
|
||||||
comms_v.register(ps, "comms_version", function (version) comms_v.set_value(util.c("NT: v", version)) end)
|
local comp_id = util.sprintf("%03d", os.getComputerID())
|
||||||
|
|
||||||
|
TextBox{parent=hw_labels,text="FW "..ps.get("version"),fg_bg=s_hi_box}
|
||||||
|
TextBox{parent=hw_labels,text="NT v"..ps.get("comms_version"),fg_bg=s_hi_box}
|
||||||
|
TextBox{parent=hw_labels,text="SN "..comp_id.."-CRD",fg_bg=s_hi_box}
|
||||||
|
|
||||||
--
|
--
|
||||||
-- page handling
|
-- page handling
|
||||||
|
|||||||
@@ -2,16 +2,20 @@
|
|||||||
-- Graphics Style Options
|
-- Graphics Style Options
|
||||||
--
|
--
|
||||||
|
|
||||||
local util = require("scada-common.util")
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
local core = require("graphics.core")
|
local core = require("graphics.core")
|
||||||
local themes = require("graphics.themes")
|
local themes = require("graphics.themes")
|
||||||
|
|
||||||
|
local coordinator = require("coordinator.coordinator")
|
||||||
|
|
||||||
---@class crd_style
|
---@class crd_style
|
||||||
local style = {}
|
local style = {}
|
||||||
|
|
||||||
local cpair = core.cpair
|
local cpair = core.cpair
|
||||||
|
|
||||||
|
local config = coordinator.config
|
||||||
|
|
||||||
-- front panel styling
|
-- front panel styling
|
||||||
|
|
||||||
style.fp_theme = themes.sandstone
|
style.fp_theme = themes.sandstone
|
||||||
@@ -223,27 +227,34 @@ style.sps = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
style.waste = {
|
-- get waste styling, which depends on the configuration
|
||||||
-- auto waste processing states
|
---@return { states: { color: color, text: string }, states_abbrv: { color: color, text: string }, options: string[], unit_opts: { text: string, fg_bg: cpair, active_fg_bg:cpair } }
|
||||||
states = {
|
function style.get_waste()
|
||||||
{ color = cpair(colors.black, colors.green), text = "PLUTONIUM" },
|
local pu_color = util.trinary(config.GreenPuPellet, colors.green, colors.cyan)
|
||||||
{ color = cpair(colors.black, colors.cyan), text = "POLONIUM" },
|
local po_color = util.trinary(config.GreenPuPellet, colors.cyan, colors.green)
|
||||||
{ color = cpair(colors.black, colors.purple), text = "ANTI MATTER" }
|
|
||||||
},
|
return {
|
||||||
states_abbrv = {
|
-- auto waste processing states
|
||||||
{ color = cpair(colors.black, colors.green), text = "Pu" },
|
states = {
|
||||||
{ color = cpair(colors.black, colors.cyan), text = "Po" },
|
{ color = cpair(colors.black, pu_color), text = "PLUTONIUM" },
|
||||||
{ color = cpair(colors.black, colors.purple), text = "AM" }
|
{ color = cpair(colors.black, po_color), text = "POLONIUM" },
|
||||||
},
|
{ color = cpair(colors.black, colors.purple), text = "ANTI MATTER" }
|
||||||
-- process radio button options
|
},
|
||||||
options = { "Plutonium", "Polonium", "Antimatter" },
|
states_abbrv = {
|
||||||
-- unit waste selection
|
{ color = cpair(colors.black, pu_color), text = "Pu" },
|
||||||
unit_opts = {
|
{ color = cpair(colors.black, po_color), text = "Po" },
|
||||||
{ text = "Auto", fg_bg = cpair(colors.black, colors.lightGray), active_fg_bg = cpair(colors.white, colors.gray) },
|
{ color = cpair(colors.black, colors.purple), text = "AM" }
|
||||||
{ text = "Pu", fg_bg = cpair(colors.black, colors.lightGray), active_fg_bg = cpair(colors.black, colors.green) },
|
},
|
||||||
{ text = "Po", fg_bg = cpair(colors.black, colors.lightGray), active_fg_bg = cpair(colors.black, colors.cyan) },
|
-- process radio button options
|
||||||
{ text = "AM", fg_bg = cpair(colors.black, colors.lightGray), active_fg_bg = cpair(colors.black, colors.purple) }
|
options = { "Plutonium", "Polonium", "Antimatter" },
|
||||||
|
-- unit waste selection
|
||||||
|
unit_opts = {
|
||||||
|
{ text = "Auto", fg_bg = cpair(colors.black, colors.lightGray), active_fg_bg = cpair(colors.white, colors.gray) },
|
||||||
|
{ text = "Pu", fg_bg = cpair(colors.black, colors.lightGray), active_fg_bg = cpair(colors.black, pu_color) },
|
||||||
|
{ text = "Po", fg_bg = cpair(colors.black, colors.lightGray), active_fg_bg = cpair(colors.black, po_color) },
|
||||||
|
{ text = "AM", fg_bg = cpair(colors.black, colors.lightGray), active_fg_bg = cpair(colors.black, colors.purple) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
end
|
||||||
|
|
||||||
return style
|
return style
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ local flasher = require("graphics.flasher")
|
|||||||
|
|
||||||
local core = {}
|
local core = {}
|
||||||
|
|
||||||
core.version = "2.4.7"
|
core.version = "2.4.8"
|
||||||
|
|
||||||
core.flasher = flasher
|
core.flasher = flasher
|
||||||
core.events = events
|
core.events = events
|
||||||
|
|||||||
@@ -86,6 +86,13 @@ return function (args)
|
|||||||
e.redraw()
|
e.redraw()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- change the foreground color of the text
|
||||||
|
---@param c color
|
||||||
|
function e.recolor(c)
|
||||||
|
e.w_set_fgd(c)
|
||||||
|
e.redraw()
|
||||||
|
end
|
||||||
|
|
||||||
---@class TextBox:graphics_element
|
---@class TextBox:graphics_element
|
||||||
local TextBox, id = e.complete(true)
|
local TextBox, id = e.complete(true)
|
||||||
|
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ return function (args)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- set the value
|
-- set the value
|
||||||
---@param val integer new value
|
---@param val boolean new value
|
||||||
function e.set_value(val)
|
function e.set_value(val)
|
||||||
e.value = val
|
e.value = val
|
||||||
draw()
|
draw()
|
||||||
|
|||||||
@@ -53,25 +53,44 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit)
|
|||||||
--#region Pocket UI
|
--#region Pocket UI
|
||||||
|
|
||||||
local ui_c_1 = Div{parent=ui_cfg,x=2,y=4,width=24}
|
local ui_c_1 = Div{parent=ui_cfg,x=2,y=4,width=24}
|
||||||
|
local ui_c_2 = Div{parent=ui_cfg,x=2,y=4,width=24}
|
||||||
|
|
||||||
|
local ui_pane = MultiPane{parent=net_cfg,x=1,y=4,panes={ui_c_1,ui_c_2}}
|
||||||
|
|
||||||
TextBox{parent=ui_cfg,x=1,y=2,text=" Pocket UI",fg_bg=cpair(colors.black,colors.lime)}
|
TextBox{parent=ui_cfg,x=1,y=2,text=" Pocket UI",fg_bg=cpair(colors.black,colors.lime)}
|
||||||
|
|
||||||
TextBox{parent=ui_c_1,x=1,y=1,height=3,text="You may customize units below."}
|
TextBox{parent=ui_c_1,x=1,y=1,height=3,text="You may customize UI options below."}
|
||||||
|
|
||||||
TextBox{parent=ui_c_1,x=1,y=4,text="Temperature Scale"}
|
TextBox{parent=ui_c_1,y=4,text="Po/Pu Pellet Color"}
|
||||||
local temp_scale = RadioButton{parent=ui_c_1,x=1,y=5,default=ini_cfg.TempScale,options=types.TEMP_SCALE_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime}
|
TextBox{parent=ui_c_1,x=20,y=4,text="new!",fg_bg=cpair(colors.red,colors._INHERIT)} ---@todo remove NEW tag on next revision
|
||||||
|
local pellet_color = RadioButton{parent=ui_c_1,y=5,default=util.trinary(ini_cfg.GreenPuPellet,1,2),options={"Green Pu/Cyan Po","Cyan Pu/Green Po"},radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime}
|
||||||
|
|
||||||
TextBox{parent=ui_c_1,x=1,y=10,text="Energy Scale"}
|
TextBox{parent=ui_c_1,y=8,height=4,text="In Mekanism 10.4 and later, pellet colors now match gas colors (Cyan Pu/Green Po).",fg_bg=g_lg_fg_bg}
|
||||||
local energy_scale = RadioButton{parent=ui_c_1,x=1,y=11,default=ini_cfg.EnergyScale,options=types.ENERGY_SCALE_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime}
|
|
||||||
|
|
||||||
local function submit_ui_opts()
|
local function submit_ui_opts()
|
||||||
|
tmp_cfg.GreenPuPellet = pellet_color.get_value() == 1
|
||||||
|
ui_pane.set_value(2)
|
||||||
|
end
|
||||||
|
|
||||||
|
PushButton{parent=ui_c_1,x=1,y=15,text="\x1b Back",callback=function()main_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
PushButton{parent=ui_c_1,x=19,y=15,text="Next \x1a",callback=submit_ui_opts,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
|
TextBox{parent=ui_c_2,x=1,y=1,height=3,text="You may customize units below."}
|
||||||
|
|
||||||
|
TextBox{parent=ui_c_2,x=1,y=4,text="Temperature Scale"}
|
||||||
|
local temp_scale = RadioButton{parent=ui_c_2,x=1,y=5,default=ini_cfg.TempScale,options=types.TEMP_SCALE_NAMES,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime}
|
||||||
|
|
||||||
|
TextBox{parent=ui_c_2,x=1,y=10,text="Energy Scale"}
|
||||||
|
local energy_scale = RadioButton{parent=ui_c_2,x=1,y=11,default=ini_cfg.EnergyScale,options=types.ENERGY_SCALE_NAMES,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime}
|
||||||
|
|
||||||
|
local function submit_ui_units()
|
||||||
tmp_cfg.TempScale = temp_scale.get_value()
|
tmp_cfg.TempScale = temp_scale.get_value()
|
||||||
tmp_cfg.EnergyScale = energy_scale.get_value()
|
tmp_cfg.EnergyScale = energy_scale.get_value()
|
||||||
main_pane.set_value(3)
|
main_pane.set_value(3)
|
||||||
end
|
end
|
||||||
|
|
||||||
PushButton{parent=ui_c_1,x=1,y=15,text="\x1b Back",callback=function()main_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=ui_c_2,x=1,y=15,text="\x1b Back",callback=function()ui_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
PushButton{parent=ui_c_1,x=19,y=15,text="Next \x1a",callback=submit_ui_opts,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=ui_c_2,x=19,y=15,text="Next \x1a",callback=submit_ui_units,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
--#endregion
|
--#endregion
|
||||||
|
|
||||||
@@ -197,7 +216,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit)
|
|||||||
TextBox{parent=log_c_1,x=1,y=1,text="Configure logging below."}
|
TextBox{parent=log_c_1,x=1,y=1,text="Configure logging below."}
|
||||||
|
|
||||||
TextBox{parent=log_c_1,x=1,y=3,text="Log File Mode"}
|
TextBox{parent=log_c_1,x=1,y=3,text="Log File Mode"}
|
||||||
local mode = RadioButton{parent=log_c_1,x=1,y=4,default=ini_cfg.LogMode+1,options={"Append on Startup","Replace on Startup"},callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.pink}
|
local mode = RadioButton{parent=log_c_1,x=1,y=4,default=ini_cfg.LogMode+1,options={"Append on Startup","Replace on Startup"},radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.pink}
|
||||||
|
|
||||||
TextBox{parent=log_c_1,x=1,y=7,text="Log File Path"}
|
TextBox{parent=log_c_1,x=1,y=7,text="Log File Path"}
|
||||||
local path = TextField{parent=log_c_1,x=1,y=8,width=24,height=1,value=ini_cfg.LogPath,max_len=128,fg_bg=bw_fg_bg}
|
local path = TextField{parent=log_c_1,x=1,y=8,width=24,height=1,value=ini_cfg.LogPath,max_len=128,fg_bg=bw_fg_bg}
|
||||||
@@ -266,6 +285,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit)
|
|||||||
load_settings(settings_cfg, true)
|
load_settings(settings_cfg, true)
|
||||||
load_settings(ini_cfg)
|
load_settings(ini_cfg)
|
||||||
|
|
||||||
|
try_set(pellet_color, ini_cfg.GreenPuPellet)
|
||||||
try_set(temp_scale, ini_cfg.TempScale)
|
try_set(temp_scale, ini_cfg.TempScale)
|
||||||
try_set(energy_scale, ini_cfg.EnergyScale)
|
try_set(energy_scale, ini_cfg.EnergyScale)
|
||||||
try_set(svr_chan, ini_cfg.SVR_Channel)
|
try_set(svr_chan, ini_cfg.SVR_Channel)
|
||||||
@@ -374,6 +394,8 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit)
|
|||||||
val = string.rep("*", string.len(val))
|
val = string.rep("*", string.len(val))
|
||||||
elseif f[1] == "LogMode" then
|
elseif f[1] == "LogMode" then
|
||||||
val = tri(raw == log.MODE.APPEND, "append", "replace")
|
val = tri(raw == log.MODE.APPEND, "append", "replace")
|
||||||
|
elseif f[1] == "GreenPuPellet" then
|
||||||
|
val = tri(raw, "Green Pu/Cyan Po", "Cyan Pu/Green Po")
|
||||||
elseif f[1] == "TempScale" then
|
elseif f[1] == "TempScale" then
|
||||||
val = util.strval(types.TEMP_SCALE_NAMES[raw])
|
val = util.strval(types.TEMP_SCALE_NAMES[raw])
|
||||||
elseif f[1] == "EnergyScale" then
|
elseif f[1] == "EnergyScale" then
|
||||||
|
|||||||
@@ -29,7 +29,8 @@ local CENTER = core.ALIGN.CENTER
|
|||||||
-- changes to the config data/format to let the user know
|
-- changes to the config data/format to let the user know
|
||||||
local changes = {
|
local changes = {
|
||||||
{ "v0.9.2", { "Added temperature scale options" } },
|
{ "v0.9.2", { "Added temperature scale options" } },
|
||||||
{ "v0.11.3", { "Added energy scale options" } }
|
{ "v0.11.3", { "Added energy scale options" } },
|
||||||
|
{ "v0.13.2", { "Added option for Po/Pu pellet green/cyan pairing" } }
|
||||||
}
|
}
|
||||||
|
|
||||||
---@class pkt_configurator
|
---@class pkt_configurator
|
||||||
@@ -64,6 +65,7 @@ local tool_ctl = {
|
|||||||
|
|
||||||
---@class pkt_config
|
---@class pkt_config
|
||||||
local tmp_cfg = {
|
local tmp_cfg = {
|
||||||
|
GreenPuPellet = false,
|
||||||
TempScale = 1, ---@type TEMP_SCALE
|
TempScale = 1, ---@type TEMP_SCALE
|
||||||
EnergyScale = 1, ---@type ENERGY_SCALE
|
EnergyScale = 1, ---@type ENERGY_SCALE
|
||||||
SVR_Channel = nil, ---@type integer
|
SVR_Channel = nil, ---@type integer
|
||||||
@@ -84,6 +86,7 @@ local settings_cfg = {}
|
|||||||
|
|
||||||
-- all settings fields, their nice names, and their default values
|
-- all settings fields, their nice names, and their default values
|
||||||
local fields = {
|
local fields = {
|
||||||
|
{ "GreenPuPellet", "Pellet Colors", false },
|
||||||
{ "TempScale", "Temperature Scale", types.TEMP_SCALE.KELVIN },
|
{ "TempScale", "Temperature Scale", types.TEMP_SCALE.KELVIN },
|
||||||
{ "EnergyScale", "Energy Scale", types.ENERGY_SCALE.FE },
|
{ "EnergyScale", "Energy Scale", types.ENERGY_SCALE.FE },
|
||||||
{ "SVR_Channel", "SVR Channel", 16240 },
|
{ "SVR_Channel", "SVR Channel", 16240 },
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ local ENERGY_UNITS = types.ENERGY_SCALE_UNITS
|
|||||||
local TEMP_SCALE = types.TEMP_SCALE
|
local TEMP_SCALE = types.TEMP_SCALE
|
||||||
local TEMP_UNITS = types.TEMP_SCALE_UNITS
|
local TEMP_UNITS = types.TEMP_SCALE_UNITS
|
||||||
|
|
||||||
---@todo nominal trip time is ping (0ms to 10ms usually)
|
|
||||||
local WARN_TT = 40
|
local WARN_TT = 40
|
||||||
local HIGH_TT = 80
|
local HIGH_TT = 80
|
||||||
|
|
||||||
@@ -35,8 +34,9 @@ iocontrol.LINK_STATE = LINK_STATE
|
|||||||
|
|
||||||
---@class pocket_ioctl
|
---@class pocket_ioctl
|
||||||
local io = {
|
local io = {
|
||||||
version = "unknown",
|
version = "unknown", -- pocket version
|
||||||
ps = psil.create()
|
ps = psil.create(), -- pocket PSIL
|
||||||
|
loader_require = { sv = false, api = false }
|
||||||
}
|
}
|
||||||
|
|
||||||
local config = nil ---@type pkt_config
|
local config = nil ---@type pkt_config
|
||||||
@@ -85,12 +85,13 @@ function iocontrol.init_core(pkt_comms, nav, cfg)
|
|||||||
|
|
||||||
get_tone_states = function () comms.diag__get_alarm_tones() end,
|
get_tone_states = function () comms.diag__get_alarm_tones() end,
|
||||||
|
|
||||||
ready_warn = nil, ---@type TextBox
|
tone_buttons = {}, ---@type SwitchButton[]
|
||||||
tone_buttons = {}, ---@type SwitchButton[]
|
alarm_buttons = {} ---@type Checkbox[]
|
||||||
alarm_buttons = {}, ---@type Checkbox[]
|
|
||||||
tone_indicators = {} ---@type IndicatorLight[] indicators to update from supervisor tone states
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
-- computer list
|
||||||
|
io.diag.get_comps = function () comms.diag__get_computers() end
|
||||||
|
|
||||||
-- API access
|
-- API access
|
||||||
---@class pocket_ioctl_api
|
---@class pocket_ioctl_api
|
||||||
io.api = {
|
io.api = {
|
||||||
@@ -98,7 +99,8 @@ function iocontrol.init_core(pkt_comms, nav, cfg)
|
|||||||
get_unit = function (unit) comms.api__get_unit(unit) end,
|
get_unit = function (unit) comms.api__get_unit(unit) end,
|
||||||
get_ctrl = function () comms.api__get_control() end,
|
get_ctrl = function () comms.api__get_control() end,
|
||||||
get_proc = function () comms.api__get_process() end,
|
get_proc = function () comms.api__get_process() end,
|
||||||
get_waste = function () comms.api__get_waste() end
|
get_waste = function () comms.api__get_waste() end,
|
||||||
|
get_rad = function () comms.api__get_rad() end
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -184,7 +186,9 @@ function iocontrol.init_fac(conf)
|
|||||||
sps_data_tbl = {}, ---@type sps_session_db[]
|
sps_data_tbl = {}, ---@type sps_session_db[]
|
||||||
|
|
||||||
tank_ps_tbl = {}, ---@type psil[]
|
tank_ps_tbl = {}, ---@type psil[]
|
||||||
tank_data_tbl = {} ---@type dynamicv_session_db[]
|
tank_data_tbl = {}, ---@type dynamicv_session_db[]
|
||||||
|
|
||||||
|
rad_monitors = {} ---@type { radiation: radiation_reading, raw: number }[]
|
||||||
}
|
}
|
||||||
|
|
||||||
-- create induction and SPS tables (currently only 1 of each is supported)
|
-- create induction and SPS tables (currently only 1 of each is supported)
|
||||||
@@ -264,7 +268,9 @@ function iocontrol.init_fac(conf)
|
|||||||
turbine_data_tbl = {}, ---@type turbinev_session_db[]
|
turbine_data_tbl = {}, ---@type turbinev_session_db[]
|
||||||
|
|
||||||
tank_ps_tbl = {}, ---@type psil[]
|
tank_ps_tbl = {}, ---@type psil[]
|
||||||
tank_data_tbl = {} ---@type dynamicv_session_db[]
|
tank_data_tbl = {}, ---@type dynamicv_session_db[]
|
||||||
|
|
||||||
|
rad_monitors = {} ---@type { radiation: radiation_reading, raw: number }[]
|
||||||
}
|
}
|
||||||
|
|
||||||
-- on other facility modes, overwrite unit TANK option with facility tank defs
|
-- on other facility modes, overwrite unit TANK option with facility tank defs
|
||||||
|
|||||||
136
pocket/iorx.lua
136
pocket/iorx.lua
@@ -2,10 +2,13 @@
|
|||||||
-- I/O Control's Data Receive (Rx) Handlers
|
-- I/O Control's Data Receive (Rx) Handlers
|
||||||
--
|
--
|
||||||
|
|
||||||
|
local comms = require("scada-common.comms")
|
||||||
local const = require("scada-common.constants")
|
local const = require("scada-common.constants")
|
||||||
local types = require("scada-common.types")
|
local types = require("scada-common.types")
|
||||||
local util = require("scada-common.util")
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
|
local DEV_TYPE = comms.DEVICE_TYPE
|
||||||
|
|
||||||
local ALARM = types.ALARM
|
local ALARM = types.ALARM
|
||||||
local ALARM_STATE = types.ALARM_STATE
|
local ALARM_STATE = types.ALARM_STATE
|
||||||
|
|
||||||
@@ -57,7 +60,6 @@ local function _record_multiblock_status(faulted, data, ps)
|
|||||||
ps.publish("formed", data.formed)
|
ps.publish("formed", data.formed)
|
||||||
ps.publish("faulted", faulted)
|
ps.publish("faulted", faulted)
|
||||||
|
|
||||||
---@todo revisit this
|
|
||||||
if data.build then
|
if data.build then
|
||||||
for key, val in pairs(data.build) do ps.publish(key, val) end
|
for key, val in pairs(data.build) do ps.publish(key, val) end
|
||||||
end
|
end
|
||||||
@@ -314,7 +316,7 @@ function iorx.record_unit_data(data)
|
|||||||
local function blue(text) return { text = text, color = colors.blue } end
|
local function blue(text) return { text = text, color = colors.blue } end
|
||||||
|
|
||||||
-- if unit.reactor_data.rps_status then
|
-- if unit.reactor_data.rps_status then
|
||||||
-- for k, v in pairs(unit.alarms) do
|
-- for k, _ in pairs(unit.alarms) do
|
||||||
-- unit.alarms[k] = ALARM_STATE.TRIPPED
|
-- unit.alarms[k] = ALARM_STATE.TRIPPED
|
||||||
-- end
|
-- end
|
||||||
-- end
|
-- end
|
||||||
@@ -658,7 +660,6 @@ function iorx.record_waste_data(data)
|
|||||||
fac.ps.publish("sps_process_rate", f_data[9])
|
fac.ps.publish("sps_process_rate", f_data[9])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
-- update facility app with facility and unit data from API_GET_FAC_DTL
|
-- update facility app with facility and unit data from API_GET_FAC_DTL
|
||||||
---@param data table
|
---@param data table
|
||||||
function iorx.record_fac_detail_data(data)
|
function iorx.record_fac_detail_data(data)
|
||||||
@@ -819,6 +820,135 @@ function iorx.record_fac_detail_data(data)
|
|||||||
s_ps.publish("SPSStateStatus", s_stat)
|
s_ps.publish("SPSStateStatus", s_stat)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- update the radiation monitor app with radiation monitor data from API_GET_RAD
|
||||||
|
---@param data table
|
||||||
|
function iorx.record_radiation_data(data)
|
||||||
|
-- unit radiation monitors
|
||||||
|
|
||||||
|
for u_id = 1, #io.units do
|
||||||
|
local unit = io.units[u_id]
|
||||||
|
local max_rad = 0
|
||||||
|
local connected = {}
|
||||||
|
|
||||||
|
unit.radiation = types.new_zero_radiation_reading()
|
||||||
|
unit.rad_monitors = data[u_id]
|
||||||
|
|
||||||
|
for id, mon in pairs(unit.rad_monitors) do
|
||||||
|
table.insert(connected, id)
|
||||||
|
|
||||||
|
unit.unit_ps.publish("radiation@" .. id, mon.radiation)
|
||||||
|
|
||||||
|
if mon.raw > max_rad then
|
||||||
|
max_rad = mon.raw
|
||||||
|
unit.radiation = mon.radiation
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
unit.unit_ps.publish("radiation", unit.radiation)
|
||||||
|
unit.unit_ps.publish("radiation_monitors", textutils.serialize(connected))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- facility radiation monitors
|
||||||
|
|
||||||
|
local fac = io.facility
|
||||||
|
|
||||||
|
fac.radiation = types.new_zero_radiation_reading()
|
||||||
|
fac.rad_monitors = data[#io.units + 1]
|
||||||
|
|
||||||
|
local max_rad = 0
|
||||||
|
local connected = {}
|
||||||
|
|
||||||
|
for id, mon in pairs(fac.rad_monitors) do
|
||||||
|
table.insert(connected, id)
|
||||||
|
|
||||||
|
fac.ps.publish("radiation@" .. id, mon.radiation)
|
||||||
|
|
||||||
|
if mon.raw > max_rad then
|
||||||
|
max_rad = mon.raw
|
||||||
|
fac.radiation = mon.radiation
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
fac.ps.publish("radiation", fac.radiation)
|
||||||
|
fac.ps.publish("radiation_monitors", textutils.serialize(connected))
|
||||||
|
end
|
||||||
|
|
||||||
|
local comp_record = {}
|
||||||
|
|
||||||
|
-- update the computers app with the network data from INFO_LIST_CMP
|
||||||
|
---@param data table
|
||||||
|
function iorx.record_network_data(data)
|
||||||
|
local ps = io.ps
|
||||||
|
local connected = {}
|
||||||
|
local crd_online = false
|
||||||
|
|
||||||
|
ps.publish("comp_online", #data)
|
||||||
|
|
||||||
|
-- add/update connected computers
|
||||||
|
for i = 1, #data do
|
||||||
|
local entry = data[i]
|
||||||
|
local type = entry[1]
|
||||||
|
local id = entry[2]
|
||||||
|
local pfx = "comp_" .. id
|
||||||
|
|
||||||
|
connected[id] = true
|
||||||
|
|
||||||
|
if type == DEV_TYPE.SVR then
|
||||||
|
ps.publish("comp_svr_addr", id)
|
||||||
|
ps.publish("comp_svr_fw", entry[3])
|
||||||
|
elseif type == DEV_TYPE.CRD then
|
||||||
|
crd_online = true
|
||||||
|
ps.publish("comp_crd_addr", id)
|
||||||
|
ps.publish("comp_crd_fw", entry[3])
|
||||||
|
ps.publish("comp_crd_rtt", entry[4])
|
||||||
|
else
|
||||||
|
ps.publish(pfx .. "_type", entry[1])
|
||||||
|
ps.publish(pfx .. "_addr", id)
|
||||||
|
ps.publish(pfx .. "_fw", entry[3])
|
||||||
|
ps.publish(pfx .. "_rtt", entry[4])
|
||||||
|
|
||||||
|
if type == DEV_TYPE.PLC then
|
||||||
|
ps.publish(pfx .. "_unit", entry[5])
|
||||||
|
end
|
||||||
|
|
||||||
|
if not comp_record[id] then
|
||||||
|
comp_record[id] = true
|
||||||
|
|
||||||
|
-- trigger the app to create the new element
|
||||||
|
ps.publish("comp_connect", id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- handle the coordinator being online or not
|
||||||
|
-- no need to worry about the supervisor since this data is from the supervisor, so it has to be 'online' if received
|
||||||
|
ps.publish("comp_crd_online", crd_online)
|
||||||
|
if not crd_online then
|
||||||
|
ps.publish("comp_crd_addr", "---")
|
||||||
|
ps.publish("comp_crd_fw", "---")
|
||||||
|
ps.publish("comp_crd_rtt", "---")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- reset the published value
|
||||||
|
ps.publish("comp_connect", false)
|
||||||
|
|
||||||
|
-- remove disconnected computers
|
||||||
|
for id, state in pairs(comp_record) do
|
||||||
|
if state and not connected[id] then
|
||||||
|
comp_record[id] = false
|
||||||
|
|
||||||
|
-- trigger the app to delete the element
|
||||||
|
ps.publish("comp_disconnect", id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- reset the published value
|
||||||
|
ps.publish("comp_disconnect", false)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- clear the tracked connected computer record
|
||||||
|
function iorx.clear_comp_record() comp_record = {} end
|
||||||
|
|
||||||
return function (io_obj)
|
return function (io_obj)
|
||||||
io = io_obj
|
io = io_obj
|
||||||
return iorx
|
return iorx
|
||||||
|
|||||||
@@ -16,16 +16,10 @@ local LINK_STATE = iocontrol.LINK_STATE
|
|||||||
|
|
||||||
local pocket = {}
|
local pocket = {}
|
||||||
|
|
||||||
local MQ__RENDER_CMD = {
|
|
||||||
UNLOAD_SV_APPS = 1,
|
|
||||||
UNLOAD_API_APPS = 2
|
|
||||||
}
|
|
||||||
|
|
||||||
local MQ__RENDER_DATA = {
|
local MQ__RENDER_DATA = {
|
||||||
LOAD_APP = 1
|
LOAD_APP = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
pocket.MQ__RENDER_CMD = MQ__RENDER_CMD
|
|
||||||
pocket.MQ__RENDER_DATA = MQ__RENDER_DATA
|
pocket.MQ__RENDER_DATA = MQ__RENDER_DATA
|
||||||
|
|
||||||
---@type pkt_config
|
---@type pkt_config
|
||||||
@@ -38,6 +32,7 @@ pocket.config = config
|
|||||||
function pocket.load_config()
|
function pocket.load_config()
|
||||||
if not settings.load("/pocket.settings") then return false end
|
if not settings.load("/pocket.settings") then return false end
|
||||||
|
|
||||||
|
config.GreenPuPellet = settings.get("GreenPuPellet")
|
||||||
config.TempScale = settings.get("TempScale")
|
config.TempScale = settings.get("TempScale")
|
||||||
config.EnergyScale = settings.get("EnergyScale")
|
config.EnergyScale = settings.get("EnergyScale")
|
||||||
|
|
||||||
@@ -54,6 +49,7 @@ function pocket.load_config()
|
|||||||
|
|
||||||
local cfv = util.new_validator()
|
local cfv = util.new_validator()
|
||||||
|
|
||||||
|
cfv.assert_type_bool(config.GreenPuPellet)
|
||||||
cfv.assert_type_int(config.TempScale)
|
cfv.assert_type_int(config.TempScale)
|
||||||
cfv.assert_range(config.TempScale, 1, 4)
|
cfv.assert_range(config.TempScale, 1, 4)
|
||||||
cfv.assert_type_int(config.EnergyScale)
|
cfv.assert_type_int(config.EnergyScale)
|
||||||
@@ -86,7 +82,7 @@ local APP_ID = {
|
|||||||
-- core UI
|
-- core UI
|
||||||
ROOT = 1,
|
ROOT = 1,
|
||||||
LOADER = 2,
|
LOADER = 2,
|
||||||
-- main app pages
|
-- main apps
|
||||||
UNITS = 3,
|
UNITS = 3,
|
||||||
FACILITY = 4,
|
FACILITY = 4,
|
||||||
CONTROL = 5,
|
CONTROL = 5,
|
||||||
@@ -94,11 +90,12 @@ local APP_ID = {
|
|||||||
WASTE = 7,
|
WASTE = 7,
|
||||||
GUIDE = 8,
|
GUIDE = 8,
|
||||||
ABOUT = 9,
|
ABOUT = 9,
|
||||||
-- diagnostic app pages
|
RADMON = 10,
|
||||||
ALARMS = 10,
|
-- diagnostic apps
|
||||||
-- other
|
ALARMS = 11,
|
||||||
DUMMY = 11,
|
COMPS = 12,
|
||||||
NUM_APPS = 11
|
-- count
|
||||||
|
NUM_APPS = 12
|
||||||
}
|
}
|
||||||
|
|
||||||
pocket.APP_ID = APP_ID
|
pocket.APP_ID = APP_ID
|
||||||
@@ -147,7 +144,7 @@ function pocket.init_nav(smem)
|
|||||||
---@class pocket_app
|
---@class pocket_app
|
||||||
local app = {
|
local app = {
|
||||||
loaded = false,
|
loaded = false,
|
||||||
cur_page = nil, ---@type nav_tree_page
|
cur_page = nil, ---@type nav_tree_page|nil
|
||||||
pane = pane,
|
pane = pane,
|
||||||
paned_pages = {}, ---@type nav_tree_page[]
|
paned_pages = {}, ---@type nav_tree_page[]
|
||||||
sidebar_items = {} ---@type sidebar_entry[]
|
sidebar_items = {} ---@type sidebar_entry[]
|
||||||
@@ -267,21 +264,28 @@ function pocket.init_nav(smem)
|
|||||||
|
|
||||||
-- open an app
|
-- open an app
|
||||||
---@param app_id POCKET_APP_ID
|
---@param app_id POCKET_APP_ID
|
||||||
---@param on_loaded? function
|
---@param on_ready? function
|
||||||
function nav.open_app(app_id, on_loaded)
|
function nav.open_app(app_id, on_ready)
|
||||||
-- reset help return on navigating out of an app
|
-- reset help return on navigating out of an app
|
||||||
if app_id == APP_ID.ROOT then self.help_return = nil end
|
if app_id == APP_ID.ROOT then self.help_return = nil end
|
||||||
|
|
||||||
local app = self.apps[app_id]
|
local app = self.apps[app_id]
|
||||||
if app then
|
if app then
|
||||||
if app.requires_conn() and not smem.pkt_sys.pocket_comms.is_linked() then
|
local p_comms = smem.pkt_sys.pocket_comms
|
||||||
|
local req_sv, req_api = app.check_requires()
|
||||||
|
|
||||||
|
if (req_sv and not p_comms.is_sv_linked()) or (req_api and not p_comms.is_api_linked()) then
|
||||||
|
-- report required connction(s)
|
||||||
|
iocontrol.get_db().loader_require = { sv = req_sv, api = req_api }
|
||||||
|
iocontrol.get_db().ps.toggle("loader_reqs")
|
||||||
|
|
||||||
-- bring up the app loader
|
-- bring up the app loader
|
||||||
self.loader_return = app_id
|
self.loader_return = app_id
|
||||||
app_id = APP_ID.LOADER
|
app_id = APP_ID.LOADER
|
||||||
app = self.apps[app_id]
|
app = self.apps[app_id]
|
||||||
else self.loader_return = nil end
|
else self.loader_return = nil end
|
||||||
|
|
||||||
if not app.loaded then smem.q.mq_render.push_data(MQ__RENDER_DATA.LOAD_APP, { app_id, on_loaded }) end
|
if not app.loaded then smem.q.mq_render.push_data(MQ__RENDER_DATA.LOAD_APP, { app_id, on_ready }) end
|
||||||
|
|
||||||
self.cur_app = app_id
|
self.cur_app = app_id
|
||||||
self.pane.set_value(app_id)
|
self.pane.set_value(app_id)
|
||||||
@@ -289,6 +293,8 @@ function pocket.init_nav(smem)
|
|||||||
if #app.sidebar_items > 0 then
|
if #app.sidebar_items > 0 then
|
||||||
self.sidebar.update(app.sidebar_items)
|
self.sidebar.update(app.sidebar_items)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if app.loaded and on_ready then on_ready() end
|
||||||
else
|
else
|
||||||
log.debug("tried to open unknown app")
|
log.debug("tried to open unknown app")
|
||||||
end
|
end
|
||||||
@@ -336,7 +342,7 @@ function pocket.init_nav(smem)
|
|||||||
function nav.get_containers() return self.containers end
|
function nav.get_containers() return self.containers end
|
||||||
|
|
||||||
-- get the currently active page
|
-- get the currently active page
|
||||||
---@return nav_tree_page
|
---@return nav_tree_page|nil
|
||||||
function nav.get_current_page()
|
function nav.get_current_page()
|
||||||
return self.apps[self.cur_app].get_current_page()
|
return self.apps[self.cur_app].get_current_page()
|
||||||
end
|
end
|
||||||
@@ -365,8 +371,7 @@ function pocket.init_nav(smem)
|
|||||||
self.help_return = self.cur_app
|
self.help_return = self.cur_app
|
||||||
|
|
||||||
nav.open_app(APP_ID.GUIDE, function ()
|
nav.open_app(APP_ID.GUIDE, function ()
|
||||||
local show = self.help_map[key]
|
if self.help_map[key] then self.help_map[key]() end
|
||||||
if show then show() end
|
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -486,20 +491,28 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav)
|
|||||||
function public.close_sv()
|
function public.close_sv()
|
||||||
sv_watchdog.cancel()
|
sv_watchdog.cancel()
|
||||||
nav.unload_sv()
|
nav.unload_sv()
|
||||||
self.sv.linked = false
|
|
||||||
self.sv.r_seq_num = nil
|
self.sv.r_seq_num = nil
|
||||||
self.sv.addr = comms.BROADCAST
|
self.sv.addr = comms.BROADCAST
|
||||||
_send_sv(MGMT_TYPE.CLOSE, {})
|
|
||||||
|
if self.sv.linked then
|
||||||
|
self.sv.linked = false
|
||||||
|
_send_sv(MGMT_TYPE.CLOSE, {})
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- close connection to coordinator API server
|
-- close connection to coordinator API server
|
||||||
function public.close_api()
|
function public.close_api()
|
||||||
api_watchdog.cancel()
|
api_watchdog.cancel()
|
||||||
nav.unload_api()
|
nav.unload_api()
|
||||||
self.api.linked = false
|
|
||||||
self.api.r_seq_num = nil
|
self.api.r_seq_num = nil
|
||||||
self.api.addr = comms.BROADCAST
|
self.api.addr = comms.BROADCAST
|
||||||
_send_crd(MGMT_TYPE.CLOSE, {})
|
|
||||||
|
if self.api.linked then
|
||||||
|
self.api.linked = false
|
||||||
|
_send_crd(MGMT_TYPE.CLOSE, {})
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- close the connections to the servers
|
-- close the connections to the servers
|
||||||
@@ -510,24 +523,18 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav)
|
|||||||
|
|
||||||
-- attempt to re-link if any of the dependent links aren't active
|
-- attempt to re-link if any of the dependent links aren't active
|
||||||
function public.link_update()
|
function public.link_update()
|
||||||
if not self.sv.linked then
|
if not (self.sv.linked and self.api.linked) then
|
||||||
if self.api.linked then
|
if self.api.linked then
|
||||||
iocontrol.report_link_state(LINK_STATE.API_LINK_ONLY, false, nil)
|
iocontrol.report_link_state(LINK_STATE.API_LINK_ONLY, false, nil)
|
||||||
|
elseif self.sv.linked then
|
||||||
|
iocontrol.report_link_state(LINK_STATE.SV_LINK_ONLY, nil, false)
|
||||||
else
|
else
|
||||||
iocontrol.report_link_state(LINK_STATE.UNLINKED, false, false)
|
iocontrol.report_link_state(LINK_STATE.UNLINKED, false, false)
|
||||||
end
|
end
|
||||||
|
|
||||||
if self.establish_delay_counter <= 0 then
|
if self.establish_delay_counter <= 0 then
|
||||||
_send_sv_establish()
|
if not self.api.linked then _send_api_establish() end
|
||||||
self.establish_delay_counter = 4
|
if not self.sv.linked then _send_sv_establish() end
|
||||||
else
|
|
||||||
self.establish_delay_counter = self.establish_delay_counter - 1
|
|
||||||
end
|
|
||||||
elseif not self.api.linked then
|
|
||||||
iocontrol.report_link_state(LINK_STATE.SV_LINK_ONLY, nil, false)
|
|
||||||
|
|
||||||
if self.establish_delay_counter <= 0 then
|
|
||||||
_send_api_establish()
|
|
||||||
self.establish_delay_counter = 4
|
self.establish_delay_counter = 4
|
||||||
else
|
else
|
||||||
self.establish_delay_counter = self.establish_delay_counter - 1
|
self.establish_delay_counter = self.establish_delay_counter - 1
|
||||||
@@ -554,6 +561,11 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav)
|
|||||||
if self.sv.linked then _send_sv(MGMT_TYPE.DIAG_ALARM_SET, { id, state }) end
|
if self.sv.linked then _send_sv(MGMT_TYPE.DIAG_ALARM_SET, { id, state }) end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- supervisor get connected computers
|
||||||
|
function public.diag__get_computers()
|
||||||
|
if self.sv.linked then _send_sv(MGMT_TYPE.INFO_LIST_CMP, {}) end
|
||||||
|
end
|
||||||
|
|
||||||
-- coordinator get facility app data
|
-- coordinator get facility app data
|
||||||
function public.api__get_facility()
|
function public.api__get_facility()
|
||||||
if self.api.linked then _send_api(CRDN_TYPE.API_GET_FAC_DTL, {}) end
|
if self.api.linked then _send_api(CRDN_TYPE.API_GET_FAC_DTL, {}) end
|
||||||
@@ -579,6 +591,11 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav)
|
|||||||
if self.api.linked then _send_api(CRDN_TYPE.API_GET_WASTE, {}) end
|
if self.api.linked then _send_api(CRDN_TYPE.API_GET_WASTE, {}) end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- coordinator get radiation app data
|
||||||
|
function public.api__get_rad()
|
||||||
|
if self.api.linked then _send_api(CRDN_TYPE.API_GET_RAD, {}) end
|
||||||
|
end
|
||||||
|
|
||||||
-- send a facility command
|
-- send a facility command
|
||||||
---@param cmd FAC_COMMAND command
|
---@param cmd FAC_COMMAND command
|
||||||
---@param option any? optional option options for the optional options (like waste mode)
|
---@param option any? optional option options for the optional options (like waste mode)
|
||||||
@@ -655,6 +672,7 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav)
|
|||||||
---@param packet mgmt_frame|crdn_frame|nil
|
---@param packet mgmt_frame|crdn_frame|nil
|
||||||
function public.handle_packet(packet)
|
function public.handle_packet(packet)
|
||||||
local diag = iocontrol.get_db().diag
|
local diag = iocontrol.get_db().diag
|
||||||
|
local ps = iocontrol.get_db().ps
|
||||||
|
|
||||||
if packet ~= nil then
|
if packet ~= nil then
|
||||||
local l_chan = packet.scada_frame.local_channel()
|
local l_chan = packet.scada_frame.local_channel()
|
||||||
@@ -755,6 +773,10 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav)
|
|||||||
if _check_length(packet, #iocontrol.get_db().units + 1) then
|
if _check_length(packet, #iocontrol.get_db().units + 1) then
|
||||||
iocontrol.rx.record_waste_data(packet.data)
|
iocontrol.rx.record_waste_data(packet.data)
|
||||||
end
|
end
|
||||||
|
elseif packet.type == CRDN_TYPE.API_GET_RAD then
|
||||||
|
if _check_length(packet, #iocontrol.get_db().units + 1) then
|
||||||
|
iocontrol.rx.record_radiation_data(packet.data)
|
||||||
|
end
|
||||||
else _fail_type(packet) end
|
else _fail_type(packet) end
|
||||||
else
|
else
|
||||||
log.debug("discarding coordinator SCADA_CRDN packet before linked")
|
log.debug("discarding coordinator SCADA_CRDN packet before linked")
|
||||||
@@ -902,23 +924,23 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav)
|
|||||||
elseif packet.type == MGMT_TYPE.DIAG_TONE_GET then
|
elseif packet.type == MGMT_TYPE.DIAG_TONE_GET then
|
||||||
if _check_length(packet, 8) then
|
if _check_length(packet, 8) then
|
||||||
for i = 1, #packet.data do
|
for i = 1, #packet.data do
|
||||||
diag.tone_test.tone_indicators[i].update(packet.data[i] == true)
|
ps.publish("alarm_tone_" .. i, packet.data[i] == true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
elseif packet.type == MGMT_TYPE.DIAG_TONE_SET then
|
elseif packet.type == MGMT_TYPE.DIAG_TONE_SET then
|
||||||
if packet.length == 1 and packet.data[1] == false then
|
if packet.length == 1 and packet.data[1] == false then
|
||||||
diag.tone_test.ready_warn.set_value("testing denied")
|
ps.publish("alarm_ready_warn", "testing denied")
|
||||||
log.debug("supervisor SCADA diag tone set failed")
|
log.debug("supervisor SCADA diag tone set failed")
|
||||||
elseif packet.length == 2 and type(packet.data[2]) == "table" then
|
elseif packet.length == 2 and type(packet.data[2]) == "table" then
|
||||||
local ready = packet.data[1]
|
local ready = packet.data[1]
|
||||||
local states = packet.data[2]
|
local states = packet.data[2]
|
||||||
|
|
||||||
diag.tone_test.ready_warn.set_value(util.trinary(ready, "", "system not idle"))
|
ps.publish("alarm_ready_warn", util.trinary(ready, "", "system not idle"))
|
||||||
|
|
||||||
for i = 1, #states do
|
for i = 1, #states do
|
||||||
if diag.tone_test.tone_buttons[i] ~= nil then
|
if diag.tone_test.tone_buttons[i] ~= nil then
|
||||||
diag.tone_test.tone_buttons[i].set_value(states[i] == true)
|
diag.tone_test.tone_buttons[i].set_value(states[i] == true)
|
||||||
diag.tone_test.tone_indicators[i].update(states[i] == true)
|
ps.publish("alarm_tone_" .. i, states[i] == true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
@@ -926,13 +948,13 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav)
|
|||||||
end
|
end
|
||||||
elseif packet.type == MGMT_TYPE.DIAG_ALARM_SET then
|
elseif packet.type == MGMT_TYPE.DIAG_ALARM_SET then
|
||||||
if packet.length == 1 and packet.data[1] == false then
|
if packet.length == 1 and packet.data[1] == false then
|
||||||
diag.tone_test.ready_warn.set_value("testing denied")
|
ps.publish("alarm_ready_warn", "testing denied")
|
||||||
log.debug("supervisor SCADA diag alarm set failed")
|
log.debug("supervisor SCADA diag alarm set failed")
|
||||||
elseif packet.length == 2 and type(packet.data[2]) == "table" then
|
elseif packet.length == 2 and type(packet.data[2]) == "table" then
|
||||||
local ready = packet.data[1]
|
local ready = packet.data[1]
|
||||||
local states = packet.data[2]
|
local states = packet.data[2]
|
||||||
|
|
||||||
diag.tone_test.ready_warn.set_value(util.trinary(ready, "", "system not idle"))
|
ps.publish("alarm_ready_warn", util.trinary(ready, "", "system not idle"))
|
||||||
|
|
||||||
for i = 1, #states do
|
for i = 1, #states do
|
||||||
if diag.tone_test.alarm_buttons[i] ~= nil then
|
if diag.tone_test.alarm_buttons[i] ~= nil then
|
||||||
@@ -942,6 +964,8 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav)
|
|||||||
else
|
else
|
||||||
log.debug("supervisor SCADA diag alarm set packet length/type mismatch")
|
log.debug("supervisor SCADA diag alarm set packet length/type mismatch")
|
||||||
end
|
end
|
||||||
|
elseif packet.type == MGMT_TYPE.INFO_LIST_CMP then
|
||||||
|
iocontrol.rx.record_network_data(packet.data)
|
||||||
else _fail_type(packet) end
|
else _fail_type(packet) end
|
||||||
elseif packet.type == MGMT_TYPE.ESTABLISH then
|
elseif packet.type == MGMT_TYPE.ESTABLISH then
|
||||||
-- connection with supervisor established
|
-- connection with supervisor established
|
||||||
|
|||||||
@@ -2,8 +2,10 @@
|
|||||||
-- SCADA System Access on a Pocket Computer
|
-- SCADA System Access on a Pocket Computer
|
||||||
--
|
--
|
||||||
|
|
||||||
---@diagnostic disable-next-line: undefined-global
|
---@diagnostic disable-next-line: lowercase-global
|
||||||
local _is_pocket_env = pocket or periphemu -- luacheck: ignore pocket
|
pocket = pocket or periphemu -- luacheck: ignore pocket
|
||||||
|
|
||||||
|
local _is_pocket_env = pocket -- luacheck: ignore pocket
|
||||||
|
|
||||||
require("/initenv").init_env()
|
require("/initenv").init_env()
|
||||||
|
|
||||||
@@ -20,7 +22,7 @@ local pocket = require("pocket.pocket")
|
|||||||
local renderer = require("pocket.renderer")
|
local renderer = require("pocket.renderer")
|
||||||
local threads = require("pocket.threads")
|
local threads = require("pocket.threads")
|
||||||
|
|
||||||
local POCKET_VERSION = "v0.13.1-beta"
|
local POCKET_VERSION = "v1.0.4"
|
||||||
|
|
||||||
local println = util.println
|
local println = util.println
|
||||||
local println_ts = util.println_ts
|
local println_ts = util.println_ts
|
||||||
|
|||||||
@@ -11,10 +11,9 @@ local core = require("graphics.core")
|
|||||||
|
|
||||||
local threads = {}
|
local threads = {}
|
||||||
|
|
||||||
local MAIN_CLOCK = 0.5 -- (2Hz, 10 ticks)
|
local MAIN_CLOCK = 0.5 -- 2Hz, 10 ticks
|
||||||
local RENDER_SLEEP = 100 -- (100ms, 2 ticks)
|
local RENDER_SLEEP = 100 -- 100ms, 2 ticks
|
||||||
|
|
||||||
local MQ__RENDER_CMD = pocket.MQ__RENDER_CMD
|
|
||||||
local MQ__RENDER_DATA = pocket.MQ__RENDER_DATA
|
local MQ__RENDER_DATA = pocket.MQ__RENDER_DATA
|
||||||
|
|
||||||
-- main thread
|
-- main thread
|
||||||
@@ -58,8 +57,10 @@ function threads.thread__main(smem)
|
|||||||
pocket_comms.link_update()
|
pocket_comms.link_update()
|
||||||
|
|
||||||
-- update any tasks for the active page
|
-- update any tasks for the active page
|
||||||
local page_tasks = nav.get_current_page().tasks
|
if nav.get_current_page() then
|
||||||
for i = 1, #page_tasks do page_tasks[i]() end
|
local page_tasks = nav.get_current_page().tasks
|
||||||
|
for i = 1, #page_tasks do page_tasks[i]() end
|
||||||
|
end
|
||||||
|
|
||||||
loop_clock.start()
|
loop_clock.start()
|
||||||
elseif sv_wd.is_timer(param1) then
|
elseif sv_wd.is_timer(param1) then
|
||||||
@@ -157,9 +158,6 @@ function threads.thread__render(smem)
|
|||||||
if msg ~= nil then
|
if msg ~= nil then
|
||||||
if msg.qtype == mqueue.TYPE.COMMAND then
|
if msg.qtype == mqueue.TYPE.COMMAND then
|
||||||
-- received a command
|
-- received a command
|
||||||
if msg.message == MQ__RENDER_CMD.UNLOAD_SV_APPS then
|
|
||||||
elseif msg.message == MQ__RENDER_CMD.UNLOAD_API_APPS then
|
|
||||||
end
|
|
||||||
elseif msg.qtype == mqueue.TYPE.DATA then
|
elseif msg.qtype == mqueue.TYPE.DATA then
|
||||||
-- received data
|
-- received data
|
||||||
local cmd = msg.message ---@type queue_data
|
local cmd = msg.message ---@type queue_data
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
--
|
--
|
||||||
-- System Apps
|
-- About Page
|
||||||
--
|
--
|
||||||
|
|
||||||
local comms = require("scada-common.comms")
|
local comms = require("scada-common.comms")
|
||||||
@@ -24,25 +24,21 @@ local cpair = core.cpair
|
|||||||
|
|
||||||
local APP_ID = pocket.APP_ID
|
local APP_ID = pocket.APP_ID
|
||||||
|
|
||||||
-- create system app pages
|
-- create about page view
|
||||||
---@param root Container parent
|
---@param root Container parent
|
||||||
local function create_pages(root)
|
local function create_pages(root)
|
||||||
local db = iocontrol.get_db()
|
local db = iocontrol.get_db()
|
||||||
|
|
||||||
----------------
|
local frame = Div{parent=root,x=1,y=1}
|
||||||
-- About Page --
|
|
||||||
----------------
|
|
||||||
|
|
||||||
local about_root = Div{parent=root,x=1,y=1}
|
local app = db.nav.register_app(APP_ID.ABOUT, frame)
|
||||||
|
|
||||||
local about_app = db.nav.register_app(APP_ID.ABOUT, about_root)
|
local about_page = app.new_page(nil, 1)
|
||||||
|
local nt_page = app.new_page(about_page, 2)
|
||||||
|
local fw_page = app.new_page(about_page, 3)
|
||||||
|
local hw_page = app.new_page(about_page, 4)
|
||||||
|
|
||||||
local about_page = about_app.new_page(nil, 1)
|
local about = Div{parent=frame,x=1,y=2}
|
||||||
local nt_page = about_app.new_page(about_page, 2)
|
|
||||||
local fw_page = about_app.new_page(about_page, 3)
|
|
||||||
local hw_page = about_app.new_page(about_page, 4)
|
|
||||||
|
|
||||||
local about = Div{parent=about_root,x=1,y=2}
|
|
||||||
|
|
||||||
TextBox{parent=about,y=1,text="System Information",alignment=ALIGN.CENTER}
|
TextBox{parent=about,y=1,text="System Information",alignment=ALIGN.CENTER}
|
||||||
|
|
||||||
@@ -58,7 +54,7 @@ local function create_pages(root)
|
|||||||
|
|
||||||
local config = pocket.config
|
local config = pocket.config
|
||||||
|
|
||||||
local nt_div = Div{parent=about_root,x=1,y=2}
|
local nt_div = Div{parent=frame,x=1,y=2}
|
||||||
TextBox{parent=nt_div,y=1,text="Network Details",alignment=ALIGN.CENTER}
|
TextBox{parent=nt_div,y=1,text="Network Details",alignment=ALIGN.CENTER}
|
||||||
|
|
||||||
PushButton{parent=nt_div,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=about_page.nav_to}
|
PushButton{parent=nt_div,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=about_page.nav_to}
|
||||||
@@ -87,7 +83,7 @@ local function create_pages(root)
|
|||||||
|
|
||||||
--#region Firmware Versions
|
--#region Firmware Versions
|
||||||
|
|
||||||
local fw_div = Div{parent=about_root,x=1,y=2}
|
local fw_div = Div{parent=frame,x=1,y=2}
|
||||||
TextBox{parent=fw_div,y=1,text="Firmware Versions",alignment=ALIGN.CENTER}
|
TextBox{parent=fw_div,y=1,text="Firmware Versions",alignment=ALIGN.CENTER}
|
||||||
|
|
||||||
PushButton{parent=fw_div,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=about_page.nav_to}
|
PushButton{parent=fw_div,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=about_page.nav_to}
|
||||||
@@ -123,7 +119,7 @@ local function create_pages(root)
|
|||||||
|
|
||||||
--#region Host Versions
|
--#region Host Versions
|
||||||
|
|
||||||
local hw_div = Div{parent=about_root,x=1,y=2}
|
local hw_div = Div{parent=frame,x=1,y=2}
|
||||||
TextBox{parent=hw_div,y=1,text="Host Versions",alignment=ALIGN.CENTER}
|
TextBox{parent=hw_div,y=1,text="Host Versions",alignment=ALIGN.CENTER}
|
||||||
|
|
||||||
PushButton{parent=hw_div,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=about_page.nav_to}
|
PushButton{parent=hw_div,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=about_page.nav_to}
|
||||||
@@ -138,9 +134,9 @@ local function create_pages(root)
|
|||||||
|
|
||||||
--#endregion
|
--#endregion
|
||||||
|
|
||||||
local root_pane = MultiPane{parent=about_root,x=1,y=1,panes={about,nt_div,fw_div,hw_div}}
|
local root_pane = MultiPane{parent=frame,x=1,y=1,panes={about,nt_div,fw_div,hw_div}}
|
||||||
|
|
||||||
about_app.set_root_pane(root_pane)
|
app.set_root_pane(root_pane)
|
||||||
end
|
end
|
||||||
|
|
||||||
return create_pages
|
return create_pages
|
||||||
184
pocket/ui/apps/alarm.lua
Normal file
184
pocket/ui/apps/alarm.lua
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
--
|
||||||
|
-- Alarm Test App
|
||||||
|
--
|
||||||
|
|
||||||
|
local iocontrol = require("pocket.iocontrol")
|
||||||
|
local pocket = require("pocket.pocket")
|
||||||
|
|
||||||
|
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.IndicatorLight")
|
||||||
|
|
||||||
|
local Checkbox = require("graphics.elements.controls.Checkbox")
|
||||||
|
local PushButton = require("graphics.elements.controls.PushButton")
|
||||||
|
local SwitchButton = require("graphics.elements.controls.SwitchButton")
|
||||||
|
|
||||||
|
local ALIGN = core.ALIGN
|
||||||
|
local cpair = core.cpair
|
||||||
|
|
||||||
|
local APP_ID = pocket.APP_ID
|
||||||
|
|
||||||
|
local c_wht_gray = cpair(colors.white, colors.gray)
|
||||||
|
local c_red_gray = cpair(colors.red, colors.gray)
|
||||||
|
local c_yel_gray = cpair(colors.yellow, colors.gray)
|
||||||
|
local c_blue_gray = cpair(colors.blue, colors.gray)
|
||||||
|
|
||||||
|
-- create alarm test page view
|
||||||
|
---@param root Container parent
|
||||||
|
local function new_view(root)
|
||||||
|
local db = iocontrol.get_db()
|
||||||
|
local ps = db.ps
|
||||||
|
local ttest = db.diag.tone_test
|
||||||
|
|
||||||
|
local frame = Div{parent=root,x=1,y=1}
|
||||||
|
|
||||||
|
local app = db.nav.register_app(APP_ID.ALARMS, frame, nil, true)
|
||||||
|
|
||||||
|
local main = Div{parent=frame,x=1,y=1}
|
||||||
|
local page_div = Div{parent=main,y=2,width=main.get_width()}
|
||||||
|
|
||||||
|
--#region alarm testing
|
||||||
|
|
||||||
|
local alarm_page = app.new_page(nil, 1)
|
||||||
|
alarm_page.tasks = { db.diag.tone_test.get_tone_states }
|
||||||
|
|
||||||
|
local alarms_div = Div{parent=page_div}
|
||||||
|
|
||||||
|
TextBox{parent=alarms_div,text="Alarm Sounder Tests",alignment=ALIGN.CENTER}
|
||||||
|
|
||||||
|
local alarm_ready_warn = TextBox{parent=alarms_div,y=2,text="",alignment=ALIGN.CENTER,fg_bg=cpair(colors.yellow,colors.black)}
|
||||||
|
alarm_ready_warn.register(ps, "alarm_ready_warn", alarm_ready_warn.set_value)
|
||||||
|
|
||||||
|
local alarm_page_states = Div{parent=alarms_div,x=2,y=3,height=5,width=8}
|
||||||
|
|
||||||
|
TextBox{parent=alarm_page_states,text="States",alignment=ALIGN.CENTER}
|
||||||
|
local ta_1 = IndicatorLight{parent=alarm_page_states,label="1",colors=c_blue_gray}
|
||||||
|
local ta_2 = IndicatorLight{parent=alarm_page_states,label="2",colors=c_blue_gray}
|
||||||
|
local ta_3 = IndicatorLight{parent=alarm_page_states,label="3",colors=c_blue_gray}
|
||||||
|
local ta_4 = IndicatorLight{parent=alarm_page_states,label="4",colors=c_blue_gray}
|
||||||
|
local ta_5 = IndicatorLight{parent=alarm_page_states,x=6,y=2,label="5",colors=c_blue_gray}
|
||||||
|
local ta_6 = IndicatorLight{parent=alarm_page_states,x=6,label="6",colors=c_blue_gray}
|
||||||
|
local ta_7 = IndicatorLight{parent=alarm_page_states,x=6,label="7",colors=c_blue_gray}
|
||||||
|
local ta_8 = IndicatorLight{parent=alarm_page_states,x=6,label="8",colors=c_blue_gray}
|
||||||
|
|
||||||
|
local ta = { ta_1, ta_2, ta_3, ta_4, ta_5, ta_6, ta_7, ta_8 }
|
||||||
|
|
||||||
|
for i = 1, #ta do
|
||||||
|
ta[i].register(ps, "alarm_tone_" .. i, ta[i].update)
|
||||||
|
end
|
||||||
|
|
||||||
|
local alarms = Div{parent=alarms_div,x=11,y=3,height=15,fg_bg=cpair(colors.lightGray,colors.black)}
|
||||||
|
|
||||||
|
TextBox{parent=alarms,text="Alarms (\x13)",alignment=ALIGN.CENTER,fg_bg=alarms_div.get_fg_bg()}
|
||||||
|
|
||||||
|
local alarm_btns = {}
|
||||||
|
alarm_btns[1] = Checkbox{parent=alarms,label="BREACH",min_width=15,box_fg_bg=c_red_gray,callback=ttest.test_breach}
|
||||||
|
alarm_btns[2] = Checkbox{parent=alarms,label="RADIATION",min_width=15,box_fg_bg=c_red_gray,callback=ttest.test_rad}
|
||||||
|
alarm_btns[3] = Checkbox{parent=alarms,label="RCT LOST",min_width=15,box_fg_bg=c_red_gray,callback=ttest.test_lost}
|
||||||
|
alarm_btns[4] = Checkbox{parent=alarms,label="CRIT DAMAGE",min_width=15,box_fg_bg=c_red_gray,callback=ttest.test_crit}
|
||||||
|
alarm_btns[5] = Checkbox{parent=alarms,label="DAMAGE",min_width=15,box_fg_bg=c_red_gray,callback=ttest.test_dmg}
|
||||||
|
alarm_btns[6] = Checkbox{parent=alarms,label="OVER TEMP",min_width=15,box_fg_bg=c_red_gray,callback=ttest.test_overtemp}
|
||||||
|
alarm_btns[7] = Checkbox{parent=alarms,label="HIGH TEMP",min_width=15,box_fg_bg=c_yel_gray,callback=ttest.test_hightemp}
|
||||||
|
alarm_btns[8] = Checkbox{parent=alarms,label="WASTE LEAK",min_width=15,box_fg_bg=c_red_gray,callback=ttest.test_wasteleak}
|
||||||
|
alarm_btns[9] = Checkbox{parent=alarms,label="WASTE HIGH",min_width=15,box_fg_bg=c_yel_gray,callback=ttest.test_highwaste}
|
||||||
|
alarm_btns[10] = Checkbox{parent=alarms,label="RPS TRANS",min_width=15,box_fg_bg=c_yel_gray,callback=ttest.test_rps}
|
||||||
|
alarm_btns[11] = Checkbox{parent=alarms,label="RCS TRANS",min_width=15,box_fg_bg=c_yel_gray,callback=ttest.test_rcs}
|
||||||
|
alarm_btns[12] = Checkbox{parent=alarms,label="TURBINE TRP",min_width=15,box_fg_bg=c_red_gray,callback=ttest.test_turbinet}
|
||||||
|
|
||||||
|
ttest.alarm_buttons = alarm_btns
|
||||||
|
|
||||||
|
local function stop_all_alarms()
|
||||||
|
for i = 1, #alarm_btns do alarm_btns[i].set_value(false) end
|
||||||
|
ttest.stop_alarms()
|
||||||
|
end
|
||||||
|
|
||||||
|
PushButton{parent=alarms,x=3,y=15,text="STOP \x13",min_width=8,fg_bg=cpair(colors.black,colors.red),active_fg_bg=c_wht_gray,callback=stop_all_alarms}
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
|
--#region direct tone testing
|
||||||
|
|
||||||
|
local tones_page = app.new_page(nil, 2)
|
||||||
|
tones_page.tasks = { db.diag.tone_test.get_tone_states }
|
||||||
|
|
||||||
|
local tones_div = Div{parent=page_div}
|
||||||
|
|
||||||
|
TextBox{parent=tones_div,text="Alarm Sounder Tests",alignment=ALIGN.CENTER}
|
||||||
|
|
||||||
|
local tone_ready_warn = TextBox{parent=tones_div,y=2,text="",alignment=ALIGN.CENTER,fg_bg=cpair(colors.yellow,colors.black)}
|
||||||
|
tone_ready_warn.register(ps, "alarm_ready_warn", tone_ready_warn.set_value)
|
||||||
|
|
||||||
|
local tone_page_states = Div{parent=tones_div,x=3,y=3,height=5,width=8}
|
||||||
|
|
||||||
|
TextBox{parent=tone_page_states,text="States",alignment=ALIGN.CENTER}
|
||||||
|
local tt_1 = IndicatorLight{parent=tone_page_states,label="1",colors=c_blue_gray}
|
||||||
|
local tt_2 = IndicatorLight{parent=tone_page_states,label="2",colors=c_blue_gray}
|
||||||
|
local tt_3 = IndicatorLight{parent=tone_page_states,label="3",colors=c_blue_gray}
|
||||||
|
local tt_4 = IndicatorLight{parent=tone_page_states,label="4",colors=c_blue_gray}
|
||||||
|
local tt_5 = IndicatorLight{parent=tone_page_states,x=6,y=2,label="5",colors=c_blue_gray}
|
||||||
|
local tt_6 = IndicatorLight{parent=tone_page_states,x=6,label="6",colors=c_blue_gray}
|
||||||
|
local tt_7 = IndicatorLight{parent=tone_page_states,x=6,label="7",colors=c_blue_gray}
|
||||||
|
local tt_8 = IndicatorLight{parent=tone_page_states,x=6,label="8",colors=c_blue_gray}
|
||||||
|
|
||||||
|
local tt = { tt_1, tt_2, tt_3, tt_4, tt_5, tt_6, tt_7, tt_8 }
|
||||||
|
|
||||||
|
for i = 1, #tt do
|
||||||
|
tt[i].register(ps, "alarm_tone_" .. i, tt[i].update)
|
||||||
|
end
|
||||||
|
|
||||||
|
local tones = Div{parent=tones_div,x=14,y=3,height=10,width=8,fg_bg=cpair(colors.black,colors.yellow)}
|
||||||
|
|
||||||
|
TextBox{parent=tones,text="Tones",alignment=ALIGN.CENTER,fg_bg=tones_div.get_fg_bg()}
|
||||||
|
|
||||||
|
local test_btns = {}
|
||||||
|
test_btns[1] = SwitchButton{parent=tones,text="TEST 1",min_width=8,active_fg_bg=c_wht_gray,callback=ttest.test_1}
|
||||||
|
test_btns[2] = SwitchButton{parent=tones,text="TEST 2",min_width=8,active_fg_bg=c_wht_gray,callback=ttest.test_2}
|
||||||
|
test_btns[3] = SwitchButton{parent=tones,text="TEST 3",min_width=8,active_fg_bg=c_wht_gray,callback=ttest.test_3}
|
||||||
|
test_btns[4] = SwitchButton{parent=tones,text="TEST 4",min_width=8,active_fg_bg=c_wht_gray,callback=ttest.test_4}
|
||||||
|
test_btns[5] = SwitchButton{parent=tones,text="TEST 5",min_width=8,active_fg_bg=c_wht_gray,callback=ttest.test_5}
|
||||||
|
test_btns[6] = SwitchButton{parent=tones,text="TEST 6",min_width=8,active_fg_bg=c_wht_gray,callback=ttest.test_6}
|
||||||
|
test_btns[7] = SwitchButton{parent=tones,text="TEST 7",min_width=8,active_fg_bg=c_wht_gray,callback=ttest.test_7}
|
||||||
|
test_btns[8] = SwitchButton{parent=tones,text="TEST 8",min_width=8,active_fg_bg=c_wht_gray,callback=ttest.test_8}
|
||||||
|
|
||||||
|
ttest.tone_buttons = test_btns
|
||||||
|
|
||||||
|
local function stop_all_tones()
|
||||||
|
for i = 1, #test_btns do test_btns[i].set_value(false) end
|
||||||
|
ttest.stop_tones()
|
||||||
|
end
|
||||||
|
|
||||||
|
PushButton{parent=tones,text="STOP",min_width=8,active_fg_bg=c_wht_gray,fg_bg=cpair(colors.black,colors.red),callback=stop_all_tones}
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
|
--#region info page
|
||||||
|
|
||||||
|
app.new_page(nil, 3)
|
||||||
|
|
||||||
|
local info_div = Div{parent=page_div}
|
||||||
|
|
||||||
|
TextBox{parent=info_div,x=2,y=1,text="This app provides tools to test alarm sounds by alarm and by tone (1-8)."}
|
||||||
|
TextBox{parent=info_div,x=2,y=6,text="The system must be idle (all units stopped with no alarms active) for testing to run."}
|
||||||
|
TextBox{parent=info_div,x=2,y=12,text="Testing will be denied unless you enabled it in the Supervisor's configuration."}
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
|
-- setup multipane
|
||||||
|
local u_pane = MultiPane{parent=page_div,x=1,y=1,panes={alarms_div,tones_div,info_div}}
|
||||||
|
app.set_root_pane(u_pane)
|
||||||
|
|
||||||
|
local list = {
|
||||||
|
{ label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = db.nav.go_home },
|
||||||
|
{ label = " \x13 ", color = core.cpair(colors.black, colors.red), callback = function () app.switcher(1) end },
|
||||||
|
{ label = " \x0f ", color = core.cpair(colors.black, colors.yellow), callback = function () app.switcher(2) end },
|
||||||
|
{ label = " ? ", color = core.cpair(colors.black, colors.blue), callback = function () app.switcher(3) end }
|
||||||
|
}
|
||||||
|
|
||||||
|
app.set_sidebar(list)
|
||||||
|
end
|
||||||
|
|
||||||
|
return new_view
|
||||||
297
pocket/ui/apps/comps.lua
Normal file
297
pocket/ui/apps/comps.lua
Normal file
@@ -0,0 +1,297 @@
|
|||||||
|
--
|
||||||
|
-- Computer List App
|
||||||
|
--
|
||||||
|
|
||||||
|
local comms = require("scada-common.comms")
|
||||||
|
local const = require("scada-common.constants")
|
||||||
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
|
local iocontrol = require("pocket.iocontrol")
|
||||||
|
local pocket = require("pocket.pocket")
|
||||||
|
|
||||||
|
local style = require("pocket.ui.style")
|
||||||
|
|
||||||
|
local core = require("graphics.core")
|
||||||
|
|
||||||
|
local Div = require("graphics.elements.Div")
|
||||||
|
local ListBox = require("graphics.elements.ListBox")
|
||||||
|
local MultiPane = require("graphics.elements.MultiPane")
|
||||||
|
local Rectangle = require("graphics.elements.Rectangle")
|
||||||
|
local TextBox = require("graphics.elements.TextBox")
|
||||||
|
|
||||||
|
local WaitingAnim = require("graphics.elements.animations.Waiting")
|
||||||
|
|
||||||
|
local DataIndicator = require("graphics.elements.indicators.DataIndicator")
|
||||||
|
|
||||||
|
local DEV_TYPE = comms.DEVICE_TYPE
|
||||||
|
|
||||||
|
local ALIGN = core.ALIGN
|
||||||
|
local cpair = core.cpair
|
||||||
|
local border = core.border
|
||||||
|
|
||||||
|
local APP_ID = pocket.APP_ID
|
||||||
|
|
||||||
|
local lu_col = style.label_unit_pair
|
||||||
|
local box_label = cpair(colors.lightGray, colors.gray)
|
||||||
|
|
||||||
|
-- new computer list page view
|
||||||
|
---@param root Container parent
|
||||||
|
local function new_view(root)
|
||||||
|
local db = iocontrol.get_db()
|
||||||
|
|
||||||
|
local frame = Div{parent=root,x=1,y=1}
|
||||||
|
|
||||||
|
local app = db.nav.register_app(APP_ID.COMPS, frame, nil, true, false)
|
||||||
|
|
||||||
|
local load_div = Div{parent=frame,x=1,y=1}
|
||||||
|
local main = Div{parent=frame,x=1,y=1}
|
||||||
|
|
||||||
|
TextBox{parent=load_div,y=12,text="Loading...",alignment=ALIGN.CENTER}
|
||||||
|
WaitingAnim{parent=load_div,x=math.floor(main.get_width()/2)-1,y=8,fg_bg=cpair(colors.orange,colors._INHERIT)}
|
||||||
|
|
||||||
|
local load_pane = MultiPane{parent=main,x=1,y=1,panes={load_div,main}}
|
||||||
|
|
||||||
|
app.set_sidebar({ { label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = db.nav.go_home } })
|
||||||
|
|
||||||
|
local page_div = nil ---@type Div|nil
|
||||||
|
|
||||||
|
-- load the app (create the elements)
|
||||||
|
local function load()
|
||||||
|
local ps = db.ps
|
||||||
|
|
||||||
|
page_div = Div{parent=main,y=2,width=main.get_width()}
|
||||||
|
|
||||||
|
local panes = {} ---@type Div[]
|
||||||
|
|
||||||
|
-- create all page divs
|
||||||
|
for _ = 1, 4 do
|
||||||
|
local div = Div{parent=page_div}
|
||||||
|
table.insert(panes, div)
|
||||||
|
end
|
||||||
|
|
||||||
|
local last_update = 0
|
||||||
|
-- refresh data callback, every 1s it will re-send the query
|
||||||
|
local function update()
|
||||||
|
if util.time_ms() - last_update >= 1000 then
|
||||||
|
db.diag.get_comps()
|
||||||
|
last_update = util.time_ms()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- create indicators for the ID, firmware, and RTT
|
||||||
|
---@param pfx string
|
||||||
|
---@param rect Rectangle
|
||||||
|
local function create_common_indicators(pfx, rect)
|
||||||
|
local first = TextBox{parent=rect,text="Computer",fg_bg=box_label}
|
||||||
|
TextBox{parent=rect,text="Firmware",fg_bg=box_label}
|
||||||
|
TextBox{parent=rect,text="RTT (ms)",fg_bg=box_label}
|
||||||
|
|
||||||
|
local y = first.get_y()
|
||||||
|
local addr = TextBox{parent=rect,x=10,y=y,text="---"}
|
||||||
|
local fw = TextBox{parent=rect,x=10,y=y+1,text="---"}
|
||||||
|
local rtt = TextBox{parent=rect,x=10,y=y+2,text="---"}
|
||||||
|
|
||||||
|
addr.register(ps, pfx .. "_addr", function (v) addr.set_value(util.strval(v)) end)
|
||||||
|
fw.register(ps, pfx .. "_fw", function (v) fw.set_value(util.strval(v)) end)
|
||||||
|
|
||||||
|
rtt.register(ps, pfx .. "_rtt", function (value)
|
||||||
|
rtt.set_value(util.strval(value))
|
||||||
|
|
||||||
|
if value == "---" then
|
||||||
|
rtt.recolor(colors.white)
|
||||||
|
elseif value > const.HIGH_RTT then
|
||||||
|
rtt.recolor(colors.red)
|
||||||
|
elseif value > const.WARN_RTT then
|
||||||
|
rtt.recolor(colors.yellow)
|
||||||
|
else
|
||||||
|
rtt.recolor(colors.green)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
--#region main computer page
|
||||||
|
|
||||||
|
local m_div = Div{parent=panes[1],x=2,width=main.get_width()-2}
|
||||||
|
|
||||||
|
local main_page = app.new_page(nil, 1)
|
||||||
|
main_page.tasks = { update }
|
||||||
|
|
||||||
|
TextBox{parent=m_div,y=1,text="Connected Computers",alignment=ALIGN.CENTER}
|
||||||
|
|
||||||
|
local conns = DataIndicator{parent=m_div,y=3,lu_colors=lu_col,label="Total Online",unit="",format="%8d",value=0,commas=true,width=21}
|
||||||
|
conns.register(ps, "comp_online", conns.update)
|
||||||
|
|
||||||
|
local svr_div = Div{parent=m_div,y=4,height=6}
|
||||||
|
local svr_rect = Rectangle{parent=svr_div,height=6,width=22,border=border(1,colors.white,true),thin=true,fg_bg=cpair(colors.white,colors.gray)}
|
||||||
|
|
||||||
|
TextBox{parent=svr_rect,text="Supervisor"}
|
||||||
|
TextBox{parent=svr_rect,text="Status",fg_bg=box_label}
|
||||||
|
TextBox{parent=svr_rect,x=10,y=2,text="Online",fg_bg=cpair(colors.green,colors._INHERIT)}
|
||||||
|
TextBox{parent=svr_rect,text="Computer",fg_bg=box_label}
|
||||||
|
TextBox{parent=svr_rect,text="Firmware",fg_bg=box_label}
|
||||||
|
local svr_addr = TextBox{parent=svr_rect,x=10,y=3,text="?"}
|
||||||
|
local svr_fw = TextBox{parent=svr_rect,x=10,y=4,text="?"}
|
||||||
|
|
||||||
|
svr_addr.register(ps, "comp_svr_addr", function (v) svr_addr.set_value(util.strval(v)) end)
|
||||||
|
svr_fw.register(ps, "comp_svr_fw", function (v) svr_fw.set_value(util.strval(v)) end)
|
||||||
|
|
||||||
|
local crd_div = Div{parent=m_div,y=11,height=7}
|
||||||
|
local crd_rect = Rectangle{parent=crd_div,height=7,width=21,border=border(1,colors.white,true),thin=true,fg_bg=cpair(colors.white,colors.gray)}
|
||||||
|
|
||||||
|
TextBox{parent=crd_rect,text="Coordinator"}
|
||||||
|
TextBox{parent=crd_rect,text="Status",fg_bg=box_label}
|
||||||
|
local crd_online = TextBox{parent=crd_rect,x=10,y=2,width=8,text="Off-line",fg_bg=cpair(colors.red,colors._INHERIT)}
|
||||||
|
|
||||||
|
create_common_indicators("comp_crd", crd_rect)
|
||||||
|
|
||||||
|
crd_online.register(ps, "comp_crd_online", function (online)
|
||||||
|
if online then
|
||||||
|
crd_online.recolor(colors.green)
|
||||||
|
crd_online.set_value("Online")
|
||||||
|
else
|
||||||
|
crd_online.recolor(colors.red)
|
||||||
|
crd_online.set_value("Off-line")
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
|
--#region PLC page
|
||||||
|
|
||||||
|
local p_div = Div{parent=panes[2],width=main.get_width()}
|
||||||
|
|
||||||
|
local plc_page = app.new_page(nil, 2)
|
||||||
|
plc_page.tasks = { update }
|
||||||
|
|
||||||
|
TextBox{parent=p_div,y=1,text="PLC Devices",alignment=ALIGN.CENTER}
|
||||||
|
|
||||||
|
local plc_list = ListBox{parent=p_div,y=3,scroll_height=100,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)}
|
||||||
|
local plc_elems = {} ---@type graphics_element[]
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
|
--#region RTU gateway page
|
||||||
|
|
||||||
|
local r_div = Div{parent=panes[3],width=main.get_width()}
|
||||||
|
|
||||||
|
local rtu_page = app.new_page(nil, 3)
|
||||||
|
rtu_page.tasks = { update }
|
||||||
|
|
||||||
|
TextBox{parent=r_div,y=1,text="RTU Gateway Devices",alignment=ALIGN.CENTER}
|
||||||
|
|
||||||
|
local rtu_list = ListBox{parent=r_div,y=3,scroll_height=100,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)}
|
||||||
|
local rtu_elems = {} ---@type graphics_element[]
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
|
--#region pocket computer page
|
||||||
|
|
||||||
|
local pk_div = Div{parent=panes[4],width=main.get_width()}
|
||||||
|
|
||||||
|
local pkt_page = app.new_page(nil, 4)
|
||||||
|
pkt_page.tasks = { update }
|
||||||
|
|
||||||
|
TextBox{parent=pk_div,y=1,text="Pocket Devices",alignment=ALIGN.CENTER}
|
||||||
|
|
||||||
|
local pkt_list = ListBox{parent=pk_div,y=3,scroll_height=100,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)}
|
||||||
|
local pkt_elems = {} ---@type graphics_element[]
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
|
--#region connect/disconnect management
|
||||||
|
|
||||||
|
ps.subscribe("comp_connect", function (id)
|
||||||
|
if id == false then return end
|
||||||
|
|
||||||
|
local pfx = "comp_" .. id
|
||||||
|
local type = ps.get(pfx .. "_type")
|
||||||
|
|
||||||
|
if type == DEV_TYPE.PLC then
|
||||||
|
plc_elems[id] = Div{parent=plc_list,height=7}
|
||||||
|
local rect = Rectangle{parent=plc_elems[id],height=6,x=2,width=20,border=border(1,colors.white,true),thin=true,fg_bg=cpair(colors.white,colors.gray)}
|
||||||
|
|
||||||
|
local title = TextBox{parent=rect,text="PLC (Unit ?)"}
|
||||||
|
title.register(ps, pfx .. "_unit", function (unit) title.set_value("PLC (Unit " .. unit .. ")") end)
|
||||||
|
|
||||||
|
create_common_indicators(pfx, rect)
|
||||||
|
elseif type == DEV_TYPE.RTU then
|
||||||
|
rtu_elems[id] = Div{parent=rtu_list,height=7}
|
||||||
|
local rect = Rectangle{parent=rtu_elems[id],height=6,x=2,width=20,border=border(1,colors.white,true),thin=true,fg_bg=cpair(colors.white,colors.gray)}
|
||||||
|
|
||||||
|
TextBox{parent=rect,text="RTU Gateway"}
|
||||||
|
|
||||||
|
create_common_indicators(pfx, rect)
|
||||||
|
elseif type == DEV_TYPE.PKT then
|
||||||
|
pkt_elems[id] = Div{parent=pkt_list,height=7}
|
||||||
|
local rect = Rectangle{parent=pkt_elems[id],height=6,x=2,width=20,border=border(1,colors.white,true),thin=true,fg_bg=cpair(colors.white,colors.gray)}
|
||||||
|
|
||||||
|
TextBox{parent=rect,text="Pocket Computer"}
|
||||||
|
|
||||||
|
create_common_indicators(pfx, rect)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
ps.subscribe("comp_disconnect", function (id)
|
||||||
|
if id == false then return end
|
||||||
|
|
||||||
|
local type = ps.get("comp_" ..id .. "_type")
|
||||||
|
|
||||||
|
if type == DEV_TYPE.PLC then
|
||||||
|
if plc_elems[id] then plc_elems[id].delete() end
|
||||||
|
plc_elems[id] = nil
|
||||||
|
elseif type == DEV_TYPE.RTU then
|
||||||
|
if rtu_elems[id] then rtu_elems[id].delete() end
|
||||||
|
rtu_elems[id] = nil
|
||||||
|
elseif type == DEV_TYPE.PKT then
|
||||||
|
if pkt_elems[id] then pkt_elems[id].delete() end
|
||||||
|
pkt_elems[id] = nil
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
|
-- setup multipane
|
||||||
|
local u_pane = MultiPane{parent=page_div,x=1,y=1,panes=panes}
|
||||||
|
app.set_root_pane(u_pane)
|
||||||
|
|
||||||
|
-- setup sidebar
|
||||||
|
|
||||||
|
local list = {
|
||||||
|
{ label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = db.nav.go_home },
|
||||||
|
{ label = " @ ", color = core.cpair(colors.black, colors.blue), callback = main_page.nav_to },
|
||||||
|
{ label = "PLC", color = core.cpair(colors.black, colors.red), callback = plc_page.nav_to },
|
||||||
|
{ label = "RTU", color = core.cpair(colors.black, colors.orange), callback = rtu_page.nav_to },
|
||||||
|
{ label = "PKT", color = core.cpair(colors.black, colors.lightGray), callback = pkt_page.nav_to }
|
||||||
|
}
|
||||||
|
|
||||||
|
app.set_sidebar(list)
|
||||||
|
|
||||||
|
-- done, show the app
|
||||||
|
main_page.nav_to()
|
||||||
|
load_pane.set_value(2)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- delete the elements and switch back to the loading screen
|
||||||
|
local function unload()
|
||||||
|
if page_div then
|
||||||
|
page_div.delete()
|
||||||
|
page_div = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
app.set_sidebar({ { label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = db.nav.go_home } })
|
||||||
|
app.delete_pages()
|
||||||
|
|
||||||
|
-- show loading screen
|
||||||
|
load_pane.set_value(1)
|
||||||
|
|
||||||
|
-- clear the list of connected computers so that connections re-appear on reload of this app
|
||||||
|
iocontrol.rx.clear_comp_record()
|
||||||
|
end
|
||||||
|
|
||||||
|
app.set_load(load)
|
||||||
|
app.set_unload(unload)
|
||||||
|
|
||||||
|
return main
|
||||||
|
end
|
||||||
|
|
||||||
|
return new_view
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
--
|
|
||||||
-- Diagnostic Apps
|
|
||||||
--
|
|
||||||
|
|
||||||
local iocontrol = require("pocket.iocontrol")
|
|
||||||
local pocket = require("pocket.pocket")
|
|
||||||
|
|
||||||
local core = require("graphics.core")
|
|
||||||
|
|
||||||
local Div = require("graphics.elements.Div")
|
|
||||||
local TextBox = require("graphics.elements.TextBox")
|
|
||||||
|
|
||||||
local IndicatorLight = require("graphics.elements.indicators.IndicatorLight")
|
|
||||||
|
|
||||||
local Checkbox = require("graphics.elements.controls.Checkbox")
|
|
||||||
local PushButton = require("graphics.elements.controls.PushButton")
|
|
||||||
local SwitchButton = require("graphics.elements.controls.SwitchButton")
|
|
||||||
|
|
||||||
local ALIGN = core.ALIGN
|
|
||||||
local cpair = core.cpair
|
|
||||||
|
|
||||||
local APP_ID = pocket.APP_ID
|
|
||||||
|
|
||||||
-- create diagnostic app pages
|
|
||||||
---@param root Container parent
|
|
||||||
local function create_pages(root)
|
|
||||||
local db = iocontrol.get_db()
|
|
||||||
|
|
||||||
------------------------
|
|
||||||
-- Alarm Testing Page --
|
|
||||||
------------------------
|
|
||||||
|
|
||||||
local alarm_test = Div{parent=root,x=1,y=1}
|
|
||||||
|
|
||||||
local alarm_app = db.nav.register_app(APP_ID.ALARMS, alarm_test, nil, true)
|
|
||||||
|
|
||||||
local page = alarm_app.new_page(nil, function () end)
|
|
||||||
page.tasks = { db.diag.tone_test.get_tone_states }
|
|
||||||
|
|
||||||
local ttest = db.diag.tone_test
|
|
||||||
|
|
||||||
local c_wht_gray = cpair(colors.white, colors.gray)
|
|
||||||
local c_red_gray = cpair(colors.red, colors.gray)
|
|
||||||
local c_yel_gray = cpair(colors.yellow, colors.gray)
|
|
||||||
local c_blue_gray = cpair(colors.blue, colors.gray)
|
|
||||||
|
|
||||||
local audio = Div{parent=alarm_test,x=1,y=1}
|
|
||||||
|
|
||||||
TextBox{parent=audio,y=1,text="Alarm Sounder Tests",alignment=ALIGN.CENTER}
|
|
||||||
|
|
||||||
ttest.ready_warn = TextBox{parent=audio,y=2,text="",alignment=ALIGN.CENTER,fg_bg=cpair(colors.yellow,colors.black)}
|
|
||||||
|
|
||||||
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",alignment=ALIGN.CENTER,fg_bg=audio.get_fg_bg()}
|
|
||||||
|
|
||||||
local test_btns = {}
|
|
||||||
test_btns[1] = SwitchButton{parent=tones,text="TEST 1",min_width=8,active_fg_bg=c_wht_gray,callback=ttest.test_1}
|
|
||||||
test_btns[2] = SwitchButton{parent=tones,text="TEST 2",min_width=8,active_fg_bg=c_wht_gray,callback=ttest.test_2}
|
|
||||||
test_btns[3] = SwitchButton{parent=tones,text="TEST 3",min_width=8,active_fg_bg=c_wht_gray,callback=ttest.test_3}
|
|
||||||
test_btns[4] = SwitchButton{parent=tones,text="TEST 4",min_width=8,active_fg_bg=c_wht_gray,callback=ttest.test_4}
|
|
||||||
test_btns[5] = SwitchButton{parent=tones,text="TEST 5",min_width=8,active_fg_bg=c_wht_gray,callback=ttest.test_5}
|
|
||||||
test_btns[6] = SwitchButton{parent=tones,text="TEST 6",min_width=8,active_fg_bg=c_wht_gray,callback=ttest.test_6}
|
|
||||||
test_btns[7] = SwitchButton{parent=tones,text="TEST 7",min_width=8,active_fg_bg=c_wht_gray,callback=ttest.test_7}
|
|
||||||
test_btns[8] = SwitchButton{parent=tones,text="TEST 8",min_width=8,active_fg_bg=c_wht_gray,callback=ttest.test_8}
|
|
||||||
|
|
||||||
ttest.tone_buttons = test_btns
|
|
||||||
|
|
||||||
local function stop_all_tones()
|
|
||||||
for i = 1, #test_btns do test_btns[i].set_value(false) end
|
|
||||||
ttest.stop_tones()
|
|
||||||
end
|
|
||||||
|
|
||||||
PushButton{parent=tones,text="STOP",min_width=8,active_fg_bg=c_wht_gray,fg_bg=cpair(colors.black,colors.red),callback=stop_all_tones}
|
|
||||||
|
|
||||||
local alarms = Div{parent=audio,x=11,y=3,height=15,fg_bg=cpair(colors.lightGray,colors.black)}
|
|
||||||
|
|
||||||
TextBox{parent=alarms,text="Alarms (\x13)",alignment=ALIGN.CENTER,fg_bg=audio.get_fg_bg()}
|
|
||||||
|
|
||||||
local alarm_btns = {}
|
|
||||||
alarm_btns[1] = Checkbox{parent=alarms,label="BREACH",min_width=15,box_fg_bg=c_red_gray,callback=ttest.test_breach}
|
|
||||||
alarm_btns[2] = Checkbox{parent=alarms,label="RADIATION",min_width=15,box_fg_bg=c_red_gray,callback=ttest.test_rad}
|
|
||||||
alarm_btns[3] = Checkbox{parent=alarms,label="RCT LOST",min_width=15,box_fg_bg=c_red_gray,callback=ttest.test_lost}
|
|
||||||
alarm_btns[4] = Checkbox{parent=alarms,label="CRIT DAMAGE",min_width=15,box_fg_bg=c_red_gray,callback=ttest.test_crit}
|
|
||||||
alarm_btns[5] = Checkbox{parent=alarms,label="DAMAGE",min_width=15,box_fg_bg=c_red_gray,callback=ttest.test_dmg}
|
|
||||||
alarm_btns[6] = Checkbox{parent=alarms,label="OVER TEMP",min_width=15,box_fg_bg=c_red_gray,callback=ttest.test_overtemp}
|
|
||||||
alarm_btns[7] = Checkbox{parent=alarms,label="HIGH TEMP",min_width=15,box_fg_bg=c_yel_gray,callback=ttest.test_hightemp}
|
|
||||||
alarm_btns[8] = Checkbox{parent=alarms,label="WASTE LEAK",min_width=15,box_fg_bg=c_red_gray,callback=ttest.test_wasteleak}
|
|
||||||
alarm_btns[9] = Checkbox{parent=alarms,label="WASTE HIGH",min_width=15,box_fg_bg=c_yel_gray,callback=ttest.test_highwaste}
|
|
||||||
alarm_btns[10] = Checkbox{parent=alarms,label="RPS TRANS",min_width=15,box_fg_bg=c_yel_gray,callback=ttest.test_rps}
|
|
||||||
alarm_btns[11] = Checkbox{parent=alarms,label="RCS TRANS",min_width=15,box_fg_bg=c_yel_gray,callback=ttest.test_rcs}
|
|
||||||
alarm_btns[12] = Checkbox{parent=alarms,label="TURBINE TRP",min_width=15,box_fg_bg=c_red_gray,callback=ttest.test_turbinet}
|
|
||||||
|
|
||||||
ttest.alarm_buttons = alarm_btns
|
|
||||||
|
|
||||||
local function stop_all_alarms()
|
|
||||||
for i = 1, #alarm_btns do alarm_btns[i].set_value(false) end
|
|
||||||
ttest.stop_alarms()
|
|
||||||
end
|
|
||||||
|
|
||||||
PushButton{parent=alarms,x=3,y=15,text="STOP \x13",min_width=8,fg_bg=cpair(colors.black,colors.red),active_fg_bg=c_wht_gray,callback=stop_all_alarms}
|
|
||||||
|
|
||||||
local states = Div{parent=audio,x=2,y=14,height=5,width=8}
|
|
||||||
|
|
||||||
TextBox{parent=states,text="States",alignment=ALIGN.CENTER}
|
|
||||||
local t_1 = IndicatorLight{parent=states,label="1",colors=c_blue_gray}
|
|
||||||
local t_2 = IndicatorLight{parent=states,label="2",colors=c_blue_gray}
|
|
||||||
local t_3 = IndicatorLight{parent=states,label="3",colors=c_blue_gray}
|
|
||||||
local t_4 = IndicatorLight{parent=states,label="4",colors=c_blue_gray}
|
|
||||||
local t_5 = IndicatorLight{parent=states,x=6,y=2,label="5",colors=c_blue_gray}
|
|
||||||
local t_6 = IndicatorLight{parent=states,x=6,label="6",colors=c_blue_gray}
|
|
||||||
local t_7 = IndicatorLight{parent=states,x=6,label="7",colors=c_blue_gray}
|
|
||||||
local t_8 = IndicatorLight{parent=states,x=6,label="8",colors=c_blue_gray}
|
|
||||||
|
|
||||||
ttest.tone_indicators = { t_1, t_2, t_3, t_4, t_5, t_6, t_7, t_8 }
|
|
||||||
end
|
|
||||||
|
|
||||||
return create_pages
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
--
|
|
||||||
-- Placeholder App
|
|
||||||
--
|
|
||||||
|
|
||||||
local iocontrol = require("pocket.iocontrol")
|
|
||||||
local pocket = require("pocket.pocket")
|
|
||||||
|
|
||||||
local core = require("graphics.core")
|
|
||||||
|
|
||||||
local Div = require("graphics.elements.Div")
|
|
||||||
local TextBox = require("graphics.elements.TextBox")
|
|
||||||
|
|
||||||
local APP_ID = pocket.APP_ID
|
|
||||||
|
|
||||||
-- create placeholder app page
|
|
||||||
---@param root Container parent
|
|
||||||
local function create_pages(root)
|
|
||||||
local db = iocontrol.get_db()
|
|
||||||
|
|
||||||
local main = Div{parent=root,x=1,y=1}
|
|
||||||
|
|
||||||
db.nav.register_app(APP_ID.DUMMY, main).new_page(nil, function () end)
|
|
||||||
|
|
||||||
TextBox{parent=main,text="This app is not implemented yet.",x=1,y=2,alignment=core.ALIGN.CENTER}
|
|
||||||
|
|
||||||
TextBox{parent=main,text=" pretend something cool is here \x03",x=1,y=10,alignment=core.ALIGN.CENTER,fg_bg=core.cpair(colors.gray,colors.black)}
|
|
||||||
end
|
|
||||||
|
|
||||||
return create_pages
|
|
||||||
@@ -41,7 +41,7 @@ local grn_ind_s = style.icon_states.grn_ind_s
|
|||||||
-- new unit page view
|
-- new unit page view
|
||||||
---@param root Container parent
|
---@param root Container parent
|
||||||
local function new_view(root)
|
local function new_view(root)
|
||||||
local db = iocontrol.get_db()
|
local db = iocontrol.get_db()
|
||||||
|
|
||||||
local frame = Div{parent=root,x=1,y=1}
|
local frame = Div{parent=root,x=1,y=1}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ local iocontrol = require("pocket.iocontrol")
|
|||||||
local pocket = require("pocket.pocket")
|
local pocket = require("pocket.pocket")
|
||||||
|
|
||||||
local docs = require("pocket.ui.docs")
|
local docs = require("pocket.ui.docs")
|
||||||
-- local style = require("pocket.ui.style")
|
|
||||||
|
|
||||||
local guide_section = require("pocket.ui.pages.guide_section")
|
local guide_section = require("pocket.ui.pages.guide_section")
|
||||||
|
|
||||||
@@ -31,10 +30,6 @@ local cpair = core.cpair
|
|||||||
|
|
||||||
local APP_ID = pocket.APP_ID
|
local APP_ID = pocket.APP_ID
|
||||||
|
|
||||||
-- local label = style.label
|
|
||||||
-- local lu_col = style.label_unit_pair
|
|
||||||
-- local text_fg = style.text_fg
|
|
||||||
|
|
||||||
-- new system guide view
|
-- new system guide view
|
||||||
---@param root Container parent
|
---@param root Container parent
|
||||||
local function new_view(root)
|
local function new_view(root)
|
||||||
@@ -47,14 +42,21 @@ local function new_view(root)
|
|||||||
local load_div = Div{parent=frame,x=1,y=1}
|
local load_div = Div{parent=frame,x=1,y=1}
|
||||||
local main = Div{parent=frame,x=1,y=1}
|
local main = Div{parent=frame,x=1,y=1}
|
||||||
|
|
||||||
TextBox{parent=load_div,y=12,text="Loading...",alignment=ALIGN.CENTER}
|
|
||||||
WaitingAnim{parent=load_div,x=math.floor(main.get_width()/2)-1,y=8,fg_bg=cpair(colors.cyan,colors._INHERIT)}
|
WaitingAnim{parent=load_div,x=math.floor(main.get_width()/2)-1,y=8,fg_bg=cpair(colors.cyan,colors._INHERIT)}
|
||||||
|
TextBox{parent=load_div,y=12,text="Loading...",alignment=ALIGN.CENTER}
|
||||||
|
local load_text_1 = TextBox{parent=load_div,y=14,text="",alignment=ALIGN.CENTER,fg_bg=cpair(colors.lightGray,colors._INHERIT)}
|
||||||
|
local load_text_2 = TextBox{parent=load_div,y=15,text="",alignment=ALIGN.CENTER,fg_bg=cpair(colors.lightGray,colors._INHERIT)}
|
||||||
|
|
||||||
|
-- give more detailed information so the user doesn't give up
|
||||||
|
local function load_text(a, b)
|
||||||
|
if a then load_text_1.set_value(a) end
|
||||||
|
load_text_2.set_value(b or "")
|
||||||
|
end
|
||||||
|
|
||||||
local load_pane = MultiPane{parent=main,x=1,y=1,panes={load_div,main}}
|
local load_pane = MultiPane{parent=main,x=1,y=1,panes={load_div,main}}
|
||||||
|
|
||||||
local btn_fg_bg = cpair(colors.cyan, colors.black)
|
local btn_fg_bg = cpair(colors.cyan, colors.black)
|
||||||
local btn_active = cpair(colors.white, colors.black)
|
local btn_active = cpair(colors.white, colors.black)
|
||||||
local btn_disable = cpair(colors.gray, colors.black)
|
|
||||||
|
|
||||||
app.set_sidebar({{ label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = db.nav.go_home }})
|
app.set_sidebar({{ label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = db.nav.go_home }})
|
||||||
|
|
||||||
@@ -71,7 +73,7 @@ local function new_view(root)
|
|||||||
app.set_sidebar(list)
|
app.set_sidebar(list)
|
||||||
|
|
||||||
page_div = Div{parent=main,y=2}
|
page_div = Div{parent=main,y=2}
|
||||||
local p_width = page_div.get_width() - 2
|
local p_width = page_div.get_width() - 1
|
||||||
|
|
||||||
local main_page = app.new_page(nil, 1)
|
local main_page = app.new_page(nil, 1)
|
||||||
local search_page = app.new_page(main_page, 2)
|
local search_page = app.new_page(main_page, 2)
|
||||||
@@ -104,6 +106,8 @@ local function new_view(root)
|
|||||||
PushButton{parent=home,text="Glossary >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=gls_page.nav_to}
|
PushButton{parent=home,text="Glossary >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=gls_page.nav_to}
|
||||||
PushButton{parent=home,y=10,text="Wiki and Discord >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=lnk_page.nav_to}
|
PushButton{parent=home,y=10,text="Wiki and Discord >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=lnk_page.nav_to}
|
||||||
|
|
||||||
|
load_text("Search")
|
||||||
|
|
||||||
TextBox{parent=search,y=1,text="Search",alignment=ALIGN.CENTER}
|
TextBox{parent=search,y=1,text="Search",alignment=ALIGN.CENTER}
|
||||||
|
|
||||||
local query_field = TextField{parent=search,x=1,y=3,width=18,fg_bg=cpair(colors.white,colors.gray)}
|
local query_field = TextField{parent=search,x=1,y=3,width=18,fg_bg=cpair(colors.white,colors.gray)}
|
||||||
@@ -171,14 +175,29 @@ local function new_view(root)
|
|||||||
|
|
||||||
util.nop()
|
util.nop()
|
||||||
|
|
||||||
|
load_text("System Usage")
|
||||||
|
|
||||||
TextBox{parent=use,y=1,text="System Usage",alignment=ALIGN.CENTER}
|
TextBox{parent=use,y=1,text="System Usage",alignment=ALIGN.CENTER}
|
||||||
PushButton{parent=use,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=main_page.nav_to}
|
PushButton{parent=use,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=main_page.nav_to}
|
||||||
|
|
||||||
PushButton{parent=use,y=3,text="Configuring Devices >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,dis_fg_bg=btn_disable,callback=function()end}.disable()
|
load_text(false, "Connecting Devices")
|
||||||
PushButton{parent=use,text="Connecting Devices >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,dis_fg_bg=btn_disable,callback=function()end}.disable()
|
local conn_dev_page = guide_section(sect_construct_data, use_page, "Connecting Devs", docs.usage.conn, 110)
|
||||||
PushButton{parent=use,text="Manual Control >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,dis_fg_bg=btn_disable,callback=function()end}.disable()
|
load_text(false, "Configuring Devices")
|
||||||
PushButton{parent=use,text="Automatic Control >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,dis_fg_bg=btn_disable,callback=function()end}.disable()
|
local config_dev_page = guide_section(sect_construct_data, use_page, "Configuring Devs", docs.usage.config, 350)
|
||||||
PushButton{parent=use,text="Waste Control >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,dis_fg_bg=btn_disable,callback=function()end}.disable()
|
load_text(false, "Manual Control")
|
||||||
|
local man_ctrl_page = guide_section(sect_construct_data, use_page, "Manual Control", docs.usage.manual, 100)
|
||||||
|
load_text(false, "Auto Control")
|
||||||
|
local auto_ctrl_page = guide_section(sect_construct_data, use_page, "Auto Control", docs.usage.auto, 200)
|
||||||
|
load_text(false, "Waste Control")
|
||||||
|
local waste_ctrl_page = guide_section(sect_construct_data, use_page, "Waste Control", docs.usage.waste, 120)
|
||||||
|
|
||||||
|
PushButton{parent=use,y=3,text="Connecting Devices >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=conn_dev_page.nav_to}
|
||||||
|
PushButton{parent=use,text="Configuring Devices >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=config_dev_page.nav_to}
|
||||||
|
PushButton{parent=use,text="Manual Control >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=man_ctrl_page.nav_to}
|
||||||
|
PushButton{parent=use,text="Automatic Control >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=auto_ctrl_page.nav_to}
|
||||||
|
PushButton{parent=use,text="Waste Control >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=waste_ctrl_page.nav_to}
|
||||||
|
|
||||||
|
load_text("Operator UIs")
|
||||||
|
|
||||||
TextBox{parent=uis,y=1,text="Operator UIs",alignment=ALIGN.CENTER}
|
TextBox{parent=uis,y=1,text="Operator UIs",alignment=ALIGN.CENTER}
|
||||||
PushButton{parent=uis,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=main_page.nav_to}
|
PushButton{parent=uis,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=main_page.nav_to}
|
||||||
@@ -187,51 +206,84 @@ local function new_view(root)
|
|||||||
local annunc_div = Div{parent=page_div,x=2}
|
local annunc_div = Div{parent=page_div,x=2}
|
||||||
table.insert(panes, annunc_div)
|
table.insert(panes, annunc_div)
|
||||||
|
|
||||||
|
local coord_page = app.new_page(uis_page, #panes + 1)
|
||||||
|
local coord_div = Div{parent=page_div,x=2}
|
||||||
|
table.insert(panes, coord_div)
|
||||||
|
|
||||||
|
load_text(false, "Alarms")
|
||||||
|
|
||||||
local alarms_page = guide_section(sect_construct_data, uis_page, "Alarms", docs.alarms, 100)
|
local alarms_page = guide_section(sect_construct_data, uis_page, "Alarms", docs.alarms, 100)
|
||||||
|
|
||||||
PushButton{parent=uis,y=3,text="Alarms >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=alarms_page.nav_to}
|
PushButton{parent=uis,y=3,text="Alarms >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=alarms_page.nav_to}
|
||||||
PushButton{parent=uis,text="Annunciators >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=annunc_page.nav_to}
|
PushButton{parent=uis,text="Annunciators >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=annunc_page.nav_to}
|
||||||
PushButton{parent=uis,text="Pocket UI >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,dis_fg_bg=btn_disable,callback=function()end}.disable()
|
PushButton{parent=uis,text="Coordinator UI >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=coord_page.nav_to}
|
||||||
PushButton{parent=uis,text="Coordinator UI >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,dis_fg_bg=btn_disable,callback=function()end}.disable()
|
|
||||||
|
load_text(false, "Annunciators")
|
||||||
|
|
||||||
TextBox{parent=annunc_div,y=1,text="Annunciators",alignment=ALIGN.CENTER}
|
TextBox{parent=annunc_div,y=1,text="Annunciators",alignment=ALIGN.CENTER}
|
||||||
PushButton{parent=annunc_div,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=uis_page.nav_to}
|
PushButton{parent=annunc_div,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=uis_page.nav_to}
|
||||||
|
|
||||||
|
local fac_annunc_page = guide_section(sect_construct_data, annunc_page, "Facility", docs.annunc.facility.main_section, 110)
|
||||||
local unit_gen_page = guide_section(sect_construct_data, annunc_page, "Unit General", docs.annunc.unit.main_section, 170)
|
local unit_gen_page = guide_section(sect_construct_data, annunc_page, "Unit General", docs.annunc.unit.main_section, 170)
|
||||||
local unit_rps_page = guide_section(sect_construct_data, annunc_page, "Unit RPS", docs.annunc.unit.rps_section, 100)
|
local unit_rps_page = guide_section(sect_construct_data, annunc_page, "Unit RPS", docs.annunc.unit.rps_section, 100)
|
||||||
local unit_rcs_page = guide_section(sect_construct_data, annunc_page, "Unit RCS", docs.annunc.unit.rcs_section, 170)
|
local unit_rcs_page = guide_section(sect_construct_data, annunc_page, "Unit RCS", docs.annunc.unit.rcs_section, 170)
|
||||||
|
|
||||||
local fac_annunc_page = guide_section(sect_construct_data, annunc_page, "Facility", docs.annunc.facility.main_section, 110)
|
PushButton{parent=annunc_div,y=3,text="Facility General >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=fac_annunc_page.nav_to}
|
||||||
|
PushButton{parent=annunc_div,text="Unit General >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=unit_gen_page.nav_to}
|
||||||
PushButton{parent=annunc_div,y=3,text="Unit General >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=unit_gen_page.nav_to}
|
|
||||||
PushButton{parent=annunc_div,text="Unit RPS >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=unit_rps_page.nav_to}
|
PushButton{parent=annunc_div,text="Unit RPS >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=unit_rps_page.nav_to}
|
||||||
PushButton{parent=annunc_div,text="Unit RCS >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=unit_rcs_page.nav_to}
|
PushButton{parent=annunc_div,text="Unit RCS >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=unit_rcs_page.nav_to}
|
||||||
PushButton{parent=annunc_div,text="Facility General >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=fac_annunc_page.nav_to}
|
|
||||||
PushButton{parent=annunc_div,text="Waste & Valves >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,dis_fg_bg=btn_disable,callback=function()end}.disable()
|
load_text(false, "Coordinator UI")
|
||||||
|
|
||||||
|
TextBox{parent=coord_div,y=1,text="Coordinator UI",alignment=ALIGN.CENTER}
|
||||||
|
PushButton{parent=coord_div,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=uis_page.nav_to}
|
||||||
|
|
||||||
|
load_text(false, "Main Display")
|
||||||
|
local main_disp_page = guide_section(sect_construct_data, coord_page, "Main Display", docs.c_ui.main, 300)
|
||||||
|
load_text(false, "Flow Display")
|
||||||
|
local flow_disp_page = guide_section(sect_construct_data, coord_page, "Flow Display", docs.c_ui.flow, 210)
|
||||||
|
load_text(false, "Unit Displays")
|
||||||
|
local unit_disp_page = guide_section(sect_construct_data, coord_page, "Unit Displays", docs.c_ui.unit, 150)
|
||||||
|
|
||||||
|
PushButton{parent=coord_div,y=3,text="Main Display >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=main_disp_page.nav_to}
|
||||||
|
PushButton{parent=coord_div,text="Flow Display >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=flow_disp_page.nav_to}
|
||||||
|
PushButton{parent=coord_div,text="Unit Displays >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=unit_disp_page.nav_to}
|
||||||
|
|
||||||
|
load_text("Front Panels")
|
||||||
|
|
||||||
TextBox{parent=fps,y=1,text="Front Panels",alignment=ALIGN.CENTER}
|
TextBox{parent=fps,y=1,text="Front Panels",alignment=ALIGN.CENTER}
|
||||||
PushButton{parent=fps,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=main_page.nav_to}
|
PushButton{parent=fps,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=main_page.nav_to}
|
||||||
|
|
||||||
|
load_text(false, "Common Items")
|
||||||
local fp_common_page = guide_section(sect_construct_data, fps_page, "Common Items", docs.fp.common, 100)
|
local fp_common_page = guide_section(sect_construct_data, fps_page, "Common Items", docs.fp.common, 100)
|
||||||
local fp_rplc_page = guide_section(sect_construct_data, fps_page, "Reactor PLC", docs.fp.r_plc, 180)
|
load_text(false, "Reactor PLC")
|
||||||
|
local fp_rplc_page = guide_section(sect_construct_data, fps_page, "Reactor PLC", docs.fp.r_plc, 190)
|
||||||
|
load_text(false, "RTU Gateway")
|
||||||
local fp_rtu_page = guide_section(sect_construct_data, fps_page, "RTU Gateway", docs.fp.rtu_gw, 100)
|
local fp_rtu_page = guide_section(sect_construct_data, fps_page, "RTU Gateway", docs.fp.rtu_gw, 100)
|
||||||
|
load_text(false, "Supervisor")
|
||||||
local fp_supervisor_page = guide_section(sect_construct_data, fps_page, "Supervisor", docs.fp.supervisor, 160)
|
local fp_supervisor_page = guide_section(sect_construct_data, fps_page, "Supervisor", docs.fp.supervisor, 160)
|
||||||
|
load_text(false, "Coordinator")
|
||||||
|
local fp_coordinator_page = guide_section(sect_construct_data, fps_page, "Coordinator", docs.fp.coordinator, 80)
|
||||||
|
|
||||||
PushButton{parent=fps,y=3,text="Common Items >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=fp_common_page.nav_to}
|
PushButton{parent=fps,y=3,text="Common Items >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=fp_common_page.nav_to}
|
||||||
PushButton{parent=fps,text="Reactor PLC >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=fp_rplc_page.nav_to}
|
PushButton{parent=fps,text="Reactor PLC >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=fp_rplc_page.nav_to}
|
||||||
PushButton{parent=fps,text="RTU Gateway >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=fp_rtu_page.nav_to}
|
PushButton{parent=fps,text="RTU Gateway >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=fp_rtu_page.nav_to}
|
||||||
PushButton{parent=fps,text="Supervisor >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=fp_supervisor_page.nav_to}
|
PushButton{parent=fps,text="Supervisor >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=fp_supervisor_page.nav_to}
|
||||||
PushButton{parent=fps,text="Coordinator >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,dis_fg_bg=btn_disable,callback=function()end}.disable()
|
PushButton{parent=fps,text="Coordinator >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=fp_coordinator_page.nav_to}
|
||||||
|
|
||||||
|
load_text("Glossary")
|
||||||
|
|
||||||
TextBox{parent=gls,y=1,text="Glossary",alignment=ALIGN.CENTER}
|
TextBox{parent=gls,y=1,text="Glossary",alignment=ALIGN.CENTER}
|
||||||
PushButton{parent=gls,x=3,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=main_page.nav_to}
|
PushButton{parent=gls,x=3,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=main_page.nav_to}
|
||||||
|
|
||||||
local gls_abbv_page = guide_section(sect_construct_data, gls_page, "Abbreviations", docs.glossary.abbvs, 140)
|
local gls_abbv_page = guide_section(sect_construct_data, gls_page, "Abbreviations", docs.glossary.abbvs, 140)
|
||||||
local gls_term_page = guide_section(sect_construct_data, gls_page, "Terminology", docs.glossary.terms, 100)
|
local gls_term_page = guide_section(sect_construct_data, gls_page, "Terminology", docs.glossary.terms, 120)
|
||||||
|
|
||||||
PushButton{parent=gls,y=3,text="Abbreviations >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=gls_abbv_page.nav_to}
|
PushButton{parent=gls,y=3,text="Abbreviations >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=gls_abbv_page.nav_to}
|
||||||
PushButton{parent=gls,text="Terminology >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=gls_term_page.nav_to}
|
PushButton{parent=gls,text="Terminology >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=gls_term_page.nav_to}
|
||||||
|
|
||||||
|
load_text("Links")
|
||||||
|
|
||||||
TextBox{parent=lnk,y=1,text="Wiki and Discord",alignment=ALIGN.CENTER}
|
TextBox{parent=lnk,y=1,text="Wiki and Discord",alignment=ALIGN.CENTER}
|
||||||
PushButton{parent=lnk,x=1,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=main_page.nav_to}
|
PushButton{parent=lnk,x=1,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=main_page.nav_to}
|
||||||
|
|
||||||
|
|||||||
@@ -32,16 +32,29 @@ local function create_pages(root)
|
|||||||
|
|
||||||
local root_pane = MultiPane{parent=main,x=1,y=1,panes={conn_sv_wait,conn_api_wait,main_pane}}
|
local root_pane = MultiPane{parent=main,x=1,y=1,panes={conn_sv_wait,conn_api_wait,main_pane}}
|
||||||
|
|
||||||
root_pane.register(db.ps, "link_state", function (state)
|
local function update()
|
||||||
if state == LINK_STATE.UNLINKED or state == LINK_STATE.API_LINK_ONLY then
|
local state = db.ps.get("link_state")
|
||||||
|
|
||||||
|
if state == LINK_STATE.UNLINKED then
|
||||||
root_pane.set_value(1)
|
root_pane.set_value(1)
|
||||||
|
elseif state == LINK_STATE.API_LINK_ONLY then
|
||||||
|
if not db.loader_require.sv then
|
||||||
|
root_pane.set_value(3)
|
||||||
|
db.nav.on_loader_connected()
|
||||||
|
else root_pane.set_value(1) end
|
||||||
elseif state == LINK_STATE.SV_LINK_ONLY then
|
elseif state == LINK_STATE.SV_LINK_ONLY then
|
||||||
root_pane.set_value(2)
|
if not db.loader_require.api then
|
||||||
|
root_pane.set_value(3)
|
||||||
|
db.nav.on_loader_connected()
|
||||||
|
else root_pane.set_value(2) end
|
||||||
else
|
else
|
||||||
root_pane.set_value(3)
|
root_pane.set_value(3)
|
||||||
db.nav.on_loader_connected()
|
db.nav.on_loader_connected()
|
||||||
end
|
end
|
||||||
end)
|
end
|
||||||
|
|
||||||
|
root_pane.register(db.ps, "link_state", update)
|
||||||
|
root_pane.register(db.ps, "loader_reqs", update)
|
||||||
|
|
||||||
TextBox{parent=main_pane,text="Connected!",x=1,y=6,alignment=core.ALIGN.CENTER}
|
TextBox{parent=main_pane,text="Connected!",x=1,y=6,alignment=core.ALIGN.CENTER}
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ local function new_view(root)
|
|||||||
TextBox{parent=o_div,y=1,text="Process Options",alignment=ALIGN.CENTER}
|
TextBox{parent=o_div,y=1,text="Process Options",alignment=ALIGN.CENTER}
|
||||||
|
|
||||||
local ctl_opts = { "Monitored Max Burn", "Combined Burn Rate", "Charge Level", "Generation Rate" }
|
local ctl_opts = { "Monitored Max Burn", "Combined Burn Rate", "Charge Level", "Generation Rate" }
|
||||||
local mode = RadioButton{parent=o_div,x=1,y=3,options=ctl_opts,callback=function()end,radio_colors=cpair(colors.lightGray,colors.gray),select_color=colors.purple,dis_fg_bg=style.btn_disable}
|
local mode = RadioButton{parent=o_div,x=1,y=3,options=ctl_opts,radio_colors=cpair(colors.lightGray,colors.gray),select_color=colors.purple,dis_fg_bg=style.btn_disable}
|
||||||
|
|
||||||
mode.register(f_ps, "process_mode", mode.set_value)
|
mode.register(f_ps, "process_mode", mode.set_value)
|
||||||
|
|
||||||
@@ -194,7 +194,7 @@ local function new_view(root)
|
|||||||
|
|
||||||
TextBox{parent=c_div,y=1,text="Process Control",alignment=ALIGN.CENTER}
|
TextBox{parent=c_div,y=1,text="Process Control",alignment=ALIGN.CENTER}
|
||||||
|
|
||||||
local u_stat = Rectangle{parent=c_div,border=border(1,colors.gray,true),thin=true,width=21,height=5,x=1,y=3,fg_bg=cpair(colors.black,colors.lightGray)}
|
local u_stat = Rectangle{parent=c_div,border=border(1,colors.gray,true),thin=true,width=21,height=5,x=1,y=3,fg_bg=cpair(colors.black,colors.lightGray)}
|
||||||
local stat_line_1 = TextBox{parent=u_stat,x=1,y=1,text="UNKNOWN",alignment=ALIGN.CENTER}
|
local stat_line_1 = TextBox{parent=u_stat,x=1,y=1,text="UNKNOWN",alignment=ALIGN.CENTER}
|
||||||
local stat_line_2 = TextBox{parent=u_stat,x=1,y=2,text="awaiting data...",height=2,alignment=ALIGN.CENTER,trim_whitespace=true,fg_bg=cpair(colors.gray,colors.lightGray)}
|
local stat_line_2 = TextBox{parent=u_stat,x=1,y=2,text="awaiting data...",height=2,alignment=ALIGN.CENTER,trim_whitespace=true,fg_bg=cpair(colors.gray,colors.lightGray)}
|
||||||
|
|
||||||
@@ -210,7 +210,7 @@ local function new_view(root)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local start = HazardButton{parent=c_div,x=2,y=9,text="START",accent=colors.lightBlue,callback=_start_auto,timeout=3,fg_bg=hzd_fg_bg,dis_colors=dis_colors}
|
local start = HazardButton{parent=c_div,x=2,y=9,text="START",accent=colors.lightBlue,callback=_start_auto,timeout=3,fg_bg=hzd_fg_bg,dis_colors=dis_colors}
|
||||||
local stop = HazardButton{parent=c_div,x=13,y=9,text="STOP",accent=colors.red,callback=process.process_stop,timeout=3,fg_bg=hzd_fg_bg,dis_colors=dis_colors}
|
local stop = HazardButton{parent=c_div,x=13,y=9,text="STOP",accent=colors.red,callback=process.process_stop,timeout=3,fg_bg=hzd_fg_bg,dis_colors=dis_colors}
|
||||||
|
|
||||||
db.facility.start_ack = start.on_response
|
db.facility.start_ack = start.on_response
|
||||||
db.facility.stop_ack = stop.on_response
|
db.facility.stop_ack = stop.on_response
|
||||||
|
|||||||
219
pocket/ui/apps/radiation.lua
Normal file
219
pocket/ui/apps/radiation.lua
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
--
|
||||||
|
-- Radiation Monitor App
|
||||||
|
--
|
||||||
|
|
||||||
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
|
local iocontrol = require("pocket.iocontrol")
|
||||||
|
local pocket = require("pocket.pocket")
|
||||||
|
|
||||||
|
local style = require("pocket.ui.style")
|
||||||
|
|
||||||
|
local core = require("graphics.core")
|
||||||
|
|
||||||
|
local Div = require("graphics.elements.Div")
|
||||||
|
local ListBox = require("graphics.elements.ListBox")
|
||||||
|
local MultiPane = require("graphics.elements.MultiPane")
|
||||||
|
local Rectangle = require("graphics.elements.Rectangle")
|
||||||
|
local TextBox = require("graphics.elements.TextBox")
|
||||||
|
|
||||||
|
local WaitingAnim = require("graphics.elements.animations.Waiting")
|
||||||
|
|
||||||
|
local RadIndicator = require("graphics.elements.indicators.RadIndicator")
|
||||||
|
|
||||||
|
local ALIGN = core.ALIGN
|
||||||
|
local cpair = core.cpair
|
||||||
|
local border = core.border
|
||||||
|
|
||||||
|
local APP_ID = pocket.APP_ID
|
||||||
|
|
||||||
|
local label_fg_bg = style.label
|
||||||
|
local lu_col = style.label_unit_pair
|
||||||
|
|
||||||
|
-- new radiation monitor page view
|
||||||
|
---@param root Container parent
|
||||||
|
local function new_view(root)
|
||||||
|
local db = iocontrol.get_db()
|
||||||
|
|
||||||
|
local frame = Div{parent=root,x=1,y=1}
|
||||||
|
|
||||||
|
local app = db.nav.register_app(APP_ID.RADMON, frame, nil, false, true)
|
||||||
|
|
||||||
|
local load_div = Div{parent=frame,x=1,y=1}
|
||||||
|
local main = Div{parent=frame,x=1,y=1}
|
||||||
|
|
||||||
|
TextBox{parent=load_div,y=12,text="Loading...",alignment=ALIGN.CENTER}
|
||||||
|
WaitingAnim{parent=load_div,x=math.floor(main.get_width()/2)-1,y=8,fg_bg=cpair(colors.yellow,colors._INHERIT)}
|
||||||
|
|
||||||
|
local load_pane = MultiPane{parent=main,x=1,y=1,panes={load_div,main}}
|
||||||
|
|
||||||
|
app.set_sidebar({ { label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = db.nav.go_home } })
|
||||||
|
|
||||||
|
local page_div = nil ---@type Div|nil
|
||||||
|
|
||||||
|
-- load the app (create the elements)
|
||||||
|
local function load()
|
||||||
|
local f_ps = db.facility.ps
|
||||||
|
|
||||||
|
page_div = Div{parent=main,y=2,width=main.get_width()}
|
||||||
|
|
||||||
|
local panes = {} ---@type Div[]
|
||||||
|
|
||||||
|
-- create all page divs
|
||||||
|
for _ = 1, db.facility.num_units + 2 do
|
||||||
|
local div = Div{parent=page_div}
|
||||||
|
table.insert(panes, div)
|
||||||
|
end
|
||||||
|
|
||||||
|
local last_update = 0
|
||||||
|
-- refresh data callback, every 500ms it will re-send the query
|
||||||
|
local function update()
|
||||||
|
if util.time_ms() - last_update >= 500 then
|
||||||
|
db.api.get_rad()
|
||||||
|
last_update = util.time_ms()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- create a new radiation monitor list
|
||||||
|
---@param parent Container
|
||||||
|
---@param ps psil
|
||||||
|
local function new_mon_list(parent, ps)
|
||||||
|
local mon_list = ListBox{parent=parent,y=6,scroll_height=100,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)}
|
||||||
|
|
||||||
|
local elem_list = {} ---@type graphics_element[]
|
||||||
|
|
||||||
|
mon_list.register(ps, "radiation_monitors", function (data)
|
||||||
|
local ids = textutils.unserialize(data)
|
||||||
|
|
||||||
|
-- delete any disconnected monitors
|
||||||
|
for id, elem in pairs(elem_list) do
|
||||||
|
if not util.table_contains(ids, id) then
|
||||||
|
elem.delete()
|
||||||
|
elem_list[id] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- add newly connected monitors
|
||||||
|
for _, id in pairs(ids) do
|
||||||
|
if not elem_list[id] then
|
||||||
|
elem_list[id] = Div{parent=mon_list,height=5}
|
||||||
|
local mon_rect = Rectangle{parent=elem_list[id],height=4,x=2,width=20,border=border(1,colors.gray,true),thin=true,fg_bg=cpair(colors.black,colors.lightGray)}
|
||||||
|
|
||||||
|
TextBox{parent=mon_rect,text="Env. Detector "..id}
|
||||||
|
local mon_rad = RadIndicator{parent=mon_rect,x=2,label="",format="%13.3f",lu_colors=cpair(colors.gray,colors.gray),width=18}
|
||||||
|
mon_rad.register(ps, "radiation@" .. id, mon_rad.update)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
--#region unit radiation monitors
|
||||||
|
|
||||||
|
for i = 1, db.facility.num_units do
|
||||||
|
local u_pane = panes[i]
|
||||||
|
local u_div = Div{parent=u_pane}
|
||||||
|
local unit = db.units[i]
|
||||||
|
local u_ps = unit.unit_ps
|
||||||
|
|
||||||
|
local u_page = app.new_page(nil, i)
|
||||||
|
u_page.tasks = { update }
|
||||||
|
|
||||||
|
TextBox{parent=u_div,y=1,text="Unit #"..i.." Monitors",alignment=ALIGN.CENTER}
|
||||||
|
|
||||||
|
TextBox{parent=u_div,x=2,y=3,text="Max Radiation",fg_bg=label_fg_bg}
|
||||||
|
local radiation = RadIndicator{parent=u_div,x=2,label="",format="%17.3f",lu_colors=lu_col,width=21}
|
||||||
|
radiation.register(u_ps, "radiation", radiation.update)
|
||||||
|
|
||||||
|
new_mon_list(u_div, u_ps)
|
||||||
|
end
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
|
--#region overview page
|
||||||
|
|
||||||
|
local s_pane = panes[db.facility.num_units + 1]
|
||||||
|
local s_div = Div{parent=s_pane,x=2,width=main.get_width()-2}
|
||||||
|
|
||||||
|
local stat_page = app.new_page(nil, db.facility.num_units + 1)
|
||||||
|
stat_page.tasks = { update }
|
||||||
|
|
||||||
|
TextBox{parent=s_div,y=1,text=" Radiation Monitoring",alignment=ALIGN.CENTER}
|
||||||
|
|
||||||
|
TextBox{parent=s_div,y=3,text="Max Facility Rad.",fg_bg=label_fg_bg}
|
||||||
|
local s_f_rad = RadIndicator{parent=s_div,label="",format="%17.3f",lu_colors=lu_col,width=21}
|
||||||
|
s_f_rad.register(f_ps, "radiation", s_f_rad.update)
|
||||||
|
|
||||||
|
for i = 1, db.facility.num_units do
|
||||||
|
local unit = db.units[i]
|
||||||
|
local u_ps = unit.unit_ps
|
||||||
|
|
||||||
|
s_div.line_break()
|
||||||
|
TextBox{parent=s_div,text="Max Unit "..i.." Radiation",fg_bg=label_fg_bg}
|
||||||
|
local s_u_rad = RadIndicator{parent=s_div,label="",format="%17.3f",lu_colors=lu_col,width=21}
|
||||||
|
s_u_rad.register(u_ps, "radiation", s_u_rad.update)
|
||||||
|
end
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
|
--#region overview page
|
||||||
|
|
||||||
|
local f_pane = panes[db.facility.num_units + 2]
|
||||||
|
local f_div = Div{parent=f_pane,width=main.get_width()}
|
||||||
|
|
||||||
|
local fac_page = app.new_page(nil, db.facility.num_units + 2)
|
||||||
|
fac_page.tasks = { update }
|
||||||
|
|
||||||
|
TextBox{parent=f_div,y=1,text="Facility Monitors",alignment=ALIGN.CENTER}
|
||||||
|
|
||||||
|
TextBox{parent=f_div,x=2,y=3,text="Max Radiation",fg_bg=label_fg_bg}
|
||||||
|
local f_rad = RadIndicator{parent=f_div,x=2,label="",format="%17.3f",lu_colors=lu_col,width=21}
|
||||||
|
f_rad.register(f_ps, "radiation", f_rad.update)
|
||||||
|
|
||||||
|
new_mon_list(f_div, f_ps)
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
|
-- setup multipane
|
||||||
|
local u_pane = MultiPane{parent=page_div,x=1,y=1,panes=panes}
|
||||||
|
app.set_root_pane(u_pane)
|
||||||
|
|
||||||
|
-- setup sidebar
|
||||||
|
|
||||||
|
local list = {
|
||||||
|
{ label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = db.nav.go_home },
|
||||||
|
{ label = " \x1e ", color = core.cpair(colors.black, colors.blue), callback = stat_page.nav_to },
|
||||||
|
{ label = "FAC", color = core.cpair(colors.black, colors.yellow), callback = fac_page.nav_to }
|
||||||
|
}
|
||||||
|
|
||||||
|
for i = 1, db.facility.num_units do
|
||||||
|
table.insert(list, { label = "U-" .. i, color = core.cpair(colors.black, colors.lightGray), callback = function () app.switcher(i) end })
|
||||||
|
end
|
||||||
|
|
||||||
|
app.set_sidebar(list)
|
||||||
|
|
||||||
|
-- done, show the app
|
||||||
|
stat_page.nav_to()
|
||||||
|
load_pane.set_value(2)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- delete the elements and switch back to the loading screen
|
||||||
|
local function unload()
|
||||||
|
if page_div then
|
||||||
|
page_div.delete()
|
||||||
|
page_div = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
app.set_sidebar({ { label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = db.nav.go_home } })
|
||||||
|
app.delete_pages()
|
||||||
|
|
||||||
|
-- show loading screen
|
||||||
|
load_pane.set_value(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
app.set_load(load)
|
||||||
|
app.set_unload(unload)
|
||||||
|
|
||||||
|
return main
|
||||||
|
end
|
||||||
|
|
||||||
|
return new_view
|
||||||
@@ -312,8 +312,6 @@ local function new_view(root)
|
|||||||
c_emg.register(u_ps, "EmergencyCoolant", c_emg.update)
|
c_emg.register(u_ps, "EmergencyCoolant", c_emg.update)
|
||||||
c_mwrf.register(u_ps, "MaxWaterReturnFeed", c_mwrf.update)
|
c_mwrf.register(u_ps, "MaxWaterReturnFeed", c_mwrf.update)
|
||||||
|
|
||||||
-- rcs_div.line_break()
|
|
||||||
-- TextBox{parent=rcs_div,text="Mismatches",alignment=ALIGN.CENTER,fg_bg=label}
|
|
||||||
local c_cfm = IconIndicator{parent=rcs_div,label="Coolant Feed",states=yel_ind_s}
|
local c_cfm = IconIndicator{parent=rcs_div,label="Coolant Feed",states=yel_ind_s}
|
||||||
local c_brm = IconIndicator{parent=rcs_div,label="Boil Rate",states=yel_ind_s}
|
local c_brm = IconIndicator{parent=rcs_div,label="Boil Rate",states=yel_ind_s}
|
||||||
local c_sfm = IconIndicator{parent=rcs_div,label="Steam Feed",states=yel_ind_s}
|
local c_sfm = IconIndicator{parent=rcs_div,label="Steam Feed",states=yel_ind_s}
|
||||||
@@ -323,7 +321,6 @@ local function new_view(root)
|
|||||||
c_sfm.register(u_ps, "SteamFeedMismatch", c_sfm.update)
|
c_sfm.register(u_ps, "SteamFeedMismatch", c_sfm.update)
|
||||||
|
|
||||||
rcs_div.line_break()
|
rcs_div.line_break()
|
||||||
-- TextBox{parent=rcs_div,text="Aggregate Checks",alignment=ALIGN.CENTER,fg_bg=label}
|
|
||||||
|
|
||||||
if unit.num_boilers > 0 then
|
if unit.num_boilers > 0 then
|
||||||
local wll = IconIndicator{parent=rcs_div,label="Boiler Water Lo",states=red_ind_s}
|
local wll = IconIndicator{parent=rcs_div,label="Boiler Water Lo",states=red_ind_s}
|
||||||
|
|||||||
@@ -95,8 +95,8 @@ local function new_view(root)
|
|||||||
|
|
||||||
local function set_waste(mode) process.set_unit_waste(i, mode) end
|
local function set_waste(mode) process.set_unit_waste(i, mode) end
|
||||||
|
|
||||||
local waste_prod = StateIndicator{parent=u_div,x=16,y=3,states=style.waste.states_abbrv,value=1,min_width=6}
|
local waste_prod = StateIndicator{parent=u_div,x=16,y=3,states=style.get_waste().states_abbrv,value=1,min_width=6}
|
||||||
local waste_mode = RadioButton{parent=u_div,y=3,options=style.waste.unit_opts,callback=set_waste,radio_colors=cpair(colors.lightGray,colors.gray),select_color=colors.white}
|
local waste_mode = RadioButton{parent=u_div,y=3,options=style.get_waste().unit_opts,callback=set_waste,radio_colors=cpair(colors.lightGray,colors.gray),select_color=colors.white}
|
||||||
|
|
||||||
waste_prod.register(u_ps, "U_WasteProduct", waste_prod.update)
|
waste_prod.register(u_ps, "U_WasteProduct", waste_prod.update)
|
||||||
waste_mode.register(u_ps, "U_WasteMode", waste_mode.set_value)
|
waste_mode.register(u_ps, "U_WasteMode", waste_mode.set_value)
|
||||||
@@ -159,8 +159,8 @@ local function new_view(root)
|
|||||||
|
|
||||||
TextBox{parent=c_div,y=1,text="Waste Control",alignment=ALIGN.CENTER}
|
TextBox{parent=c_div,y=1,text="Waste Control",alignment=ALIGN.CENTER}
|
||||||
|
|
||||||
local status = StateIndicator{parent=c_div,x=3,y=3,states=style.waste.states,value=1,min_width=17}
|
local status = StateIndicator{parent=c_div,x=3,y=3,states=style.get_waste().states,value=1,min_width=17}
|
||||||
local waste_prod = RadioButton{parent=c_div,y=5,options=style.waste.options,callback=process.set_process_waste,radio_colors=cpair(colors.lightGray,colors.gray),select_color=colors.white}
|
local waste_prod = RadioButton{parent=c_div,y=5,options=style.get_waste().options,callback=process.set_process_waste,radio_colors=cpair(colors.lightGray,colors.gray),select_color=colors.white}
|
||||||
|
|
||||||
status.register(f_ps, "current_waste_product", status.update)
|
status.register(f_ps, "current_waste_product", status.update)
|
||||||
waste_prod.register(f_ps, "process_waste_product", waste_prod.set_value)
|
waste_prod.register(f_ps, "process_waste_product", waste_prod.set_value)
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
--
|
||||||
|
-- All the text documentation used in the Guide app is defined in this file.
|
||||||
|
--
|
||||||
|
|
||||||
local const = require("scada-common.constants")
|
local const = require("scada-common.constants")
|
||||||
|
|
||||||
local docs = {}
|
local docs = {}
|
||||||
@@ -7,7 +11,9 @@ local DOC_ITEM_TYPE = {
|
|||||||
SECTION = 1,
|
SECTION = 1,
|
||||||
SUBSECTION = 2,
|
SUBSECTION = 2,
|
||||||
TEXT = 3,
|
TEXT = 3,
|
||||||
LIST = 4
|
NOTE = 4,
|
||||||
|
TIP = 5,
|
||||||
|
LIST = 6
|
||||||
}
|
}
|
||||||
|
|
||||||
---@enum DOC_LIST_TYPE
|
---@enum DOC_LIST_TYPE
|
||||||
@@ -51,6 +57,18 @@ local function text(body)
|
|||||||
table.insert(target, item)
|
table.insert(target, item)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function note(body)
|
||||||
|
---@class pocket_doc_note
|
||||||
|
local item = { type = DOC_ITEM_TYPE.NOTE, text = body }
|
||||||
|
table.insert(target, item)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function tip(body)
|
||||||
|
---@class pocket_doc_tip
|
||||||
|
local item = { type = DOC_ITEM_TYPE.TIP, text = body }
|
||||||
|
table.insert(target, item)
|
||||||
|
end
|
||||||
|
|
||||||
---@param type DOC_LIST_TYPE
|
---@param type DOC_LIST_TYPE
|
||||||
---@param items table
|
---@param items table
|
||||||
---@param colors table|nil colors for indicators or nil for normal lists
|
---@param colors table|nil colors for indicators or nil for normal lists
|
||||||
@@ -60,14 +78,140 @@ local function list(type, items, colors)
|
|||||||
table.insert(target, list_def)
|
table.insert(target, list_def)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- important to note in the future: The PLC should always be in a chunk with the reactor to ensure it can protect it on chunk load if you do not keep it all chunk loaded
|
--#region System Usage
|
||||||
|
|
||||||
|
docs.usage = {
|
||||||
|
conn = {}, config = {}, manual = {}, auto = {}, waste = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
target = docs.usage.conn
|
||||||
|
sect("Overview")
|
||||||
|
tip("For the best setup experience, see the Wiki on GitHub or the YouTube channel! This app does not contain all information.")
|
||||||
|
text("Mekanism devices are connected to ComputerCraft computers that form the SCADA control system.")
|
||||||
|
sect("Mekanism Conns")
|
||||||
|
text("Multiblocks and single block devices are both connected directly to a computer by touching it or via wired modems.")
|
||||||
|
doc("usage_conn_mb", "Multiblocks", "For multiblocks, a logic adapter is used if it exists for that multiblock, otherwise a valve or port block is used.")
|
||||||
|
text("A wired modem is only connected to the block when you right click it and it gets a red border and you see a message in the chat with the peripheral name.")
|
||||||
|
tip("Do not connect all peripherals in the system on the same network cable, since Reactor PLCs will grab the first reactor they find and you may accidentally duplicate RTUs.")
|
||||||
|
sect("Computer Conns")
|
||||||
|
tip("It helps to be familiar with how ComputerCraft manages peripherals before using this system, though it is not necessary.")
|
||||||
|
doc("usage_conn_network", "Network", "All computers in the system communicate with each other via wireless or ender modems. Ender modems are preferred due to the unlimited range.")
|
||||||
|
text("Five different network channels are used and must have the same value for each name across all devices.")
|
||||||
|
text("For example, the supervisor channel SVR_CHANNEL must be set to the same channel for all devices in your system. Two different named channels should not share the same value (such as SVR_CHANNEL vs CRD_CHANNEL).")
|
||||||
|
doc("usage_conn_peri", "Peripherals", "ComputerCraft peripherals like monitors and speakers need to touch the computer or be connected via wired modems.")
|
||||||
|
|
||||||
|
target = docs.usage.config
|
||||||
|
sect("Overview")
|
||||||
|
tip("For the best setup experience, see the Wiki on GitHub or the YouTube channel! This app does not contain all information.")
|
||||||
|
text("All devices have a configurator program you can launch by running the 'configure' command.")
|
||||||
|
sect("Networking")
|
||||||
|
doc("usage_cfg_id", "Computer ID", "A computer ID must NEVER be the identical between devices, which can only happen if you duplicate a computer (such as if you middle-click on it and place it again in creative mode).")
|
||||||
|
doc("usage_cfg_chan", "Channels", "Channels are used for the computer to computer communication, described in the connection guide section. Channels with the same name must have the same value across all devices in your system and channels with different names cannot overlap.")
|
||||||
|
doc("usage_cfg_to", "Conn Timeout", "After this period of time the device will close the connection assuming the other device is unresponsive.")
|
||||||
|
doc("usage_cfg_tr", "Trusted Range", "Devices further than this block distance away will have any network traffic rejected by this device.")
|
||||||
|
doc("usage_cfg_auth", "Authentication", "To provide a level of security, you can enable facility-wide authentication by setting keys, which must be the same (and set) on all your devices. This adds computation time to each network transmission so you should only do this if you need it on multiplayer.")
|
||||||
|
sect("Logging")
|
||||||
|
text("Logs are automatically saved to a log.txt file in the root of the computer. You can change the path to it, if it contains verbose debug messages, and if it is appended to or overwritten each time the program runs.")
|
||||||
|
text("If you intend to be able to share logs, you should leave it to append.")
|
||||||
|
doc("usage_cfg_log_upload", "Sharing Logs", "To share logs, you would run 'pastebin put log.txt' where your log file is then share the code.")
|
||||||
|
sect("Reactor PLC")
|
||||||
|
text("The Reactor PLC must be connected to a single fission reactor that it will manage. Use the configurator to choose if you would like it to operate as networked or not.")
|
||||||
|
tip("The Reactor PLC should always be in a chunk with the reactor to ensure it can protect it on server start and/or chunk load.")
|
||||||
|
doc("usage_cfg_plc_nonet", "Non-Networked", "This lets you use this device as an advanced standalone safety system rather than a basic redstone breaker for easier safety protection.")
|
||||||
|
doc("usage_cfg_plc_net", "Networked", "This is the most commonly used mode. The Reactor PLC will require a connection to the Supervisor to operate and will allow usage through that for more advanced functionality.")
|
||||||
|
doc("usage_cfg_plc_unit", "Unit ID", "When networked, you can set any unit ID ranging from 1 to 4. Multiple Reactor PLCs cannot share the same unit ID.")
|
||||||
|
sect("RTU Gateway")
|
||||||
|
text("The RTU Gateway allows connecting multiple RTU interfaces to the SCADA system. These interfaces may be external peripherals or redstone.")
|
||||||
|
text("All devices except for fission reactors must be connected via an RTU Gateway.")
|
||||||
|
sect("Supervisor")
|
||||||
|
text("The Supervisor configuration is core to the entire system. If you change things about the system, such as the cooling devices or reactor count, it must be updated here.")
|
||||||
|
text("This configuration contains many settings that are detailed better in the configurator so they will not be covered here.")
|
||||||
|
doc("usage_cfg_sv_tanks", "Dynamic Tanks", "Dynamic tanks can be used to provide emergency coolant (and/or auxiliary coolant) to the system. Many layouts are supported by using a mix of facility tanks (connect to 1+ units) and unit tanks (connect to only one unit).")
|
||||||
|
doc("usage_cfg_sv_aux", "Auxiliary Coolant", "This coolant is enabled at the start of reactors to prevent water levels from dropping in the reactor or boiler while the turbine ramps up. This can be connected to a dynamic tank, a sink, or any other water supply.")
|
||||||
|
sect("Coordinator")
|
||||||
|
text("The Coordinator configuration is mainly focused around setting up your displays. This is best to do last after everything else. See the wiki on the GitHub for details on monitor sizing.")
|
||||||
|
tip("When changing the unit count on the Supervisor, you must also update it on the Coordinator.")
|
||||||
|
doc("usage_cfg_crd_main", "Main Monitor", "The main monitor contains the main interface and overview. It is always 8 block wide with varying height depending on how many units you have.")
|
||||||
|
doc("usage_cfg_crd_flow", "Flow Monitor", "The flow monitor contains the waste and coolant flow diagram. It is always 8 block wide with varying height depending on how many units you have.")
|
||||||
|
doc("usage_cfg_crd_unit", "Unit Monitor", "You need one unit monitor per reactor, and it is always a 4x4 monitor.")
|
||||||
|
text("Monitors can be connected by direct contact or via wired modems.")
|
||||||
|
text("Various unit and color options are available to customize the display to your liking. Using energy scales other than RF can impact the precision of your power-related auto control setpoints as RF is always used internally.")
|
||||||
|
sect("Pocket")
|
||||||
|
text("You're already here, so not much to mention!")
|
||||||
|
sect("Self-Check")
|
||||||
|
text("Most application configurators provide a self-check function that will check the validity of your configuration and the network connection. You should run this if you are having issues with that device.")
|
||||||
|
sect("Config Changes")
|
||||||
|
text("When an update adds or removes or otherwise modifies configuration requirements, you will be warned that you need to re-configure. You will not lose any prior data as updates will preserve configurations, you just need to step through the instructions again to add or change any new data.")
|
||||||
|
|
||||||
|
target = docs.usage.manual
|
||||||
|
sect("Overview")
|
||||||
|
text("Manual reactor control still includes safety checks and monitoring, but the burn rate is not automatically controlled.")
|
||||||
|
text("A unit is under manual control when the AUTO CTRL option Manual is selected on the unit display.")
|
||||||
|
note("Specific UIs will not be discussed here. If you need help with the UI, refer to Operator UIs > Coordinator UI > Unit Displays.")
|
||||||
|
sect("Manual Control")
|
||||||
|
text("The unit display on the Coordinator is used to run manual control. You may also start/stop and set the burn rate via the Mekanism UI on the Fission Reactor.")
|
||||||
|
tip("If some controls are grayed out on the unit display, that operation isn't currently available, such as due to the reactor being already started or being under auto control.")
|
||||||
|
text("Manual control is started by the START button and runs at the commanded burn rate next to it, which can be modified before starting or after having started by selecting a value then pressing SET.")
|
||||||
|
text("The reactor can be stopped via SCRAM, then the RPS needs to be reset via RESET.")
|
||||||
|
|
||||||
|
target = docs.usage.auto
|
||||||
|
sect("Overview")
|
||||||
|
text("A main feature of this system is automatic reactor control that supports various managed control modes.")
|
||||||
|
tip("You should first review the Main Display and Unit Display documentation under Operator UIs > Coordinator before proceeding if you are not familiar with the interfaces.")
|
||||||
|
sect("Configuration")
|
||||||
|
note("Configurations cannot be modified while auto control is active.")
|
||||||
|
doc("usage_auto_assign", "Unit Assignments", "Auto control only applies to units set to a mode other than Manual. To prefer certain units or only use the minimum number necessary, priority groups are used to split up the required burn rate.")
|
||||||
|
text("Primary units will be used first, followed by secondary, etc. If multiple are assigned to a group, burn rate will be assigned evenly between them.")
|
||||||
|
text("The next priority group will only be used once the previous one cannot keep up with the total required burn rate for auto control at that moment.")
|
||||||
|
doc("usage_auto_setpoints", "Setpoints", "Three setpoint spinner inputs are available for the three setpoint-based auto control modes. The system will do its best to meet the requested value, with the current value listed below the input.")
|
||||||
|
doc("usage_auto_limits", "Unit Limits", "Each unit can be limited to a maximum auto control burn rate to prevent exceeding any safe levels that you know of.")
|
||||||
|
doc("usage_auto_states", "Unit States", "Any assigned units must be shown as Ready and not Degraded to use auto control. See Operator UIs > Coordinator > Main Display for more.")
|
||||||
|
sect("Operation Modes")
|
||||||
|
text("Four auto control modes are available that function based on configurations set on the main display. All modes except Monitored Max Burn will try to only use the primary group until it can't keep up, then the secondary, etc.")
|
||||||
|
note("No units will be set to a burn rate higher than their limit.")
|
||||||
|
doc("usage_op_mon_max", "Monitored Max Burn", "This mode runs all units assigned to auto control at their unit limit burn rate regardless of priority group.")
|
||||||
|
doc("usage_op_com_rate", "Combined Burn Rate", "Assigned units will be commanded to meet the Burn Target setpoint.")
|
||||||
|
doc("usage_op_chg_level", "Charge Level", "Assigned units will be commanded to bring the induction matrix up to the requested Charge Target.")
|
||||||
|
doc("usage_op_gen_rate", "Generation Rate", "Assigned units will be commanded to maintain the requested Generation Target.")
|
||||||
|
note("The rate used is the input rate into the induction matrix, so using other power generation sources may disrupt this control mode.")
|
||||||
|
sect("Start and Stop")
|
||||||
|
text("A text box is used to indicate the system status. It will also provide information of why the system has paused control or failed to start.")
|
||||||
|
text("You cannot start auto control until all assigned units have all their devices connected and functional and the reactor's RPS is not tripped.")
|
||||||
|
doc("usage_op_save", "SAVE", "SAVE will save the configuration without starting control.")
|
||||||
|
doc("usage_op_start", "START", "START will attempt to start auto control, which includes first saving the configuration.")
|
||||||
|
doc("usage_op_stop", "STOP", "STOP will stop all reactors assigned to automatic control.")
|
||||||
|
|
||||||
|
target = docs.usage.waste
|
||||||
|
sect("Overview")
|
||||||
|
text("When 'valves' are connected for routing waste, this system can manage which waste product(s) are made. The flow monitor shows the diagram of how valves are meant to be connected.")
|
||||||
|
text("There are three waste products, listed below with the colors generally associated with them.")
|
||||||
|
list(DOC_LIST_TYPE.LED, { "Pu - Plutonium", "Po - Polonium", "AM - Antimatter" }, { colors.cyan, colors.green, colors.purple })
|
||||||
|
note("The Po and Pu colors are swapped in older versions of Mekanism.")
|
||||||
|
sect("Unit Waste")
|
||||||
|
text("Units can be set to specific waste products via buttons at the bottom right of a unit display.")
|
||||||
|
note("Refer to Operator UIs > Coordinator UI > Unit Displays for details.")
|
||||||
|
text("If 'Auto' is selected instead of a waste product, that unit's waste will be processed per the facility waste control.")
|
||||||
|
sect("Facility Waste")
|
||||||
|
text("Facility waste control adds additional functionality to waste processing through automatic control.")
|
||||||
|
text("The waste control interface on the main display lets you set a target waste type along with options that can change that based on circumstances.")
|
||||||
|
note("Refer to Operator UIs > Coordinator UI > Main Display for information on the display and control interface.")
|
||||||
|
doc("usage_waste_fallback", "Pu Fallback", "This option switches facility waste control to plutonium when the SNAs cannot keep up, such as at night.")
|
||||||
|
doc("usage_waste_sps_lc", "Low Charge SPS", "This option prevents the facility waste control from stopping antimatter production at low induction matrix charge (< 10%, resumes after reaching 15%).")
|
||||||
|
text("With that option enabled, antimatter production will continue. With it disabled, it will switch to polonium if set to antimatter while charge is low.")
|
||||||
|
note("Pu Fallback takes priority and will switch to plutonium when appropriate regardless of the Low Charge SPS setting.")
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
|
--#region Operator UIs
|
||||||
|
|
||||||
|
--#region Alarms
|
||||||
|
|
||||||
docs.alarms = {}
|
docs.alarms = {}
|
||||||
|
|
||||||
target = docs.alarms
|
target = docs.alarms
|
||||||
doc("ContainmentBreach", "Containment Breach", "Reactor disconnected or indicated unformed while being at or above 100% damage; explosion assumed.")
|
doc("ContainmentBreach", "Containment Breach", "Reactor disconnected or indicated unformed while being at or above 100% damage; explosion assumed.")
|
||||||
doc("ContainmentRadiation", "Containment Radiation", "Environment detector(s) assigned to the unit have observed high levels of radiation.")
|
doc("ContainmentRadiation", "Containment Radiation", "Environment detector(s) assigned to the unit have observed high levels of radiation.")
|
||||||
doc("ReactorLost", "Reactor Lost", "Reactor PLC has stopped communicating with the supervisor.")
|
doc("ReactorLost", "Reactor Lost", "Reactor PLC has stopped communicating with the Supervisor.")
|
||||||
doc("CriticalDamage", "Damage Critical", "Reactor damage has reached or exceeded 100%, so it will explode at any moment.")
|
doc("CriticalDamage", "Damage Critical", "Reactor damage has reached or exceeded 100%, so it will explode at any moment.")
|
||||||
doc("ReactorDamage", "Reactor Damage", "Reactor temperature causing increasing damage to the reactor casing.")
|
doc("ReactorDamage", "Reactor Damage", "Reactor temperature causing increasing damage to the reactor casing.")
|
||||||
doc("ReactorOverTemp", "Reactor Over Temp", "Reactor temperature is at or above maximum safe temperature, so it is now taking damage.")
|
doc("ReactorOverTemp", "Reactor Over Temp", "Reactor temperature is at or above maximum safe temperature, so it is now taking damage.")
|
||||||
@@ -78,6 +222,10 @@ doc("RPSTransient", "RPS Transient", "Reactor protection system was activated.")
|
|||||||
doc("RCSTransient", "RCS Transient", "Something is wrong with the reactor coolant system, check RCS indicators for details.")
|
doc("RCSTransient", "RCS Transient", "Something is wrong with the reactor coolant system, check RCS indicators for details.")
|
||||||
doc("TurbineTripAlarm", "Turbine Trip", "A turbine stopped rotating, likely due to having full energy storage. This will prevent cooling, so it needs to be resolved before using that unit.")
|
doc("TurbineTripAlarm", "Turbine Trip", "A turbine stopped rotating, likely due to having full energy storage. This will prevent cooling, so it needs to be resolved before using that unit.")
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
|
--#region Annunciators
|
||||||
|
|
||||||
docs.annunc = {
|
docs.annunc = {
|
||||||
unit = {
|
unit = {
|
||||||
main_section = {}, rps_section = {}, rcs_section = {}
|
main_section = {}, rps_section = {}, rcs_section = {}
|
||||||
@@ -89,8 +237,8 @@ docs.annunc = {
|
|||||||
|
|
||||||
target = docs.annunc.unit.main_section
|
target = docs.annunc.unit.main_section
|
||||||
sect("Unit Status")
|
sect("Unit Status")
|
||||||
doc("PLCOnline", "PLC Online", "Indicates if the fission reactor PLC is connected. If it isn't, check that your PLC is on and configured properly.")
|
doc("PLCOnline", "PLC Online", "Indicates if the fission Reactor PLC is connected. If it isn't, check that your PLC is on and configured properly.")
|
||||||
doc("PLCHeartbeat", "PLC Heartbeat", "An indicator of status data being live. As status messages are received from the PLC, this light will turn on and off. If it gets stuck, the supervisor has stopped receiving data or a screen has frozen.")
|
doc("PLCHeartbeat", "PLC Heartbeat", "An indicator of status data being live. As status messages are received from the PLC, this light will turn on and off. If it gets stuck, the Supervisor has stopped receiving data or a screen has frozen.")
|
||||||
doc("RadiationMonitor", "Radiation Monitor", "On if at least one environment detector is connected and assigned to this unit.")
|
doc("RadiationMonitor", "Radiation Monitor", "On if at least one environment detector is connected and assigned to this unit.")
|
||||||
doc("AutoControl", "Automatic Control", "On if the reactor is under the control of one of the automatic control modes.")
|
doc("AutoControl", "Automatic Control", "On if the reactor is under the control of one of the automatic control modes.")
|
||||||
sect("Safety Status")
|
sect("Safety Status")
|
||||||
@@ -98,7 +246,7 @@ doc("ReactorSCRAM", "Reactor SCRAM", "On if the reactor protection system is hol
|
|||||||
doc("ManualReactorSCRAM", "Manual Reactor SCRAM", "On if the operator (you) initiated a SCRAM.")
|
doc("ManualReactorSCRAM", "Manual Reactor SCRAM", "On if the operator (you) initiated a SCRAM.")
|
||||||
doc("AutoReactorSCRAM", "Auto Reactor SCRAM", "On if the automatic control system initiated a SCRAM. The main view screen annunciator will have an indication as to why.")
|
doc("AutoReactorSCRAM", "Auto Reactor SCRAM", "On if the automatic control system initiated a SCRAM. The main view screen annunciator will have an indication as to why.")
|
||||||
doc("RadiationWarning", "Radiation Warning", "On if radiation levels are above normal. There is likely a leak somewhere, so that should be identified and fixed. Hazmat suit recommended.")
|
doc("RadiationWarning", "Radiation Warning", "On if radiation levels are above normal. There is likely a leak somewhere, so that should be identified and fixed. Hazmat suit recommended.")
|
||||||
doc("RCPTrip", "RCP Trip", "Reactor coolant pump tripped. This is a technical concept not directly mapping to Mekansim. Here, it indicates if there is either high heated coolant or low cooled coolant that caused an RPS trip. Check the coolant system if this occurs.")
|
doc("RCPTrip", "RCP Trip", "Reactor coolant pump tripped. This is a technical concept not directly mapping to Mekanism. Here, it indicates if there is either high heated coolant or low cooled coolant that caused an RPS trip. Check the coolant system if this occurs.")
|
||||||
doc("RCSFlowLow", "RCS Flow Low", "Indicates if the reactor coolant system flow is low. This is observed when the cooled coolant level in the reactor is dropping. This can occur while a turbine spins up, but if it persists, check that the cooling system is operating properly. This can occur with smaller boilers or when using pipes and not having enough.")
|
doc("RCSFlowLow", "RCS Flow Low", "Indicates if the reactor coolant system flow is low. This is observed when the cooled coolant level in the reactor is dropping. This can occur while a turbine spins up, but if it persists, check that the cooling system is operating properly. This can occur with smaller boilers or when using pipes and not having enough.")
|
||||||
doc("CoolantLevelLow", "Coolant Level Low", "On if the reactor coolant level is lower than it should be. Check the coolant system.")
|
doc("CoolantLevelLow", "Coolant Level Low", "On if the reactor coolant level is lower than it should be. Check the coolant system.")
|
||||||
doc("ReactorTempHigh", "Reactor Temp. High", "On if the reactor temperature is above expected maximum operating temperature. This is not yet damaging, but should be attended to. Check coolant system.")
|
doc("ReactorTempHigh", "Reactor Temp. High", "On if the reactor temperature is above expected maximum operating temperature. This is not yet damaging, but should be attended to. Check coolant system.")
|
||||||
@@ -118,7 +266,7 @@ doc("high_temp", "Temperature High", "Indicates if the RPS tripped due to reachi
|
|||||||
doc("low_cool", "Coolant Level Low Low", "Indicates if the RPS tripped due to very low coolant levels that result in the temperature uncontrollably rising. Ensure that the cooling system can provide sufficient cooled coolant flow.")
|
doc("low_cool", "Coolant Level Low Low", "Indicates if the RPS tripped due to very low coolant levels that result in the temperature uncontrollably rising. Ensure that the cooling system can provide sufficient cooled coolant flow.")
|
||||||
doc("no_fuel", "No Fuel", "Indicates if the RPS tripped due to no fuel being available. Check fuel input.")
|
doc("no_fuel", "No Fuel", "Indicates if the RPS tripped due to no fuel being available. Check fuel input.")
|
||||||
doc("fault", "PPM Fault", "Indicates if the RPS tripped due to a peripheral access fault. Something went wrong interfacing with the reactor, try restarting the PLC.")
|
doc("fault", "PPM Fault", "Indicates if the RPS tripped due to a peripheral access fault. Something went wrong interfacing with the reactor, try restarting the PLC.")
|
||||||
doc("timeout", "Connection Timeout", "Indicates if the RPS tripped due to losing connection with the supervisory computer. Check that your PLC and supervisor remain chunk loaded.")
|
doc("timeout", "Connection Timeout", "Indicates if the RPS tripped due to losing connection with the supervisory computer. Check that your PLC and Supervisor remain chunk loaded.")
|
||||||
doc("sys_fail", "System Failure", "Indicates if the RPS tripped due to the reactor not being formed. Ensure that the multi-block is formed.")
|
doc("sys_fail", "System Failure", "Indicates if the RPS tripped due to the reactor not being formed. Ensure that the multi-block is formed.")
|
||||||
|
|
||||||
target = docs.annunc.unit.rcs_section
|
target = docs.annunc.unit.rcs_section
|
||||||
@@ -130,7 +278,7 @@ doc("SteamFeedMismatch", "Steam Feed Mismatch", "There is an above tolerance dif
|
|||||||
doc("MaxWaterReturnFeed", "Max Water Return Feed", "The turbines are condensing the max rate of water that they can per the structure build. If water return is insufficient, add more saturating condensers to your turbine(s).")
|
doc("MaxWaterReturnFeed", "Max Water Return Feed", "The turbines are condensing the max rate of water that they can per the structure build. If water return is insufficient, add more saturating condensers to your turbine(s).")
|
||||||
doc("WaterLevelLow", "Water Level Low", "The water level in the boiler is low. A larger boiler water tank may help, or you can feed additional water into the boiler from elsewhere.")
|
doc("WaterLevelLow", "Water Level Low", "The water level in the boiler is low. A larger boiler water tank may help, or you can feed additional water into the boiler from elsewhere.")
|
||||||
doc("HeatingRateLow", "Heating Rate Low", "The boiler is not hot enough to boil water, but it is receiving heated coolant. This is almost never a safety concern.")
|
doc("HeatingRateLow", "Heating Rate Low", "The boiler is not hot enough to boil water, but it is receiving heated coolant. This is almost never a safety concern.")
|
||||||
doc("SteamDumpOpen", "Steam Relief Valve Open", "This turns yellow if the turbine is set to dumping excess and red if it is set to dumping [all]. 'Relief Valve' in this case is that setting allowing the venting of steam. You should never have this set to dumping [all]. Emergency coolant activation from the supervisor will automatically set it to dumping excess to ensure there is no backup of steam as water is added.")
|
doc("SteamDumpOpen", "Steam Relief Valve Open", "This turns yellow if the turbine is set to dumping excess and red if it is set to dumping [all]. 'Relief Valve' in this case is that setting allowing the venting of steam. You should never have this set to dumping [all]. Emergency coolant activation from the Supervisor will automatically set it to dumping excess to ensure there is no backup of steam as water is added.")
|
||||||
doc("TurbineOverSpeed", "Turbine Over Speed", "The turbine is at steam capacity, but not tripped. You may need more turbines if they can't keep up.")
|
doc("TurbineOverSpeed", "Turbine Over Speed", "The turbine is at steam capacity, but not tripped. You may need more turbines if they can't keep up.")
|
||||||
doc("GeneratorTrip", "Generator Trip", "The turbine is no longer outputting power due to it having nowhere to go. Likely due to full power storage. This will lead to a Turbine Trip if not addressed.")
|
doc("GeneratorTrip", "Generator Trip", "The turbine is no longer outputting power due to it having nowhere to go. Likely due to full power storage. This will lead to a Turbine Trip if not addressed.")
|
||||||
doc("TurbineTrip", "Turbine Trip", "The turbine has reached its maximum power charge and has stopped rotating, and as a result stopped cooling steam to water. Ensure the turbine has somewhere to output power, as this is the most common cause of reactor meltdowns. However, the likelihood of a meltdown with this system in place is much lower, especially with emergency coolant helping during turbine trips.")
|
doc("TurbineTrip", "Turbine Trip", "The turbine has reached its maximum power charge and has stopped rotating, and as a result stopped cooling steam to water. Ensure the turbine has somewhere to output power, as this is the most common cause of reactor meltdowns. However, the likelihood of a meltdown with this system in place is much lower, especially with emergency coolant helping during turbine trips.")
|
||||||
@@ -154,28 +302,144 @@ doc("as_crit_alarm", "Unit Critical Alarm", "Automatic SCRAM occurred due to cri
|
|||||||
doc("as_radiation", "Facility Radiation High", "Automatic SCRAM occurred due to high facility radiation levels.")
|
doc("as_radiation", "Facility Radiation High", "Automatic SCRAM occurred due to high facility radiation levels.")
|
||||||
doc("as_gen_fault", "Gen. Control Fault", "Automatic SCRAM occurred due to assigned units being degraded/no longer ready during generation mode. The system will automatically resume (starting with initial ramp) once the problem is resolved.")
|
doc("as_gen_fault", "Gen. Control Fault", "Automatic SCRAM occurred due to assigned units being degraded/no longer ready during generation mode. The system will automatically resume (starting with initial ramp) once the problem is resolved.")
|
||||||
|
|
||||||
docs.fp = {
|
--#endregion
|
||||||
common = {}, r_plc = {}, rtu_gw = {}, supervisor = {}
|
|
||||||
|
--#region Coordinator UI
|
||||||
|
|
||||||
|
docs.c_ui = {
|
||||||
|
main = {}, flow = {}, unit = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
--comp id "This must never be the identical between devices, and that can only happen if you duplicate a computer (such as middle-click on it and place it elsewhere in creative mode)."
|
target = docs.c_ui.main
|
||||||
|
sect("Facility Diagram")
|
||||||
|
text("The facility overview diagram is made up of unit diagrams showing the reactor, boiler(s) if present, and turbine(s). This includes values of various key statistics such as temperatures along with bars showing the fill percentage of the tanks in each multiblock.")
|
||||||
|
text("Boilers are shown under the reactor, listed in order of index (#1 then #2 below). Turbines are shown to the right, also listed in order of index (indexes are per unit and set in the RTU Gateway configuration).")
|
||||||
|
text("Pipe connections are visualized with color-coded lines, which are primarily to indicate connections, as not all facilities may use pipes.")
|
||||||
|
note("If a component you have is not showing up, ensure the Supervisor is configured for your actual cooling configuration.")
|
||||||
|
sect("Facility Status")
|
||||||
|
note("The annunciator here is described in Operator UIs > Annunciators.")
|
||||||
|
doc("ui_fac_scram", "FAC SCRAM", "This SCRAMs all units in the facility.")
|
||||||
|
doc("ui_fac_ack", "ACK \x13", "This acknowledges (mutes) all alarms for all units in the facility.")
|
||||||
|
doc("ui_fac_rad", "Radiation", "The facility radiation, which is the current maximum of all connected facility radiation monitors (excludes unit monitors).")
|
||||||
|
doc("ui_fac_linked", "Linked RTUs", "The number of RTU Gateways connected.")
|
||||||
|
sect("Automatic Control")
|
||||||
|
text("This interface is used for managing automatic facility control, which only applies to units set via the unit display to be under auto control. This includes setpoints, status, configuration, and control.")
|
||||||
|
doc("ui_fac_auto_bt", "Burn Target", "When set to Combined Burn Rate mode, assigned units will ramp up to meet this combined target.")
|
||||||
|
doc("ui_fac_auto_ct", "Charge Target", "When set to Charge Level mode, assigned units will run to reach and maintain this induction matrix charge level.")
|
||||||
|
doc("ui_fac_auto_gt", "Gen. Target", "When set to Generation Rate mode, assigned units will run to reach and maintain this continuous power output, using the induction matrix input rate.")
|
||||||
|
doc("ui_fac_save", "SAVE", "This saves your configuration without starting control.")
|
||||||
|
doc("ui_fac_start", "START", "This starts the configured automatic control.")
|
||||||
|
tip("START also includes the SAVE operation.")
|
||||||
|
doc("ui_fac_stop", "STOP", "This terminates automatic control, stopping assigned units.")
|
||||||
|
text("There are four automatic control modes, detailed further in System Usage > Automatic Control")
|
||||||
|
doc("ui_fac_auto_mmb", "Monitored Max Burn", "This runs all assigned units at the maximum configured rate.")
|
||||||
|
doc("ui_fac_auto_cbr", "Combined Burn Rate", "This runs assigned units to meet the target combined rate.")
|
||||||
|
doc("ui_fac_auto_cl", "Charge Level", "This runs assigned units to maintain an induction matrix charge level.")
|
||||||
|
doc("ui_fac_auto_gr", "Generation Rate", "This runs assigned units to meet a target induction matrix power input rate.")
|
||||||
|
doc("ui_fac_auto_lim", "Unit Limit", "Each unit can have a limit set that auto control will never exceed.")
|
||||||
|
doc("ui_fac_unit_ready", "Unit Status Ready", "A unit is only ready for auto control if all multiblocks are formed, online with data received, and there is no RPS trip.")
|
||||||
|
doc("ui_fac_unit_degraded", "Unit Status Degraded", "A unit is degraded if the reactor, boiler(s), and/or turbine(s) are faulted or not connected.")
|
||||||
|
sect("Waste Control")
|
||||||
|
text("Above unit statuses are the unit waste statuses, showing which are set to the auto waste mode and the actual current waste production of that unit.")
|
||||||
|
text("The facility automatic waste control interface is surrounded by a brown border and lets you configure that system, starting with the requested waste product.")
|
||||||
|
doc("ui_fac_waste_pu_fall_act", "Fallback Active", "When the system is falling back to plutonium production while SNAs cannot keep up.")
|
||||||
|
doc("ui_fac_waste_sps_lc_act", "SPS Disabled LC", "When the system is falling back to polonium production to prevent draining all power with the SPS while the induction matrix charge has dropped below 10% and not yet reached 15%.")
|
||||||
|
doc("ui_fac_waste_pu_fall", "Pu Fallback", "Switch from Po or Antimatter when the SNAs can't keep up (like at night).")
|
||||||
|
doc("ui_fac_waste_sps_lc", "Low Charge SPS", "Continue running antimatter production even at low induction matrix charge levels (<10%).")
|
||||||
|
sect("Induction Matrix")
|
||||||
|
text("The induction matrix statistics are shown at the bottom right, including fill bars for the FILL, I (input rate), and O (output rate).")
|
||||||
|
text("Averages are computed by the system while other data is directly from the device.")
|
||||||
|
doc("ui_fac_im_charge", "Charging", "Charge is increasing (more input than output).")
|
||||||
|
doc("ui_fac_im_charge", "Discharging", "Charge is draining (more output than input).")
|
||||||
|
doc("ui_fac_im_charge", "Max I/O Rate", "The induction providers are at their maximum rate.")
|
||||||
|
doc("ui_fac_eta", "ETA", "The ETA is based off a longer average so it may take a minute to stabilize, but will give a rough estimate of time to charge/discharge.")
|
||||||
|
|
||||||
|
target = docs.c_ui.flow
|
||||||
|
sect("Flow Diagram")
|
||||||
|
text("The coolant and waste flow monitor is one large P&ID (process and instrumentation diagram) showing an overview of those flows.")
|
||||||
|
text("Color-coded pipes are used to show the connections, and valve symbols \x10\x11 are used to show valves (redstone controlled pipes).")
|
||||||
|
doc("ui_flow_rates", "Flow Rates", "Flow rates are always shown below their respective pipes and sourced from devices when possible. The waste flow is based on the reactor burn rate, then everything downstream of the SNAs are based on the SNA production rate.")
|
||||||
|
doc("ui_flow_valves", "Standard Valves", "Valve naming (PV00-XX) is based on P&ID naming conventions. These count up across the whole facility, and use tags at the end to add clarity.")
|
||||||
|
note("The indicator next to the label turns on when the associated redstone RTU is connected.")
|
||||||
|
list(DOC_LIST_TYPE.BULLET, { "PU: Plutonium", "PO: Polonium", "PL: Po Pellets", "AM: Antimatter", "EMC: Emer. Coolant", "AUX: Aux. Coolant" })
|
||||||
|
doc("ui_flow_valve_open", "OPEN", "This indicates if the respective valve is commanded open.")
|
||||||
|
doc("ui_flow_prv", "PRVs", "Pressure Relief Valves (PRVs) are used to show the turbine steam dumping states of each turbine.")
|
||||||
|
list(DOC_LIST_TYPE.LED, { "Not Dumping", "Dumping Excess", "Dumping" }, { colors.gray, colors.yellow, colors.red })
|
||||||
|
sect("SNAs")
|
||||||
|
text("Solar Neutron Activators are shown on the flow diagram as a combined block due to the large variable count supported.")
|
||||||
|
tip("SNAs consume 10x the waste as they produce in antimatter, so take that into account before connecting too many SNAs.")
|
||||||
|
doc("ui_flow_sna_act", "ACTIVE", "The SNAs have a non-zero total flow.")
|
||||||
|
doc("ui_flow_sna_cnt", "CNT", "The count of SNAs assigned to the unit.")
|
||||||
|
doc("ui_flow_sna_peak_o", "PEAK\x1a", "The combined theoretical peak output the SNAs can achieve under full sunlight.")
|
||||||
|
doc("ui_flow_sna_max_o", "MAX \x1a", "The current combined maximum output rate of the SNAs (based on current sunlight).")
|
||||||
|
doc("ui_flow_sna_max_i", "\x1aMAX", "The computed combined maximum input rate (10x the output rate).")
|
||||||
|
doc("ui_flow_sna_in", "\x1aIN", "The current input rate into the SNAs.")
|
||||||
|
sect("Dynamic Tanks")
|
||||||
|
text("Dynamic tanks configured for the system are listed to the left. The title may start with U for unit tanks or F for facility tanks.")
|
||||||
|
text("The fill information and water level are shown below the status label.")
|
||||||
|
doc("ui_flow_dyn_fill", "FILL", "If filling is enabled by the tank mode (via Mekanism UI).")
|
||||||
|
doc("ui_flow_dyn_empty", "EMPTY", "If emptying is enabled by the tank mode (via Mekanism UI).")
|
||||||
|
sect("SPS")
|
||||||
|
doc("ui_flow_sps_in", "Input Rate", "The rate of polonium into the SPS.")
|
||||||
|
doc("ui_flow_sps_prod", "Production Rate", "The rate of antimatter produced by the SPS.")
|
||||||
|
sect("Statistics")
|
||||||
|
text("The sum of all unit's waste rate statistics are shown under the SPS block. These are combined current rates, not long-term sums.")
|
||||||
|
doc("ui_flow_stat_raw", "RAW WASTE", "The combined rate of raw waste generated by the reactors before processing.")
|
||||||
|
doc("ui_flow_stat_proc", "PROC. WASTE", "The combined rates of different waste product production. Pu is plutonium, Po is polonium, and PoPl is polonium pellets. Antimatter is shown in the SPS block.")
|
||||||
|
doc("ui_flow_stat_spent", "SPENT WASTE", "The combined rate of spent waste generated after processing.")
|
||||||
|
sect("Other Blocks")
|
||||||
|
text("Other blocks, such as CENTRIFUGE, correspond to devices that are not intended to be connected and/or serve as labels.")
|
||||||
|
|
||||||
|
target = docs.c_ui.unit
|
||||||
|
sect("Data Display")
|
||||||
|
text("The unit monitor contains extensive data information, including annunciator and alarm displays described in the associated sections in the Operator UIs section.")
|
||||||
|
doc("ui_unit_core", "Core Map", "A core map diagram is shown at the top right, colored by core temperature. The layout is based off of the multiblock dimensions.")
|
||||||
|
list(DOC_LIST_TYPE.BULLET, { "Gray <= 300\xb0C", "Blue <= 350\xb0C", "Green < 600\xb0C", "Yellow < 100\xb0C", "Orange < 1200\xb0C", "Red < 1300\xb0C", "Pink >= 1300\xb0C" })
|
||||||
|
text("Internal tanks (fuel, cooled coolant, heated coolant, and waste) are displayed below the core map, labeled F, C, H, and W, respectively.")
|
||||||
|
doc("ui_unit_rad", "Radiation", "The unit radiation, which is the current maximum of all connected radiation monitors assigned to this unit.")
|
||||||
|
text("Multiple other data values are shown but should be self-explanatory.")
|
||||||
|
sect("Controls")
|
||||||
|
text("A set of buttons and the burn rate input are used for manual reactor control. When in auto mode, unavailable controls are disabled. The burn rate is only applied after SET is pressed.")
|
||||||
|
doc("ui_unit_start", "START", "This starts the reactor at the requested burn rate.")
|
||||||
|
doc("ui_unit_scram", "SCRAM", "This SCRAMs the reactor.")
|
||||||
|
doc("ui_unit_ack", "ACK \x13", "This acknowledges alarms on this unit.")
|
||||||
|
doc("ui_unit_reset", "RESET", "This resets the RPS for this unit.")
|
||||||
|
sect("Auto Control")
|
||||||
|
text("To put this unit under auto control, select an option other than Manual. You must press SET to apply this, but cannot change this while auto control is active. The priorities available are described in System Usage > Automatic Control.")
|
||||||
|
doc("ui_unit_prio", "Prio. Group", "This displays the unit's auto control priority group.")
|
||||||
|
doc("ui_unit_ready", "READY", "This indicates if the unit is ready for auto control. A unit is only ready for auto control if all multiblocks are formed, online with data received, and there is no RPS trip.")
|
||||||
|
doc("ui_unit_standby", "STANDBY", "This indicates if the unit is set to auto control and that is active, but the auto control does not currently need this reactor to run at the moment, so it is idle.")
|
||||||
|
sect("Waste Processing")
|
||||||
|
text("The unit's waste output configuration can be set via these buttons. Auto will put this unit under control of the facility waste control, otherwise the system will always command the requested option for this unit.")
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
|
--#region Front Panels
|
||||||
|
|
||||||
|
docs.fp = {
|
||||||
|
common = {}, r_plc = {}, rtu_gw = {}, supervisor = {}, coordinator = {}
|
||||||
|
}
|
||||||
|
|
||||||
target = docs.fp.common
|
target = docs.fp.common
|
||||||
sect("Core Status")
|
sect("Core Status")
|
||||||
doc("fp_status", "STATUS", "This is always lit, except on the Reactor PLC (see Reactor PLC section).")
|
doc("fp_status", "STATUS", "This is always lit, except on the Reactor PLC (see Reactor PLC section).")
|
||||||
doc("fp_heartbeat", "HEARTBEAT", "This alternates between lit and unlit as the main loop on the device runs. If this freezes, something is wrong and the logs will indicate why.")
|
doc("fp_heartbeat", "HEARTBEAT", "This alternates between lit and unlit as the main loop on the device runs. If this freezes, something is wrong and the logs will indicate why.")
|
||||||
sect("Hardware & Network")
|
sect("Hardware & Network")
|
||||||
doc("fp_modem", "MODEM", "This lights up if the wireless/ender modem is connected. In parentheses is the unique computer ID of this device, which will show up in places such as the supervisor's connection lists.")
|
doc("fp_modem", "MODEM", "This lights up if the wireless/ender modem is connected. In parentheses is the unique computer ID of this device, which will show up in places such as the Supervisor's connection lists.")
|
||||||
doc("fp_modem", "NETWORK", "This is present when in standard color modes and indicates the network status using multiple colors.")
|
doc("fp_modem", "NETWORK", "This is present when in standard color modes and indicates the network status using multiple colors.")
|
||||||
list(DOC_LIST_TYPE.LED, { "not linked", "linked", "link denied", "bad comms version", "duplicate PLC" }, { colors.gray, colors.green, colors.red, colors.orange, colors.yellow })
|
list(DOC_LIST_TYPE.LED, { "not linked", "linked", "link denied", "bad comms version", "duplicate PLC" }, { colors.gray, colors.green, colors.red, colors.orange, colors.yellow })
|
||||||
text("You can fix \"bad comms version\" by ensuring all devices are up-to-date, as this indicates a communications protocol version mismatch. Note that yellow is Reactor PLC-specific, indicating duplicate unit IDs in use.")
|
text("You can fix \"bad comms version\" by ensuring all devices are up-to-date, as this indicates a communications protocol version mismatch. Note that yellow is Reactor PLC-specific, indicating duplicate unit IDs in use.")
|
||||||
doc("fp_nt_linked", "NT LINKED", "(color accessibility modes only)", "This indicates the device is linked to the supervisor.")
|
doc("fp_nt_linked", "NT LINKED", "(color accessibility modes only)", "This indicates the device is linked to the Supervisor.")
|
||||||
doc("fp_nt_version", "NT VERSION", "(color accessibility modes only)", "This indicates the communications versions of the supervisor and this device do not match. Make sure everything is up-to-date.")
|
doc("fp_nt_version", "NT VERSION", "(color accessibility modes only)", "This indicates the communications versions of the Supervisor and this device do not match. Make sure everything is up-to-date.")
|
||||||
sect("Versions")
|
sect("Versions")
|
||||||
doc("fp_fw", "FW", "Firmware application version of this device.")
|
doc("fp_fw", "FW", "Firmware application version of this device.")
|
||||||
doc("fp_nt", "NT", "Network (comms) version this device has. These must match between devices in order for them to connect.")
|
doc("fp_nt", "NT", "Network (comms) version this device has. These must match between devices in order for them to connect.")
|
||||||
|
|
||||||
target = docs.fp.r_plc
|
target = docs.fp.r_plc
|
||||||
|
sect("Overview")
|
||||||
|
text("Documentation for Reactor PLC-specific front panel items are below. Refer to 'Common Items' for the items not covered in this section.")
|
||||||
sect("Core Status")
|
sect("Core Status")
|
||||||
doc("fp_status", "STATUS", "This is green once the PLC is initialized and OK (has all its peripherals) and red if something is wrong, in which case you should refer to the other indicator lights (REACTOR & MODEM).")
|
doc("fp_status", "STATUS", "This is green once the PLC is initialized and OK (has all its peripherals) and red if something is wrong, in which case you should refer to the other indicator lights (REACTOR & MODEM).")
|
||||||
sect("Hardware & Network")
|
sect("Hardware & Network")
|
||||||
@@ -194,8 +458,8 @@ doc("fp_emer_cool", "EMER COOLANT", "This is only present if PLC-controlled emer
|
|||||||
doc("fp_rps_trip", "RPS TRIP", "Flashes when the RPS has SCRAM'd the reactor due to a safety trip.")
|
doc("fp_rps_trip", "RPS TRIP", "Flashes when the RPS has SCRAM'd the reactor due to a safety trip.")
|
||||||
sect("RPS Conditions")
|
sect("RPS Conditions")
|
||||||
doc("fp_rps_man", "MANUAL", "The RPS was tripped manually (SCRAM by user, not via the Mekanism Reactor UI).")
|
doc("fp_rps_man", "MANUAL", "The RPS was tripped manually (SCRAM by user, not via the Mekanism Reactor UI).")
|
||||||
doc("fp_rps_auto", "AUTOMATIC", "The RPS was tripped by the supervisor automatically.")
|
doc("fp_rps_auto", "AUTOMATIC", "The RPS was tripped by the Supervisor automatically.")
|
||||||
doc("fp_rps_to", "TIMEOUT", "The RPS tripped due to losing the supervisor connection.")
|
doc("fp_rps_to", "TIMEOUT", "The RPS tripped due to losing the Supervisor connection.")
|
||||||
doc("fp_rps_pflt", "PLC FAULT", "The RPS tripped due to a peripheral error.")
|
doc("fp_rps_pflt", "PLC FAULT", "The RPS tripped due to a peripheral error.")
|
||||||
doc("fp_rps_rflt", "RCT FAULT", "The RPS tripped due to the reactor not being formed.")
|
doc("fp_rps_rflt", "RCT FAULT", "The RPS tripped due to the reactor not being formed.")
|
||||||
doc("fp_rps_temp", "HI DAMAGE", "The RPS tripped due to being >=" .. const.RPS_LIMITS.MAX_DAMAGE_PERCENT .. "% damaged.")
|
doc("fp_rps_temp", "HI DAMAGE", "The RPS tripped due to being >=" .. const.RPS_LIMITS.MAX_DAMAGE_PERCENT .. "% damaged.")
|
||||||
@@ -206,6 +470,9 @@ doc("fp_rps_ccool", "LO CCOOLANT", "The RPS tripped due to having low levels of
|
|||||||
doc("fp_rps_ccool", "HI HCOOLANT", "The RPS tripped due to having high levels of heated coolant (>" .. (const.RPS_LIMITS.MAX_HEATED_COLLANT_FILL * 100) .. "%).")
|
doc("fp_rps_ccool", "HI HCOOLANT", "The RPS tripped due to having high levels of heated coolant (>" .. (const.RPS_LIMITS.MAX_HEATED_COLLANT_FILL * 100) .. "%).")
|
||||||
|
|
||||||
target = docs.fp.rtu_gw
|
target = docs.fp.rtu_gw
|
||||||
|
sect("Overview")
|
||||||
|
text("Documentation for RTU Gateway-specific front panel items are below. Refer to 'Common Items' for the items not covered in this section.")
|
||||||
|
doc("fp_rtu_spkr", "SPEAKERS", "This is the count of speaker peripherals connected to this RTU Gateway.")
|
||||||
sect("Co-Routine States")
|
sect("Co-Routine States")
|
||||||
doc("fp_rtu_rt_main", "RT MAIN", "This indicates if the device's main loop co-routine is running.")
|
doc("fp_rtu_rt_main", "RT MAIN", "This indicates if the device's main loop co-routine is running.")
|
||||||
doc("fp_rtu_rt_comms", "RT COMMS", "This indicates if the communications handler co-routine is running.")
|
doc("fp_rtu_rt_comms", "RT COMMS", "This indicates if the communications handler co-routine is running.")
|
||||||
@@ -218,30 +485,49 @@ doc("fp_rtu_rt", "Device Assignment", "In each RTU entry row, the device identif
|
|||||||
|
|
||||||
target = docs.fp.supervisor
|
target = docs.fp.supervisor
|
||||||
sect("Round Trip Times")
|
sect("Round Trip Times")
|
||||||
doc("fp_sv_fw", "RTT", "Each connection has a round trip time, or RTT. Since the supervisor updates at a rate of 150ms, RTTs from ~150ms to ~300ms are typical. Higher RTTs indicate lag, and if they end up in the thousands there will be performance problems.")
|
doc("fp_sv_rtt", "RTT", "Each connection has a round trip time, or RTT. Since the Supervisor updates at a rate of 150ms, RTTs from ~150ms to ~300ms are typical. Higher RTTs indicate lag, and if they end up in the thousands there will be performance problems.")
|
||||||
list(DOC_LIST_TYPE.BULLET, { "green: <=300ms", "yellow: <=500ms ", "red: >500ms" })
|
list(DOC_LIST_TYPE.BULLET, { "green: <=300ms", "yellow: <=500ms ", "red: >500ms" })
|
||||||
sect("SVR Tab")
|
sect("SVR Tab")
|
||||||
text("This tab includes information about the supervisor, covered by 'Common Items'.")
|
text("This tab includes information about the Supervisor, covered by 'Common Items'.")
|
||||||
sect("PLC Tab")
|
sect("PLC Tab")
|
||||||
text("This tab lists the expected PLC connections based on the number of configured units. Status information about each connection is shown when linked.")
|
text("This tab lists the expected PLC connections based on the number of configured units. Status information about each connection is shown when linked.")
|
||||||
doc("fp_sv_link", "LINK", "This indicates if the reactor PLC is linked.")
|
doc("fp_sv_link", "LINK", "This indicates if the Reactor PLC is linked.")
|
||||||
doc("fp_sv_p_cmpid", "PLC Computer ID", "This shows the computer ID of the reactor PLC, or --- if disconnected.")
|
doc("fp_sv_p_cmpid", "PLC Computer ID", "This shows the computer ID of the Reactor PLC, or --- if disconnected.")
|
||||||
doc("fp_sv_p_fw", "PLC FW", "This shows the firmware version of the reactor PLC.")
|
doc("fp_sv_p_fw", "PLC FW", "This shows the firmware version of the Reactor PLC.")
|
||||||
sect("RTU Tab")
|
sect("RTU Tab")
|
||||||
text("As RTU gateways connect to the supervisor, they will show up here along with some information.")
|
text("As RTU gateways connect to the Supervisor, they will show up here along with some information.")
|
||||||
doc("fp_sv_r_cmpid", "RTU Computer ID", "At the start of the entry is an @ sign followed by the computer ID of the RTU gateway.")
|
doc("fp_sv_r_cmpid", "RTU Computer ID", "At the start of the entry is an @ sign followed by the computer ID of the RTU Gateway.")
|
||||||
doc("fp_sv_r_units", "UNITS", "This is a count of the number of RTUs configured on the RTU gateway (each line on the RTU gateway's front panel).")
|
doc("fp_sv_r_units", "UNITS", "This is a count of the number of RTUs configured on the RTU Gateway (each line on the RTU Gateway's front panel).")
|
||||||
doc("fp_sv_r_fw", "RTU FW", "This shows the firmware version of the RTU gateway.")
|
doc("fp_sv_r_fw", "RTU FW", "This shows the firmware version of the RTU Gateway.")
|
||||||
sect("PKT Tab")
|
sect("PKT Tab")
|
||||||
text("As pocket computers connect to the supervisor, they will show up here along with some information. The properties listed are the same as with RTU gateways (except for UNITS), so they will not be further described here.")
|
text("As pocket computers connect to the Supervisor, they will show up here along with some information. The properties listed are the same as with RTU gateways (except for UNITS), so they will not be further described here.")
|
||||||
sect("DEV Tab")
|
sect("DEV Tab")
|
||||||
text("If nothing is connected, this will list all the expected RTU devices that aren't found. This page should be blank if everything is connected and configured correctly. If not, it will list certain types of detectable problems.")
|
text("If nothing is connected, this will list all the expected RTU devices that aren't found. This page should be blank if everything is connected and configured correctly. If not, it will list certain types of detectable problems.")
|
||||||
doc("fp_sv_d_miss", "MISSING", "These items list missing devices, with the details that should be used in the RTU's configuration.")
|
doc("fp_sv_d_miss", "MISSING", "These items list missing devices, with the details that should be used in the RTU's configuration.")
|
||||||
doc("fp_sv_d_oor", "BAD INDEX", "If you have a configuration entry that has an index outside of the maximum number of devices configured on the supervisor, this will show up indicating what entry is incorrect. For example, if you specified a unit has 2 turbines and a #3 connected, it would show up here as out of range.")
|
doc("fp_sv_d_oor", "BAD INDEX", "If you have a configuration entry that has an index outside of the maximum number of devices configured on the Supervisor, this will show up indicating what entry is incorrect. For example, if you specified a unit has 2 turbines and a #3 connected, it would show up here as out of range.")
|
||||||
doc("fp_sv_d_dupe", "DUPLICATE", "If a device tries to connect that is configured the same as another, it will be rejected and show up here. If you try to connect two #1 turbines for a unit, that would fail and one would appear here.")
|
doc("fp_sv_d_dupe", "DUPLICATE", "If a device tries to connect that is configured the same as another, it will be rejected and show up here. If you try to connect two #1 turbines for a unit, that would fail and one would appear here.")
|
||||||
sect("INF Tab")
|
sect("INF Tab")
|
||||||
text("This tab gives information about the other tabs, along with extra details on the DEV tab.")
|
text("This tab gives information about the other tabs, along with extra details on the DEV tab.")
|
||||||
|
|
||||||
|
target = docs.fp.coordinator
|
||||||
|
sect("Round Trip Times")
|
||||||
|
doc("fp_crd_rtt", "RTT", "Each connection has a round trip time, or RTT. Since the Coordinator updates at a rate of 500ms, RTTs ~500ms - ~1000ms are typical. Higher RTTs indicate lag, which results in performance problems.")
|
||||||
|
list(DOC_LIST_TYPE.BULLET, { "green: <=1000ms", "yellow: <=1500ms ", "red: >1500ms" })
|
||||||
|
sect("CRD Tab")
|
||||||
|
text("This tab includes information about the Coordinator, partially covered by 'Common Items'.")
|
||||||
|
doc("fp_crd_spkr", "SPEAKER", "This indicates if the speaker is connected.")
|
||||||
|
doc("fp_crd_rt_main", "RT MAIN", "This indicates that the device's main loop co-routine is running.")
|
||||||
|
doc("fp_crd_rt_render", "RT RENDER", "This indicates that the Coordinator graphics renderer co-routine is running.")
|
||||||
|
doc("fp_crd_mon_main", "MAIN MONITOR", "The connection status of the main display monitor.")
|
||||||
|
doc("fp_crd_mon_flow", "FLOW MONITOR", "The connection status of the coolant and waste flow display monitor.")
|
||||||
|
doc("fp_crd_mon_unit", "UNIT X MONITOR", "The connection status of the monitor associated with a given unit.")
|
||||||
|
sect("API Tab")
|
||||||
|
text("This tab lists connected pocket computers. Refer to the Supervisor PKT tab documentation for details on fields.")
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
|
--#region Glossary
|
||||||
|
|
||||||
docs.glossary = {
|
docs.glossary = {
|
||||||
abbvs = {}, terms = {}
|
abbvs = {}, terms = {}
|
||||||
}
|
}
|
||||||
@@ -249,8 +535,7 @@ docs.glossary = {
|
|||||||
target = docs.glossary.abbvs
|
target = docs.glossary.abbvs
|
||||||
doc("G_ACK", "ACK", "Alarm ACKnowledge. Pressing this acknowledges that you understand an alarm occurred and would like to stop the audio tone(s).")
|
doc("G_ACK", "ACK", "Alarm ACKnowledge. Pressing this acknowledges that you understand an alarm occurred and would like to stop the audio tone(s).")
|
||||||
doc("G_Auto", "Auto", "Automatic.")
|
doc("G_Auto", "Auto", "Automatic.")
|
||||||
doc("G_CRD", "CRD", "Coordinator. Abbreviation for the coordinator computer.")
|
doc("G_CRD", "CRD", "Coordinator. Abbreviation for the Coordinator computer.")
|
||||||
doc("G_DBG", "DBG", "Debug. Abbreviation for the debugging sessions from pocket computers found on the supervisor's front panel.")
|
|
||||||
doc("G_FP", "FP", "Front Panel. See Terminology.")
|
doc("G_FP", "FP", "Front Panel. See Terminology.")
|
||||||
doc("G_Hi", "Hi", "High.")
|
doc("G_Hi", "Hi", "High.")
|
||||||
doc("G_Lo", "Lo", "Low.")
|
doc("G_Lo", "Lo", "Low.")
|
||||||
@@ -260,7 +545,7 @@ doc("G_PLC", "PLC", "Programmable Logic Controller. A device that not only repor
|
|||||||
doc("G_PPM", "PPM", "Protected Peripheral Manager. This is an abstraction layer created for this project that prevents peripheral calls from crashing applications.")
|
doc("G_PPM", "PPM", "Protected Peripheral Manager. This is an abstraction layer created for this project that prevents peripheral calls from crashing applications.")
|
||||||
doc("G_RCP", "RCP", "Reactor Coolant Pump. This is from real-world terminology with water-cooled (boiling water and pressurized water) reactors, but in this system it just reflects to the functioning of reactor coolant flow. See the annunciator page on it for more information.")
|
doc("G_RCP", "RCP", "Reactor Coolant Pump. This is from real-world terminology with water-cooled (boiling water and pressurized water) reactors, but in this system it just reflects to the functioning of reactor coolant flow. See the annunciator page on it for more information.")
|
||||||
doc("G_RCS", "RCS", "Reactor Cooling System. The combination of all machines used to cool the reactor (turbines, boilers, dynamic tanks).")
|
doc("G_RCS", "RCS", "Reactor Cooling System. The combination of all machines used to cool the reactor (turbines, boilers, dynamic tanks).")
|
||||||
doc("G_RPS", "RPS", "Reactor Protection System. A component of the reactor PLC responsible for keeping the reactor safe.")
|
doc("G_RPS", "RPS", "Reactor Protection System. A component of the Reactor PLC responsible for keeping the reactor safe.")
|
||||||
doc("G_RTU", "RT", "co-RouTine. This is used to identify the status of core Lua co-routines on front panels.")
|
doc("G_RTU", "RT", "co-RouTine. This is used to identify the status of core Lua co-routines on front panels.")
|
||||||
doc("G_RTU", "RTU", "Remote Terminal Unit. Provides monitoring to and basic output from a SCADA system, interfacing with various types of devices/interfaces.")
|
doc("G_RTU", "RTU", "Remote Terminal Unit. Provides monitoring to and basic output from a SCADA system, interfacing with various types of devices/interfaces.")
|
||||||
doc("G_SCADA", "SCADA", "Supervisory Control and Data Acquisition. A control systems architecture used in a wide variety process control applications.")
|
doc("G_SCADA", "SCADA", "Supervisory Control and Data Acquisition. A control systems architecture used in a wide variety process control applications.")
|
||||||
@@ -269,6 +554,8 @@ doc("G_UI", "UI", "User Interface.")
|
|||||||
|
|
||||||
target = docs.glossary.terms
|
target = docs.glossary.terms
|
||||||
doc("G_AssignedUnit", "Assigned Unit", "A unit that is assigned to an automatic control group (not assigned to Manual).")
|
doc("G_AssignedUnit", "Assigned Unit", "A unit that is assigned to an automatic control group (not assigned to Manual).")
|
||||||
|
doc("G_AuxCoolant", "Auxiliary Coolant", "A separate water input to the reactor or boiler to supplement return water from a turbine during initial ramp-up.")
|
||||||
|
doc("G_EmerCoolant", "Emergency Coolant", "A dynamic tank or other water supply used when a reactor or boiler does not have enough water to stop a runaway reactor overheat.")
|
||||||
doc("G_Fault", "Fault", "Something has gone wrong and/or failed to function.")
|
doc("G_Fault", "Fault", "Something has gone wrong and/or failed to function.")
|
||||||
doc("G_FrontPanel", "Front Panel", "A basic interface on the front of a device for viewing and sometimes modifying its state. This is what you see when looking at a computer running one of the SCADA applications.")
|
doc("G_FrontPanel", "Front Panel", "A basic interface on the front of a device for viewing and sometimes modifying its state. This is what you see when looking at a computer running one of the SCADA applications.")
|
||||||
doc("G_HighHigh", "High High", "Very High.")
|
doc("G_HighHigh", "High High", "Very High.")
|
||||||
@@ -282,4 +569,6 @@ doc("G_Tripped", "Tripped", "An alarm condition has been met, and is still met."
|
|||||||
doc("G_Tripping", "Tripping", "Alarm condition(s) is/are met, but has/have not reached the minimum time before the condition(s) is/are deemed a problem.")
|
doc("G_Tripping", "Tripping", "Alarm condition(s) is/are met, but has/have not reached the minimum time before the condition(s) is/are deemed a problem.")
|
||||||
doc("G_TurbineTrip", "Turbine Trip", "The turbine stopped, which prevents heated coolant from being cooled. In Mekanism, this would occur when a turbine cannot generate any more energy due to filling its buffer and having no output with any remaining energy capacity.")
|
doc("G_TurbineTrip", "Turbine Trip", "The turbine stopped, which prevents heated coolant from being cooled. In Mekanism, this would occur when a turbine cannot generate any more energy due to filling its buffer and having no output with any remaining energy capacity.")
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
return docs
|
return docs
|
||||||
|
|||||||
@@ -7,14 +7,15 @@ local util = require("scada-common.util")
|
|||||||
local iocontrol = require("pocket.iocontrol")
|
local iocontrol = require("pocket.iocontrol")
|
||||||
local pocket = require("pocket.pocket")
|
local pocket = require("pocket.pocket")
|
||||||
|
|
||||||
|
local about_app = require("pocket.ui.apps.about")
|
||||||
|
local alarm_app = require("pocket.ui.apps.alarm")
|
||||||
|
local comps_app = require("pocket.ui.apps.comps")
|
||||||
local control_app = require("pocket.ui.apps.control")
|
local control_app = require("pocket.ui.apps.control")
|
||||||
local diag_apps = require("pocket.ui.apps.diag_apps")
|
|
||||||
local dummy_app = require("pocket.ui.apps.dummy_app")
|
|
||||||
local facil_app = require("pocket.ui.apps.facility")
|
local facil_app = require("pocket.ui.apps.facility")
|
||||||
local guide_app = require("pocket.ui.apps.guide")
|
local guide_app = require("pocket.ui.apps.guide")
|
||||||
local loader_app = require("pocket.ui.apps.loader")
|
local loader_app = require("pocket.ui.apps.loader")
|
||||||
local process_app = require("pocket.ui.apps.process")
|
local process_app = require("pocket.ui.apps.process")
|
||||||
local sys_apps = require("pocket.ui.apps.sys_apps")
|
local rad_app = require("pocket.ui.apps.radiation")
|
||||||
local unit_app = require("pocket.ui.apps.unit")
|
local unit_app = require("pocket.ui.apps.unit")
|
||||||
local waste_app = require("pocket.ui.apps.waste")
|
local waste_app = require("pocket.ui.apps.waste")
|
||||||
|
|
||||||
@@ -71,10 +72,11 @@ local function init(main)
|
|||||||
process_app(page_div)
|
process_app(page_div)
|
||||||
waste_app(page_div)
|
waste_app(page_div)
|
||||||
guide_app(page_div)
|
guide_app(page_div)
|
||||||
|
rad_app(page_div)
|
||||||
loader_app(page_div)
|
loader_app(page_div)
|
||||||
sys_apps(page_div)
|
about_app(page_div)
|
||||||
diag_apps(page_div)
|
alarm_app(page_div)
|
||||||
dummy_app(page_div)
|
comps_app(page_div)
|
||||||
|
|
||||||
-- verify all apps were created
|
-- verify all apps were created
|
||||||
assert(util.table_len(db.nav.get_containers()) == APP_ID.NUM_APPS, "app IDs were not sequential or some apps weren't registered")
|
assert(util.table_len(db.nav.get_containers()) == APP_ID.NUM_APPS, "app IDs were not sequential or some apps weren't registered")
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
--
|
||||||
|
-- Dynamic Tank View
|
||||||
|
--
|
||||||
|
|
||||||
local types = require("scada-common.types")
|
local types = require("scada-common.types")
|
||||||
local util = require("scada-common.util")
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
@@ -16,7 +20,7 @@ local IconIndicator = require("graphics.elements.indicators.IconIndicator")
|
|||||||
local StateIndicator = require("graphics.elements.indicators.StateIndicator")
|
local StateIndicator = require("graphics.elements.indicators.StateIndicator")
|
||||||
|
|
||||||
local CONTAINER_MODE = types.CONTAINER_MODE
|
local CONTAINER_MODE = types.CONTAINER_MODE
|
||||||
local COOLANT_TYPE = types.COOLANT_TYPE
|
local COOLANT_TYPE = types.COOLANT_TYPE
|
||||||
|
|
||||||
local ALIGN = core.ALIGN
|
local ALIGN = core.ALIGN
|
||||||
local cpair = core.cpair
|
local cpair = core.cpair
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
--
|
||||||
|
-- Induction Matrix View
|
||||||
|
--
|
||||||
|
|
||||||
local iocontrol = require("pocket.iocontrol")
|
local iocontrol = require("pocket.iocontrol")
|
||||||
|
|
||||||
local style = require("pocket.ui.style")
|
local style = require("pocket.ui.style")
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
--
|
||||||
|
-- SPS View
|
||||||
|
--
|
||||||
|
|
||||||
local iocontrol = require("pocket.iocontrol")
|
local iocontrol = require("pocket.iocontrol")
|
||||||
|
|
||||||
local style = require("pocket.ui.style")
|
local style = require("pocket.ui.style")
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
--
|
||||||
|
-- A Guide App Subsection
|
||||||
|
--
|
||||||
|
|
||||||
local log = require("scada-common.log")
|
local log = require("scada-common.log")
|
||||||
local util = require("scada-common.util")
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
@@ -17,7 +21,7 @@ local LED = require("graphics.elements.indicators.LED")
|
|||||||
local ALIGN = core.ALIGN
|
local ALIGN = core.ALIGN
|
||||||
local cpair = core.cpair
|
local cpair = core.cpair
|
||||||
|
|
||||||
local DOC_TYPE = docs.DOC_ITEM_TYPE
|
local DOC_TYPE = docs.DOC_ITEM_TYPE
|
||||||
local LIST_TYPE = docs.DOC_LIST_TYPE
|
local LIST_TYPE = docs.DOC_LIST_TYPE
|
||||||
|
|
||||||
-- new guide documentation section
|
-- new guide documentation section
|
||||||
@@ -34,13 +38,13 @@ return function (data, base_page, title, items, scroll_height)
|
|||||||
local section_div = Div{parent=page_div,x=2}
|
local section_div = Div{parent=page_div,x=2}
|
||||||
table.insert(panes, section_div)
|
table.insert(panes, section_div)
|
||||||
TextBox{parent=section_div,y=1,text=title,alignment=ALIGN.CENTER}
|
TextBox{parent=section_div,y=1,text=title,alignment=ALIGN.CENTER}
|
||||||
PushButton{parent=section_div,x=3,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=base_page.nav_to}
|
PushButton{parent=section_div,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=base_page.nav_to}
|
||||||
|
|
||||||
local view_page = app.new_page(section_page, #panes + 1)
|
local view_page = app.new_page(section_page, #panes + 1)
|
||||||
local section_view_div = Div{parent=page_div,x=2}
|
local section_view_div = Div{parent=page_div,x=2}
|
||||||
table.insert(panes, section_view_div)
|
table.insert(panes, section_view_div)
|
||||||
TextBox{parent=section_view_div,y=1,text=title,alignment=ALIGN.CENTER}
|
TextBox{parent=section_view_div,y=1,text=title,alignment=ALIGN.CENTER}
|
||||||
PushButton{parent=section_view_div,x=3,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=section_page.nav_to}
|
PushButton{parent=section_view_div,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=section_page.nav_to}
|
||||||
|
|
||||||
local name_list = ListBox{parent=section_div,x=1,y=3,scroll_height=60,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)}
|
local name_list = ListBox{parent=section_div,x=1,y=3,scroll_height=60,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)}
|
||||||
local def_list = ListBox{parent=section_view_div,x=1,y=3,scroll_height=scroll_height,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)}
|
local def_list = ListBox{parent=section_view_div,x=1,y=3,scroll_height=scroll_height,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)}
|
||||||
@@ -49,7 +53,7 @@ return function (data, base_page, title, items, scroll_height)
|
|||||||
local page_end
|
local page_end
|
||||||
|
|
||||||
for i = 1, #items do
|
for i = 1, #items do
|
||||||
local item = items[i] ---@type pocket_doc_sect|pocket_doc_subsect|pocket_doc_text|pocket_doc_list
|
local item = items[i] ---@type pocket_doc_sect|pocket_doc_subsect|pocket_doc_text|pocket_doc_note|pocket_doc_tip|pocket_doc_list
|
||||||
|
|
||||||
if item.type == DOC_TYPE.SECTION then
|
if item.type == DOC_TYPE.SECTION then
|
||||||
---@cast item pocket_doc_sect
|
---@cast item pocket_doc_sect
|
||||||
@@ -73,6 +77,8 @@ return function (data, base_page, title, items, scroll_height)
|
|||||||
local _ = Div{parent=name_list,height=1}
|
local _ = Div{parent=name_list,height=1}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
table.insert(search_db, { string.lower(item.name), item.name, title, view })
|
||||||
|
|
||||||
local name_title = Div{parent=name_list,height=1}
|
local name_title = Div{parent=name_list,height=1}
|
||||||
TextBox{parent=name_title,x=1,text=title_text,fg_bg=cpair(colors.lightGray,colors.black)}
|
TextBox{parent=name_title,x=1,text=title_text,fg_bg=cpair(colors.lightGray,colors.black)}
|
||||||
PushButton{parent=name_title,x=title_offs,y=1,text=item.name,alignment=ALIGN.LEFT,fg_bg=cpair(colors.green,colors.black),active_fg_bg=btn_active,callback=view}
|
PushButton{parent=name_title,x=title_offs,y=1,text=item.name,alignment=ALIGN.LEFT,fg_bg=cpair(colors.green,colors.black),active_fg_bg=btn_active,callback=view}
|
||||||
@@ -108,6 +114,19 @@ return function (data, base_page, title, items, scroll_height)
|
|||||||
|
|
||||||
TextBox{parent=def_list,text=item.text}
|
TextBox{parent=def_list,text=item.text}
|
||||||
|
|
||||||
|
page_end = Div{parent=def_list,height=1,can_focus=true}
|
||||||
|
elseif item.type == DOC_TYPE.NOTE then
|
||||||
|
---@cast item pocket_doc_note
|
||||||
|
|
||||||
|
TextBox{parent=def_list,text=item.text,fg_bg=cpair(colors.gray,colors._INHERIT)}
|
||||||
|
|
||||||
|
page_end = Div{parent=def_list,height=1,can_focus=true}
|
||||||
|
elseif item.type == DOC_TYPE.TIP then
|
||||||
|
---@cast item pocket_doc_tip
|
||||||
|
|
||||||
|
TextBox{parent=def_list,text="TIP!",fg_bg=cpair(colors.orange,colors._INHERIT)}
|
||||||
|
TextBox{parent=def_list,text=item.text}
|
||||||
|
|
||||||
page_end = Div{parent=def_list,height=1,can_focus=true}
|
page_end = Div{parent=def_list,height=1,can_focus=true}
|
||||||
elseif item.type == DOC_TYPE.LIST then
|
elseif item.type == DOC_TYPE.LIST then
|
||||||
---@cast item pocket_doc_list
|
---@cast item pocket_doc_list
|
||||||
|
|||||||
@@ -9,11 +9,9 @@ local core = require("graphics.core")
|
|||||||
|
|
||||||
local AppMultiPane = require("graphics.elements.AppMultiPane")
|
local AppMultiPane = require("graphics.elements.AppMultiPane")
|
||||||
local Div = require("graphics.elements.Div")
|
local Div = require("graphics.elements.Div")
|
||||||
local TextBox = require("graphics.elements.TextBox")
|
|
||||||
|
|
||||||
local App = require("graphics.elements.controls.App")
|
local App = require("graphics.elements.controls.App")
|
||||||
|
|
||||||
local ALIGN = core.ALIGN
|
|
||||||
local cpair = core.cpair
|
local cpair = core.cpair
|
||||||
|
|
||||||
local APP_ID = pocket.APP_ID
|
local APP_ID = pocket.APP_ID
|
||||||
@@ -50,15 +48,12 @@ local function new_view(root)
|
|||||||
App{parent=apps_1,x=16,y=2,text="\x15",title="Control",callback=function()open(APP_ID.CONTROL)end,app_fg_bg=cpair(colors.black,colors.green),active_fg_bg=active_fg_bg}
|
App{parent=apps_1,x=16,y=2,text="\x15",title="Control",callback=function()open(APP_ID.CONTROL)end,app_fg_bg=cpair(colors.black,colors.green),active_fg_bg=active_fg_bg}
|
||||||
App{parent=apps_1,x=2,y=7,text="\x17",title="Process",callback=function()open(APP_ID.PROCESS)end,app_fg_bg=cpair(colors.black,colors.purple),active_fg_bg=active_fg_bg}
|
App{parent=apps_1,x=2,y=7,text="\x17",title="Process",callback=function()open(APP_ID.PROCESS)end,app_fg_bg=cpair(colors.black,colors.purple),active_fg_bg=active_fg_bg}
|
||||||
App{parent=apps_1,x=9,y=7,text="\x7f",title="Waste",callback=function()open(APP_ID.WASTE)end,app_fg_bg=cpair(colors.black,colors.brown),active_fg_bg=active_fg_bg}
|
App{parent=apps_1,x=9,y=7,text="\x7f",title="Waste",callback=function()open(APP_ID.WASTE)end,app_fg_bg=cpair(colors.black,colors.brown),active_fg_bg=active_fg_bg}
|
||||||
App{parent=apps_1,x=16,y=7,text="\x08",title="Devices",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.lightGray),active_fg_bg=active_fg_bg}
|
App{parent=apps_1,x=16,y=7,text="\xb6",title="Guide",callback=function()open(APP_ID.GUIDE)end,app_fg_bg=cpair(colors.black,colors.cyan),active_fg_bg=active_fg_bg}
|
||||||
App{parent=apps_1,x=2,y=12,text="\xb6",title="Guide",callback=function()open(APP_ID.GUIDE)end,app_fg_bg=cpair(colors.black,colors.cyan),active_fg_bg=active_fg_bg}
|
App{parent=apps_1,x=2,y=12,text="?",title="About",callback=function()open(APP_ID.ABOUT)end,app_fg_bg=cpair(colors.black,colors.white),active_fg_bg=active_fg_bg}
|
||||||
App{parent=apps_1,x=9,y=12,text="?",title="About",callback=function()open(APP_ID.ABOUT)end,app_fg_bg=cpair(colors.black,colors.white),active_fg_bg=active_fg_bg}
|
|
||||||
|
|
||||||
TextBox{parent=apps_2,text="Diagnostic Apps",x=1,y=2,alignment=ALIGN.CENTER}
|
App{parent=apps_2,x=2,y=2,text="\x0f",title="Alarm",callback=function()open(APP_ID.ALARMS)end,app_fg_bg=cpair(colors.black,colors.red),active_fg_bg=active_fg_bg}
|
||||||
|
App{parent=apps_2,x=9,y=2,text="@",title="Comps",callback=function()open(APP_ID.COMPS)end,app_fg_bg=cpair(colors.black,colors.orange),active_fg_bg=active_fg_bg}
|
||||||
App{parent=apps_2,x=2,y=4,text="\x0f",title="Alarm",callback=function()open(APP_ID.ALARMS)end,app_fg_bg=cpair(colors.black,colors.red),active_fg_bg=active_fg_bg}
|
App{parent=apps_2,x=16,y=2,text="\x1e",title="Rad",callback=function()open(APP_ID.RADMON)end,app_fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=active_fg_bg}
|
||||||
App{parent=apps_2,x=9,y=4,text="\x1e",title="LoopT",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.cyan),active_fg_bg=active_fg_bg}
|
|
||||||
App{parent=apps_2,x=16,y=4,text="@",title="Comps",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.orange),active_fg_bg=active_fg_bg}
|
|
||||||
|
|
||||||
return main
|
return main
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
--
|
||||||
|
-- Unit Boiler View
|
||||||
|
--
|
||||||
|
|
||||||
local types = require("scada-common.types")
|
local types = require("scada-common.types")
|
||||||
local util = require("scada-common.util")
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
@@ -13,8 +17,8 @@ local TextBox = require("graphics.elements.TextBox")
|
|||||||
local PushButton = require("graphics.elements.controls.PushButton")
|
local PushButton = require("graphics.elements.controls.PushButton")
|
||||||
|
|
||||||
local DataIndicator = require("graphics.elements.indicators.DataIndicator")
|
local DataIndicator = require("graphics.elements.indicators.DataIndicator")
|
||||||
local StateIndicator = require("graphics.elements.indicators.StateIndicator")
|
|
||||||
local IconIndicator = require("graphics.elements.indicators.IconIndicator")
|
local IconIndicator = require("graphics.elements.indicators.IconIndicator")
|
||||||
|
local StateIndicator = require("graphics.elements.indicators.StateIndicator")
|
||||||
local VerticalBar = require("graphics.elements.indicators.VerticalBar")
|
local VerticalBar = require("graphics.elements.indicators.VerticalBar")
|
||||||
|
|
||||||
local ALIGN = core.ALIGN
|
local ALIGN = core.ALIGN
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
--
|
||||||
|
-- Unit Reactor View
|
||||||
|
--
|
||||||
|
|
||||||
local types = require("scada-common.types")
|
local types = require("scada-common.types")
|
||||||
local util = require("scada-common.util")
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
@@ -13,8 +17,8 @@ local TextBox = require("graphics.elements.TextBox")
|
|||||||
local PushButton = require("graphics.elements.controls.PushButton")
|
local PushButton = require("graphics.elements.controls.PushButton")
|
||||||
|
|
||||||
local DataIndicator = require("graphics.elements.indicators.DataIndicator")
|
local DataIndicator = require("graphics.elements.indicators.DataIndicator")
|
||||||
local StateIndicator = require("graphics.elements.indicators.StateIndicator")
|
|
||||||
local IconIndicator = require("graphics.elements.indicators.IconIndicator")
|
local IconIndicator = require("graphics.elements.indicators.IconIndicator")
|
||||||
|
local StateIndicator = require("graphics.elements.indicators.StateIndicator")
|
||||||
local VerticalBar = require("graphics.elements.indicators.VerticalBar")
|
local VerticalBar = require("graphics.elements.indicators.VerticalBar")
|
||||||
|
|
||||||
local ALIGN = core.ALIGN
|
local ALIGN = core.ALIGN
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
--
|
||||||
|
-- Unit Turbine View
|
||||||
|
--
|
||||||
|
|
||||||
local util = require("scada-common.util")
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
local iocontrol = require("pocket.iocontrol")
|
local iocontrol = require("pocket.iocontrol")
|
||||||
|
|||||||
@@ -2,12 +2,18 @@
|
|||||||
-- Graphics Style Options
|
-- Graphics Style Options
|
||||||
--
|
--
|
||||||
|
|
||||||
local core = require("graphics.core")
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
|
local core = require("graphics.core")
|
||||||
|
|
||||||
|
local pocket = require("pocket.pocket")
|
||||||
|
|
||||||
local style = {}
|
local style = {}
|
||||||
|
|
||||||
local cpair = core.cpair
|
local cpair = core.cpair
|
||||||
|
|
||||||
|
local config = pocket.config
|
||||||
|
|
||||||
-- GLOBAL --
|
-- GLOBAL --
|
||||||
|
|
||||||
style.root = cpair(colors.white, colors.black)
|
style.root = cpair(colors.white, colors.black)
|
||||||
@@ -171,22 +177,29 @@ style.sps = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
style.waste = {
|
-- get waste styling, which depends on the configuration
|
||||||
-- auto waste processing states
|
---@return { states: { color: color, text: string }, states_abbrv: { color: color, text: string }, options: string[], unit_opts: string[] }
|
||||||
states = {
|
function style.get_waste()
|
||||||
{ color = cpair(colors.black, colors.green), text = "PLUTONIUM" },
|
local pu_color = util.trinary(config.GreenPuPellet, colors.green, colors.cyan)
|
||||||
{ color = cpair(colors.black, colors.cyan), text = "POLONIUM" },
|
local po_color = util.trinary(config.GreenPuPellet, colors.cyan, colors.green)
|
||||||
{ color = cpair(colors.black, colors.purple), text = "ANTI MATTER" }
|
|
||||||
},
|
return {
|
||||||
states_abbrv = {
|
-- auto waste processing states
|
||||||
{ color = cpair(colors.black, colors.green), text = "Pu" },
|
states = {
|
||||||
{ color = cpair(colors.black, colors.cyan), text = "Po" },
|
{ color = cpair(colors.black, pu_color), text = "PLUTONIUM" },
|
||||||
{ color = cpair(colors.black, colors.purple), text = "AM" }
|
{ color = cpair(colors.black, po_color), text = "POLONIUM" },
|
||||||
},
|
{ color = cpair(colors.black, colors.purple), text = "ANTI MATTER" }
|
||||||
-- process radio button options
|
},
|
||||||
options = { "Plutonium", "Polonium", "Antimatter" },
|
states_abbrv = {
|
||||||
-- unit waste selection
|
{ color = cpair(colors.black, pu_color), text = "Pu" },
|
||||||
unit_opts = { "Auto", "Plutonium", "Polonium", "Antimatter" }
|
{ color = cpair(colors.black, po_color), text = "Po" },
|
||||||
}
|
{ color = cpair(colors.black, colors.purple), text = "AM" }
|
||||||
|
},
|
||||||
|
-- process radio button options
|
||||||
|
options = { "Plutonium", "Polonium", "Antimatter" },
|
||||||
|
-- unit waste selection
|
||||||
|
unit_opts = { "Auto", "Plutonium", "Polonium", "Antimatter" }
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
return style
|
return style
|
||||||
|
|||||||
324
reactor-plc/backplane.lua
Normal file
324
reactor-plc/backplane.lua
Normal file
@@ -0,0 +1,324 @@
|
|||||||
|
--
|
||||||
|
-- Reactor PLC System Core Peripheral Backplane
|
||||||
|
--
|
||||||
|
|
||||||
|
local log = require("scada-common.log")
|
||||||
|
local network = require("scada-common.network")
|
||||||
|
local ppm = require("scada-common.ppm")
|
||||||
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
|
local println = util.println
|
||||||
|
|
||||||
|
---@class plc_backplane
|
||||||
|
local backplane = {}
|
||||||
|
|
||||||
|
local _bp = {
|
||||||
|
smem = nil, ---@type plc_shared_memory
|
||||||
|
|
||||||
|
wlan_pref = true,
|
||||||
|
lan_iface = "",
|
||||||
|
|
||||||
|
act_nic = nil, ---@type nic
|
||||||
|
wd_nic = nil, ---@type nic|nil
|
||||||
|
wl_nic = nil ---@type nic|nil
|
||||||
|
}
|
||||||
|
|
||||||
|
-- initialize the system peripheral backplane<br>
|
||||||
|
---@param config plc_config
|
||||||
|
---@param __shared_memory plc_shared_memory
|
||||||
|
--- EVENT_CONSUMER: this function consumes events
|
||||||
|
function backplane.init(config, __shared_memory)
|
||||||
|
_bp.smem = __shared_memory
|
||||||
|
_bp.wlan_pref = config.PreferWireless
|
||||||
|
_bp.lan_iface = config.WiredModem
|
||||||
|
|
||||||
|
local plc_dev = __shared_memory.plc_dev
|
||||||
|
local plc_state = __shared_memory.plc_state
|
||||||
|
|
||||||
|
plc_state.degraded = false
|
||||||
|
|
||||||
|
-- Modem Init
|
||||||
|
|
||||||
|
if _bp.smem.networked then
|
||||||
|
-- init wired NIC
|
||||||
|
if type(_bp.lan_iface) == "string" then
|
||||||
|
local modem = ppm.get_modem(_bp.lan_iface)
|
||||||
|
local wd_nic = network.nic(modem)
|
||||||
|
|
||||||
|
log.info("BKPLN: WIRED PHY_" .. util.trinary(modem, "UP ", "DOWN ") .. _bp.lan_iface)
|
||||||
|
|
||||||
|
_bp.wd_nic = wd_nic
|
||||||
|
_bp.act_nic = wd_nic -- set this as active for now
|
||||||
|
|
||||||
|
wd_nic.closeAll()
|
||||||
|
wd_nic.open(config.PLC_Channel)
|
||||||
|
|
||||||
|
plc_state.wd_modem = wd_nic.is_connected()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- init wireless NIC(s)
|
||||||
|
if config.WirelessModem then
|
||||||
|
local modem, iface = ppm.get_wireless_modem()
|
||||||
|
local wl_nic = network.nic(modem)
|
||||||
|
|
||||||
|
log.info("BKPLN: WIRELESS PHY_" .. util.trinary(modem, "UP ", "DOWN") .. (iface or ""))
|
||||||
|
|
||||||
|
-- set this as active if connected or if both modems are disconnected and this is preferred
|
||||||
|
if (modem and _bp.wlan_pref) or not (_bp.act_nic and _bp.act_nic.is_connected()) then
|
||||||
|
_bp.act_nic = wl_nic
|
||||||
|
log.info("BKPLN: switched active to preferred wireless")
|
||||||
|
end
|
||||||
|
|
||||||
|
_bp.wl_nic = wl_nic
|
||||||
|
|
||||||
|
wl_nic.closeAll()
|
||||||
|
wl_nic.open(config.PLC_Channel)
|
||||||
|
|
||||||
|
plc_state.wl_modem = wl_nic.is_connected()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- comms modem is required if networked
|
||||||
|
if not (plc_state.wd_modem or plc_state.wl_modem) then
|
||||||
|
println("startup> no comms modem found")
|
||||||
|
log.warning("BKPLN: no comms modem on startup")
|
||||||
|
|
||||||
|
plc_state.degraded = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Reactor Init
|
||||||
|
|
||||||
|
---@diagnostic disable-next-line: assign-type-mismatch
|
||||||
|
plc_dev.reactor = ppm.get_fission_reactor()
|
||||||
|
plc_state.no_reactor = plc_dev.reactor == nil
|
||||||
|
|
||||||
|
-- we need a reactor, can at least do some things even if it isn't formed though
|
||||||
|
if plc_state.no_reactor then
|
||||||
|
log.info("BKPLN: REACTOR LINK_DOWN")
|
||||||
|
|
||||||
|
println("startup> fission reactor not found")
|
||||||
|
log.warning("BKPLN: no reactor on startup")
|
||||||
|
|
||||||
|
plc_state.degraded = true
|
||||||
|
plc_state.reactor_formed = false
|
||||||
|
|
||||||
|
-- mount a virtual peripheral to init the RPS with
|
||||||
|
local _, dev = ppm.mount_virtual()
|
||||||
|
plc_dev.reactor = dev
|
||||||
|
|
||||||
|
log.info("BKPLN: mounted virtual device as reactor")
|
||||||
|
else
|
||||||
|
log.info("BKPLN: REACTOR LINK_UP " .. ppm.get_iface(plc_dev.reactor))
|
||||||
|
|
||||||
|
if not plc_dev.reactor.isFormed() then
|
||||||
|
println("startup> fission reactor is not formed")
|
||||||
|
log.warning("BKPLN: reactor logic adapter detected, but reactor is not formed")
|
||||||
|
|
||||||
|
plc_state.degraded = true
|
||||||
|
plc_state.reactor_formed = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- get the active NIC
|
||||||
|
function backplane.active_nic() return _bp.act_nic end
|
||||||
|
|
||||||
|
-- handle a backplane peripheral attach
|
||||||
|
---@param iface string
|
||||||
|
---@param type string
|
||||||
|
---@param device table
|
||||||
|
---@param print_no_fp function
|
||||||
|
function backplane.attach(iface, type, device, print_no_fp)
|
||||||
|
local MQ__RPS_CMD = _bp.smem.q_types.MQ__RPS_CMD
|
||||||
|
|
||||||
|
local wl_nic, wd_nic = _bp.wl_nic, _bp.wd_nic
|
||||||
|
|
||||||
|
local networked = _bp.smem.networked
|
||||||
|
local state = _bp.smem.plc_state
|
||||||
|
local dev = _bp.smem.plc_dev
|
||||||
|
local sys = _bp.smem.plc_sys
|
||||||
|
|
||||||
|
if type ~= nil and device ~= nil then
|
||||||
|
if state.no_reactor and (type == "fissionReactorLogicAdapter") then
|
||||||
|
-- reconnected reactor
|
||||||
|
log.info("BKPLN: REACTOR LINK_UP " .. iface)
|
||||||
|
|
||||||
|
dev.reactor = device
|
||||||
|
state.no_reactor = false
|
||||||
|
|
||||||
|
print_no_fp("reactor connected")
|
||||||
|
log.info("BKPLN: reactor connected")
|
||||||
|
|
||||||
|
-- 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
|
||||||
|
state.reactor_formed = true
|
||||||
|
|
||||||
|
-- determine if we are still in a degraded state
|
||||||
|
if ((not networked) or (state.wd_modem or state.wl_modem)) and state.reactor_formed then
|
||||||
|
state.degraded = false
|
||||||
|
end
|
||||||
|
|
||||||
|
_bp.smem.q.mq_rps.push_command(MQ__RPS_CMD.SCRAM)
|
||||||
|
|
||||||
|
sys.rps.reconnect_reactor(dev.reactor)
|
||||||
|
if networked then
|
||||||
|
sys.plc_comms.reconnect_reactor(dev.reactor)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- partial reset of RPS, specific to becoming formed/reconnected
|
||||||
|
-- without this, auto control can't resume on chunk load
|
||||||
|
sys.rps.reset_reattach()
|
||||||
|
elseif networked and type == "modem" then
|
||||||
|
---@cast device Modem
|
||||||
|
|
||||||
|
local m_is_wl = device.isWireless()
|
||||||
|
|
||||||
|
log.info(util.c("BKPLN: ", util.trinary(m_is_wl, "WIRELESS", "WIRED"), " PHY_ATTACH ", iface))
|
||||||
|
|
||||||
|
if wd_nic and (_bp.lan_iface == iface) then
|
||||||
|
-- connect this as the wired NIC
|
||||||
|
wd_nic.connect(device)
|
||||||
|
|
||||||
|
log.info("BKPLN: WIRED PHY_UP " .. iface)
|
||||||
|
print_no_fp("wired comms modem connected")
|
||||||
|
|
||||||
|
state.wd_modem = true
|
||||||
|
|
||||||
|
if (_bp.act_nic ~= wd_nic) and not _bp.wlan_pref then
|
||||||
|
-- switch back to preferred wired
|
||||||
|
_bp.act_nic = wd_nic
|
||||||
|
|
||||||
|
sys.plc_comms.switch_nic(_bp.act_nic)
|
||||||
|
log.info("BKPLN: switched comms to wired modem (preferred)")
|
||||||
|
end
|
||||||
|
elseif wl_nic and (not wl_nic.is_connected()) and m_is_wl then
|
||||||
|
-- connect this as the wireless NIC
|
||||||
|
wl_nic.connect(device)
|
||||||
|
|
||||||
|
log.info("BKPLN: WIRELESS PHY_UP " .. iface)
|
||||||
|
print_no_fp("wireless comms modem connected")
|
||||||
|
|
||||||
|
state.wl_modem = true
|
||||||
|
|
||||||
|
if (_bp.act_nic ~= wl_nic) and _bp.wlan_pref then
|
||||||
|
-- switch back to preferred wireless
|
||||||
|
_bp.act_nic = wl_nic
|
||||||
|
|
||||||
|
sys.plc_comms.switch_nic(_bp.act_nic)
|
||||||
|
log.info("BKPLN: switched comms to wireless modem (preferred)")
|
||||||
|
end
|
||||||
|
elseif wl_nic and m_is_wl then
|
||||||
|
-- the wireless NIC already has a modem
|
||||||
|
device.closeAll()
|
||||||
|
|
||||||
|
print_no_fp("standby wireless modem connected")
|
||||||
|
log.info("BKPLN: standby wireless modem connected")
|
||||||
|
else
|
||||||
|
device.closeAll()
|
||||||
|
|
||||||
|
print_no_fp("unassigned modem connected")
|
||||||
|
log.warning("BKPLN: unassigned modem connected")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- determine if we are still in a degraded state
|
||||||
|
if (state.wd_modem or state.wl_modem) and state.reactor_formed and not state.no_reactor then
|
||||||
|
state.degraded = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- handle a backplane peripheral detach
|
||||||
|
---@param iface string
|
||||||
|
---@param type string
|
||||||
|
---@param device table
|
||||||
|
---@param print_no_fp function
|
||||||
|
function backplane.detach(iface, type, device, print_no_fp)
|
||||||
|
local MQ__RPS_CMD = _bp.smem.q_types.MQ__RPS_CMD
|
||||||
|
|
||||||
|
local wl_nic, wd_nic = _bp.wl_nic, _bp.wd_nic
|
||||||
|
|
||||||
|
local state = _bp.smem.plc_state
|
||||||
|
local dev = _bp.smem.plc_dev
|
||||||
|
local sys = _bp.smem.plc_sys
|
||||||
|
|
||||||
|
if device == dev.reactor then
|
||||||
|
log.info("BKPLN: REACTOR LINK_DOWN " .. iface)
|
||||||
|
|
||||||
|
print_no_fp("reactor disconnected")
|
||||||
|
log.warning("BKPLN: reactor disconnected")
|
||||||
|
|
||||||
|
state.no_reactor = true
|
||||||
|
state.degraded = true
|
||||||
|
elseif _bp.smem.networked and type == "modem" then
|
||||||
|
---@cast device Modem
|
||||||
|
|
||||||
|
log.info(util.c("BKPLN: PHY_DETACH ", iface))
|
||||||
|
|
||||||
|
if wd_nic and wd_nic.is_modem(device) then
|
||||||
|
wd_nic.disconnect()
|
||||||
|
log.info("BKPLN: WIRED PHY_DOWN " .. iface)
|
||||||
|
|
||||||
|
state.wd_modem = false
|
||||||
|
elseif wl_nic and wl_nic.is_modem(device) then
|
||||||
|
wl_nic.disconnect()
|
||||||
|
log.info("BKPLN: WIRELESS PHY_DOWN " .. iface)
|
||||||
|
|
||||||
|
state.wl_modem = false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- we only care if this is our active comms modem
|
||||||
|
if _bp.act_nic.is_modem(device) then
|
||||||
|
print_no_fp("active comms modem disconnected")
|
||||||
|
log.warning("BKPLN: active comms modem disconnected")
|
||||||
|
|
||||||
|
-- failover and try to find a new comms modem
|
||||||
|
if _bp.act_nic == wl_nic then
|
||||||
|
-- wireless active disconnected
|
||||||
|
-- try to find another wireless modem, otherwise switch to wired
|
||||||
|
local modem, m_iface = ppm.get_wireless_modem()
|
||||||
|
if wl_nic and modem then
|
||||||
|
log.info("BKPLN: found another wireless modem, using it for comms")
|
||||||
|
|
||||||
|
wl_nic.connect(modem)
|
||||||
|
|
||||||
|
log.info("BKPLN: WIRELESS PHY_UP " .. m_iface)
|
||||||
|
|
||||||
|
state.wl_modem = true
|
||||||
|
elseif wd_nic and wd_nic.is_connected() then
|
||||||
|
_bp.act_nic = wd_nic
|
||||||
|
|
||||||
|
sys.plc_comms.switch_nic(_bp.act_nic)
|
||||||
|
log.info("BKPLN: switched comms to wired modem")
|
||||||
|
else
|
||||||
|
-- no other wireless modems, wired unavailable
|
||||||
|
state.degraded = true
|
||||||
|
_bp.smem.q.mq_rps.push_command(MQ__RPS_CMD.DEGRADED_SCRAM)
|
||||||
|
end
|
||||||
|
elseif wl_nic and wl_nic.is_connected() then
|
||||||
|
-- wired active disconnected, wireless available
|
||||||
|
_bp.act_nic = wl_nic
|
||||||
|
|
||||||
|
sys.plc_comms.switch_nic(_bp.act_nic)
|
||||||
|
log.info("BKPLN: switched comms to wireless modem")
|
||||||
|
else
|
||||||
|
-- wired active disconnected, wireless unavailable
|
||||||
|
state.degraded = true
|
||||||
|
_bp.smem.q.mq_rps.push_command(MQ__RPS_CMD.DEGRADED_SCRAM)
|
||||||
|
end
|
||||||
|
elseif wd_nic and wd_nic.is_modem(device) then
|
||||||
|
-- wired, but not active
|
||||||
|
print_no_fp("standby wired modem disconnected")
|
||||||
|
log.info("BKPLN: standby wired modem disconnected")
|
||||||
|
elseif wl_nic and wl_nic.is_modem(device) then
|
||||||
|
-- wireless, but not active
|
||||||
|
print_no_fp("standby wireless modem disconnected")
|
||||||
|
log.info("BKPLN: standby wireless modem disconnected")
|
||||||
|
else
|
||||||
|
print_no_fp("unassigned modem disconnected")
|
||||||
|
log.warning("BKPLN: unassigned modem disconnected")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return backplane
|
||||||
@@ -24,10 +24,12 @@ local ESTABLISH_ACK = comms.ESTABLISH_ACK
|
|||||||
local MGMT_TYPE = comms.MGMT_TYPE
|
local MGMT_TYPE = comms.MGMT_TYPE
|
||||||
|
|
||||||
local self = {
|
local self = {
|
||||||
|
checking_wl = true,
|
||||||
|
wd_modem = nil, ---@type Modem|nil
|
||||||
|
wl_modem = nil, ---@type Modem|nil
|
||||||
|
|
||||||
nic = nil, ---@type nic
|
nic = nil, ---@type nic
|
||||||
net_listen = false,
|
net_listen = false,
|
||||||
sv_addr = comms.BROADCAST,
|
|
||||||
sv_seq_num = util.time_ms() * 10,
|
|
||||||
|
|
||||||
self_check_pass = true,
|
self_check_pass = true,
|
||||||
|
|
||||||
@@ -48,7 +50,7 @@ local function check_complete()
|
|||||||
TextBox{parent=more,text="- ask for help on GitHub discussions or Discord"}
|
TextBox{parent=more,text="- ask for help on GitHub discussions or Discord"}
|
||||||
end
|
end
|
||||||
|
|
||||||
-- send a management packet to the supervisor
|
-- send a management packet to the supervisor (one-time broadcast)
|
||||||
---@param msg_type MGMT_TYPE
|
---@param msg_type MGMT_TYPE
|
||||||
---@param msg table
|
---@param msg table
|
||||||
local function send_sv(msg_type, msg)
|
local function send_sv(msg_type, msg)
|
||||||
@@ -56,10 +58,9 @@ local function send_sv(msg_type, msg)
|
|||||||
local pkt = comms.mgmt_packet()
|
local pkt = comms.mgmt_packet()
|
||||||
|
|
||||||
pkt.make(msg_type, msg)
|
pkt.make(msg_type, msg)
|
||||||
s_pkt.make(self.sv_addr, self.sv_seq_num, PROTOCOL.SCADA_MGMT, pkt.raw_sendable())
|
s_pkt.make(comms.BROADCAST, util.time_ms() * 10, PROTOCOL.SCADA_MGMT, pkt.raw_sendable())
|
||||||
|
|
||||||
self.nic.transmit(self.settings.SVR_Channel, self.settings.PLC_Channel, s_pkt)
|
self.nic.transmit(self.settings.SVR_Channel, self.settings.PLC_Channel, s_pkt)
|
||||||
self.sv_seq_num = self.sv_seq_num + 1
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- handle an establish message from the supervisor
|
-- handle an establish message from the supervisor
|
||||||
@@ -75,16 +76,13 @@ local function handle_packet(packet)
|
|||||||
local est_ack = packet.data[1]
|
local est_ack = packet.data[1]
|
||||||
|
|
||||||
if est_ack== ESTABLISH_ACK.ALLOW then
|
if est_ack== ESTABLISH_ACK.ALLOW then
|
||||||
self.self_check_msg(nil, true, "")
|
-- success
|
||||||
self.sv_addr = packet.scada_frame.src_addr()
|
|
||||||
send_sv(MGMT_TYPE.CLOSE, {})
|
|
||||||
if self.self_check_pass then check_complete() end
|
|
||||||
elseif est_ack == ESTABLISH_ACK.DENY then
|
elseif est_ack == ESTABLISH_ACK.DENY then
|
||||||
error_msg = "error: supervisor connection denied"
|
error_msg = "error: supervisor connection denied"
|
||||||
elseif est_ack == ESTABLISH_ACK.COLLISION then
|
elseif est_ack == ESTABLISH_ACK.COLLISION then
|
||||||
error_msg = "another reactor PLC is connected with this reactor unit ID"
|
error_msg = "another reactor PLC is connected with this reactor unit ID"
|
||||||
elseif est_ack == ESTABLISH_ACK.BAD_VERSION then
|
elseif est_ack == ESTABLISH_ACK.BAD_VERSION then
|
||||||
error_msg = "reactor PLC comms version does not match supervisor comms version, make sure both devices are up-to-date (ccmsi update ...)"
|
error_msg = "reactor PLC comms version does not match supervisor comms version, make sure both devices are up-to-date (ccmsi update)"
|
||||||
else
|
else
|
||||||
error_msg = "error: invalid reply from supervisor"
|
error_msg = "error: invalid reply from supervisor"
|
||||||
end
|
end
|
||||||
@@ -97,18 +95,20 @@ local function handle_packet(packet)
|
|||||||
end
|
end
|
||||||
|
|
||||||
self.net_listen = false
|
self.net_listen = false
|
||||||
self.run_test_btn.enable()
|
|
||||||
|
|
||||||
if error_msg then
|
if error_msg then
|
||||||
self.self_check_msg(nil, false, error_msg)
|
self.self_check_msg(nil, false, error_msg)
|
||||||
|
else
|
||||||
|
self.self_check_msg(nil, true, "")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
util.push_event("conn_test_complete", error_msg == nil)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- handle supervisor connection failure
|
-- handle supervisor connection failure
|
||||||
local function handle_timeout()
|
local function handle_timeout()
|
||||||
self.net_listen = false
|
self.net_listen = false
|
||||||
self.run_test_btn.enable()
|
util.push_event("conn_test_complete", false)
|
||||||
self.self_check_msg(nil, false, "make sure your supervisor is running, your channels are correct, trusted ranges are set properly (if enabled), facility keys match (if set), and if you are using wireless modems rather than ender modems, that your devices are close together in the same dimension")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- execute the self-check
|
-- execute the self-check
|
||||||
@@ -120,11 +120,23 @@ local function self_check()
|
|||||||
|
|
||||||
self.self_check_pass = true
|
self.self_check_pass = true
|
||||||
|
|
||||||
local modem = ppm.get_wireless_modem()
|
local cfg = self.settings
|
||||||
|
self.wd_modem = ppm.get_modem(cfg.WiredModem)
|
||||||
|
self.wl_modem = ppm.get_wireless_modem()
|
||||||
local reactor = ppm.get_fission_reactor()
|
local reactor = ppm.get_fission_reactor()
|
||||||
local valid_cfg = plc.validate_config(self.settings)
|
local valid_cfg = plc.validate_config(cfg)
|
||||||
|
|
||||||
|
-- check for comms modems
|
||||||
|
if cfg.Networked then
|
||||||
|
if cfg.WiredModem then
|
||||||
|
self.self_check_msg("> check wired comms modem connected...", self.wd_modem, "please connect the wired comms modem " .. cfg.WiredModem)
|
||||||
|
end
|
||||||
|
|
||||||
|
if cfg.WirelessModem then
|
||||||
|
self.self_check_msg("> check wireless/ender modem connected...", self.wl_modem, "please connect an ender or wireless modem for wireless comms")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
self.self_check_msg("> check wireless/ender modem connected...", modem ~= nil, "you must connect an ender or wireless modem to the reactor PLC")
|
|
||||||
self.self_check_msg("> check fission reactor connected...", reactor ~= nil, "please connect the reactor PLC to the reactor's fission reactor logic adapter")
|
self.self_check_msg("> check fission reactor connected...", reactor ~= nil, "please connect the reactor PLC to the reactor's fission reactor logic adapter")
|
||||||
self.self_check_msg("> check fission reactor formed...")
|
self.self_check_msg("> check fission reactor formed...")
|
||||||
-- this consumes events, but that is fine here
|
-- this consumes events, but that is fine here
|
||||||
@@ -132,27 +144,37 @@ local function self_check()
|
|||||||
|
|
||||||
self.self_check_msg("> check configuration...", valid_cfg, "go through Configure System and apply settings to set any missing settings and repair any corrupted ones")
|
self.self_check_msg("> check configuration...", valid_cfg, "go through Configure System and apply settings to set any missing settings and repair any corrupted ones")
|
||||||
|
|
||||||
if valid_cfg and modem then
|
if cfg.Networked and valid_cfg then
|
||||||
self.self_check_msg("> check supervisor connection...")
|
self.checking_wl = true
|
||||||
|
|
||||||
-- init mac as needed
|
if cfg.WirelessModem and self.wl_modem then
|
||||||
if self.settings.AuthKey and string.len(self.settings.AuthKey) >= 8 then
|
self.self_check_msg("> check wireless supervisor connection...")
|
||||||
network.init_mac(self.settings.AuthKey)
|
|
||||||
|
-- init mac as needed
|
||||||
|
if cfg.AuthKey and string.len(cfg.AuthKey) >= 8 then
|
||||||
|
network.init_mac(cfg.AuthKey)
|
||||||
|
else
|
||||||
|
network.deinit_mac()
|
||||||
|
end
|
||||||
|
|
||||||
|
comms.set_trusted_range(cfg.TrustedRange)
|
||||||
|
|
||||||
|
self.nic = network.nic(self.wl_modem)
|
||||||
|
|
||||||
|
self.nic.closeAll()
|
||||||
|
self.nic.open(cfg.PLC_Channel)
|
||||||
|
|
||||||
|
self.net_listen = true
|
||||||
|
|
||||||
|
send_sv(MGMT_TYPE.ESTABLISH, { comms.version, comms.CONN_TEST_FWV, DEVICE_TYPE.PLC, cfg.UnitID })
|
||||||
|
|
||||||
|
tcd.dispatch_unique(8, handle_timeout)
|
||||||
|
elseif cfg.WiredModem and self.wd_modem then
|
||||||
|
-- skip to wired
|
||||||
|
util.push_event("conn_test_complete", true)
|
||||||
else
|
else
|
||||||
network.deinit_mac()
|
self.self_check_msg("> no modem, can't test supervisor connection", false)
|
||||||
end
|
end
|
||||||
|
|
||||||
self.nic = network.nic(modem)
|
|
||||||
|
|
||||||
self.nic.closeAll()
|
|
||||||
self.nic.open(self.settings.PLC_Channel)
|
|
||||||
|
|
||||||
self.sv_addr = comms.BROADCAST
|
|
||||||
self.net_listen = true
|
|
||||||
|
|
||||||
send_sv(MGMT_TYPE.ESTABLISH, { comms.version, "0.0.0", DEVICE_TYPE.PLC, self.settings.UnitID })
|
|
||||||
|
|
||||||
tcd.dispatch_unique(8, handle_timeout)
|
|
||||||
else
|
else
|
||||||
if self.self_check_pass then check_complete() end
|
if self.self_check_pass then check_complete() end
|
||||||
self.run_test_btn.enable()
|
self.run_test_btn.enable()
|
||||||
@@ -236,4 +258,44 @@ function check.receive_sv(side, sender, reply_to, message, distance)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- handle completed connection tests
|
||||||
|
---@param pass boolean
|
||||||
|
function check.conn_test_callback(pass)
|
||||||
|
local cfg = self.settings
|
||||||
|
|
||||||
|
if self.checking_wl then
|
||||||
|
if not pass then
|
||||||
|
self.self_check_msg(nil, false, "make sure your supervisor is running, listening on the wireless interface, your channels are correct, trusted ranges are set properly (if enabled), facility keys match (if set), and if you are using wireless modems rather than ender modems, that your devices are close together in the same dimension")
|
||||||
|
end
|
||||||
|
|
||||||
|
if cfg.WiredModem and self.wd_modem then
|
||||||
|
self.checking_wl = false
|
||||||
|
self.self_check_msg("> check wired supervisor connection...")
|
||||||
|
|
||||||
|
comms.set_trusted_range(0)
|
||||||
|
|
||||||
|
self.nic = network.nic(self.wd_modem)
|
||||||
|
|
||||||
|
self.nic.closeAll()
|
||||||
|
self.nic.open(cfg.PLC_Channel)
|
||||||
|
|
||||||
|
self.net_listen = true
|
||||||
|
|
||||||
|
send_sv(MGMT_TYPE.ESTABLISH, { comms.version, comms.CONN_TEST_FWV, DEVICE_TYPE.PLC, cfg.UnitID })
|
||||||
|
|
||||||
|
tcd.dispatch_unique(8, handle_timeout)
|
||||||
|
else
|
||||||
|
if self.self_check_pass then check_complete() end
|
||||||
|
self.run_test_btn.enable()
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if not pass then
|
||||||
|
self.self_check_msg(nil, false, "make sure your supervisor is running, listening on the wired interface, the wire is intact, and your channels are correct")
|
||||||
|
end
|
||||||
|
|
||||||
|
if self.self_check_pass then check_complete() end
|
||||||
|
self.run_test_btn.enable()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
return check
|
return check
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
local log = require("scada-common.log")
|
local log = require("scada-common.log")
|
||||||
|
local ppm = require("scada-common.ppm")
|
||||||
local rsio = require("scada-common.rsio")
|
local rsio = require("scada-common.rsio")
|
||||||
local util = require("scada-common.util")
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
@@ -20,6 +21,8 @@ local TextField = require("graphics.elements.form.TextField")
|
|||||||
|
|
||||||
local IndLight = require("graphics.elements.indicators.IndicatorLight")
|
local IndLight = require("graphics.elements.indicators.IndicatorLight")
|
||||||
|
|
||||||
|
local tri = util.trinary
|
||||||
|
|
||||||
local cpair = core.cpair
|
local cpair = core.cpair
|
||||||
|
|
||||||
local RIGHT = core.ALIGN.RIGHT
|
local RIGHT = core.ALIGN.RIGHT
|
||||||
@@ -30,6 +33,10 @@ local self = {
|
|||||||
set_networked = nil, ---@type function
|
set_networked = nil, ---@type function
|
||||||
bundled_emcool = nil, ---@type function
|
bundled_emcool = nil, ---@type function
|
||||||
|
|
||||||
|
wireless = nil, ---@type Checkbox
|
||||||
|
wl_pref = nil, ---@type Checkbox
|
||||||
|
wired = nil, ---@type Checkbox
|
||||||
|
range = nil, ---@type NumberField
|
||||||
show_auth_key = nil, ---@type function
|
show_auth_key = nil, ---@type function
|
||||||
show_key_btn = nil, ---@type PushButton
|
show_key_btn = nil, ---@type PushButton
|
||||||
auth_key_textbox = nil, ---@type TextBox
|
auth_key_textbox = nil, ---@type TextBox
|
||||||
@@ -82,8 +89,9 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit)
|
|||||||
local plc_c_2 = Div{parent=plc_cfg,x=2,y=4,width=49}
|
local plc_c_2 = Div{parent=plc_cfg,x=2,y=4,width=49}
|
||||||
local plc_c_3 = Div{parent=plc_cfg,x=2,y=4,width=49}
|
local plc_c_3 = Div{parent=plc_cfg,x=2,y=4,width=49}
|
||||||
local plc_c_4 = Div{parent=plc_cfg,x=2,y=4,width=49}
|
local plc_c_4 = Div{parent=plc_cfg,x=2,y=4,width=49}
|
||||||
|
local plc_c_5 = Div{parent=plc_cfg,x=2,y=4,width=49}
|
||||||
|
|
||||||
local plc_pane = MultiPane{parent=plc_cfg,x=1,y=4,panes={plc_c_1,plc_c_2,plc_c_3,plc_c_4}}
|
local plc_pane = MultiPane{parent=plc_cfg,x=1,y=4,panes={plc_c_1,plc_c_2,plc_c_3,plc_c_4,plc_c_5}}
|
||||||
|
|
||||||
TextBox{parent=plc_cfg,x=1,y=2,text=" PLC Configuration",fg_bg=cpair(colors.black,colors.orange)}
|
TextBox{parent=plc_cfg,x=1,y=2,text=" PLC Configuration",fg_bg=cpair(colors.black,colors.orange)}
|
||||||
|
|
||||||
@@ -152,13 +160,20 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit)
|
|||||||
|
|
||||||
function self.bundled_emcool(en) if en then color.enable() else color.disable() end end
|
function self.bundled_emcool(en) if en then color.enable() else color.disable() end end
|
||||||
|
|
||||||
|
TextBox{parent=plc_c_5,x=1,y=1,height=5,text="Advanced Options"}
|
||||||
|
local invert = Checkbox{parent=plc_c_5,x=1,y=3,label="Invert",default=ini_cfg.EmerCoolInvert,box_fg_bg=cpair(colors.orange,colors.black)}
|
||||||
|
TextBox{parent=plc_c_5,x=3,y=4,height=4,text="Digital I/O is already inverted (or not) based on intended use. If you have a non-standard setup, you can use this option to avoid needing a redstone inverter.",fg_bg=cpair(colors.gray,colors.lightGray)}
|
||||||
|
PushButton{parent=plc_c_5,x=1,y=14,text="\x1b Back",callback=function()plc_pane.set_value(4)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
local function submit_emcool()
|
local function submit_emcool()
|
||||||
tmp_cfg.EmerCoolSide = side_options_map[side.get_value()]
|
tmp_cfg.EmerCoolSide = side_options_map[side.get_value()]
|
||||||
tmp_cfg.EmerCoolColor = util.trinary(bundled.get_value(), color_options_map[color.get_value()], nil)
|
tmp_cfg.EmerCoolColor = tri(bundled.get_value(), color_options_map[color.get_value()], nil)
|
||||||
|
tmp_cfg.EmerCoolInvert = invert.get_value()
|
||||||
next_from_plc()
|
next_from_plc()
|
||||||
end
|
end
|
||||||
|
|
||||||
PushButton{parent=plc_c_4,x=1,y=14,text="\x1b Back",callback=function()plc_pane.set_value(3)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=plc_c_4,x=1,y=14,text="\x1b Back",callback=function()plc_pane.set_value(3)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
PushButton{parent=plc_c_4,x=33,y=14,min_width=10,text="Advanced",callback=function()plc_pane.set_value(5)end,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
||||||
PushButton{parent=plc_c_4,x=44,y=14,text="Next \x1a",callback=submit_emcool,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=plc_c_4,x=44,y=14,text="Next \x1a",callback=submit_emcool,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
--#endregion
|
--#endregion
|
||||||
@@ -168,22 +183,88 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit)
|
|||||||
local net_c_1 = Div{parent=net_cfg,x=2,y=4,width=49}
|
local net_c_1 = Div{parent=net_cfg,x=2,y=4,width=49}
|
||||||
local net_c_2 = Div{parent=net_cfg,x=2,y=4,width=49}
|
local net_c_2 = Div{parent=net_cfg,x=2,y=4,width=49}
|
||||||
local net_c_3 = Div{parent=net_cfg,x=2,y=4,width=49}
|
local net_c_3 = Div{parent=net_cfg,x=2,y=4,width=49}
|
||||||
|
local net_c_4 = Div{parent=net_cfg,x=2,y=4,width=49}
|
||||||
|
|
||||||
local net_pane = MultiPane{parent=net_cfg,x=1,y=4,panes={net_c_1,net_c_2,net_c_3}}
|
local net_pane = MultiPane{parent=net_cfg,x=1,y=4,panes={net_c_1,net_c_2,net_c_3,net_c_4}}
|
||||||
|
|
||||||
TextBox{parent=net_cfg,x=1,y=2,text=" Network Configuration",fg_bg=cpair(colors.black,colors.lightBlue)}
|
TextBox{parent=net_cfg,x=1,y=2,text=" Network Configuration",fg_bg=cpair(colors.black,colors.lightBlue)}
|
||||||
|
|
||||||
TextBox{parent=net_c_1,x=1,y=1,text="Please set the network channels below."}
|
TextBox{parent=net_c_1,x=1,y=1,text="Please select the network interface(s)."}
|
||||||
TextBox{parent=net_c_1,x=1,y=3,height=4,text="Each of the 5 uniquely named channels, including the 2 below, must be the same for each device in this SCADA network. For multiplayer servers, it is recommended to not use the default channels.",fg_bg=g_lg_fg_bg}
|
TextBox{parent=net_c_1,x=41,y=1,text="new!",fg_bg=cpair(colors.red,colors._INHERIT)} ---@todo remove NEW tag on next revision
|
||||||
|
|
||||||
TextBox{parent=net_c_1,x=1,y=8,text="Supervisor Channel"}
|
local function en_dis_pref()
|
||||||
local svr_chan = NumberField{parent=net_c_1,x=1,y=9,width=7,default=ini_cfg.SVR_Channel,min=1,max=65535,fg_bg=bw_fg_bg}
|
if self.wireless.get_value() and self.wired.get_value() then
|
||||||
TextBox{parent=net_c_1,x=9,y=9,height=4,text="[SVR_CHANNEL]",fg_bg=g_lg_fg_bg}
|
self.wl_pref.enable()
|
||||||
TextBox{parent=net_c_1,x=1,y=11,text="PLC Channel"}
|
else
|
||||||
local plc_chan = NumberField{parent=net_c_1,x=1,y=12,width=7,default=ini_cfg.PLC_Channel,min=1,max=65535,fg_bg=bw_fg_bg}
|
self.wl_pref.set_value(self.wireless.get_value())
|
||||||
TextBox{parent=net_c_1,x=9,y=12,height=4,text="[PLC_CHANNEL]",fg_bg=g_lg_fg_bg}
|
self.wl_pref.disable()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
local chan_err = TextBox{parent=net_c_1,x=8,y=14,width=35,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
local function on_wired_change(_)
|
||||||
|
en_dis_pref()
|
||||||
|
tool_ctl.gen_modem_list()
|
||||||
|
end
|
||||||
|
|
||||||
|
self.wireless = Checkbox{parent=net_c_1,x=1,y=3,label="Wireless/Ender Modem",default=ini_cfg.WirelessModem,box_fg_bg=cpair(colors.lightBlue,colors.black),callback=en_dis_pref}
|
||||||
|
self.wl_pref = Checkbox{parent=net_c_1,x=30,y=3,label="Prefer Wireless",default=ini_cfg.PreferWireless,box_fg_bg=cpair(colors.lightBlue,colors.black),disable_fg_bg=g_lg_fg_bg}
|
||||||
|
self.wired = Checkbox{parent=net_c_1,x=1,y=5,label="Wired Modem",default=ini_cfg.WiredModem~=false,box_fg_bg=cpair(colors.lightBlue,colors.black),callback=on_wired_change}
|
||||||
|
TextBox{parent=net_c_1,x=3,y=6,text="this one MUST ONLY connect to SCADA computers",fg_bg=cpair(colors.red,colors._INHERIT)}
|
||||||
|
TextBox{parent=net_c_1,x=3,y=7,text="connecting it to peripherals will cause issues",fg_bg=g_lg_fg_bg}
|
||||||
|
local modem_list = ListBox{parent=net_c_1,x=1,y=8,height=5,width=49,scroll_height=100,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
|
||||||
|
|
||||||
|
local modem_err = TextBox{parent=net_c_1,x=8,y=14,width=35,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||||
|
|
||||||
|
en_dis_pref()
|
||||||
|
|
||||||
|
local function submit_interfaces()
|
||||||
|
tmp_cfg.WirelessModem = self.wireless.get_value()
|
||||||
|
|
||||||
|
if tmp_cfg.WirelessModem and tmp_cfg.WiredModem then
|
||||||
|
tmp_cfg.PreferWireless = self.wl_pref.get_value()
|
||||||
|
else
|
||||||
|
tmp_cfg.PreferWireless = tmp_cfg.WirelessModem
|
||||||
|
self.wl_pref.set_value(tmp_cfg.PreferWireless)
|
||||||
|
end
|
||||||
|
|
||||||
|
if not self.wired.get_value() then
|
||||||
|
tmp_cfg.WiredModem = false
|
||||||
|
tool_ctl.gen_modem_list()
|
||||||
|
end
|
||||||
|
|
||||||
|
if not (self.wired.get_value() or self.wireless.get_value()) then
|
||||||
|
modem_err.set_value("Please select a modem type.")
|
||||||
|
modem_err.show()
|
||||||
|
elseif self.wired.get_value() and type(tmp_cfg.WiredModem) ~= "string" then
|
||||||
|
modem_err.set_value("Please select a wired modem.")
|
||||||
|
modem_err.show()
|
||||||
|
else
|
||||||
|
if tmp_cfg.WirelessModem then
|
||||||
|
self.range.enable()
|
||||||
|
else
|
||||||
|
self.range.set_value(0)
|
||||||
|
self.range.disable()
|
||||||
|
end
|
||||||
|
|
||||||
|
net_pane.set_value(2)
|
||||||
|
modem_err.hide(true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
PushButton{parent=net_c_1,x=1,y=14,text="\x1b Back",callback=function()main_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
PushButton{parent=net_c_1,x=44,y=14,text="Next \x1a",callback=submit_interfaces,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
|
TextBox{parent=net_c_2,x=1,y=1,text="Please set the network channels below."}
|
||||||
|
TextBox{parent=net_c_2,x=1,y=3,height=4,text="Each of the 5 uniquely named channels, including the 2 below, must be the same for each device in this SCADA network. For multiplayer servers, it is recommended to not use the default channels.",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
|
TextBox{parent=net_c_2,x=1,y=8,text="Supervisor Channel"}
|
||||||
|
local svr_chan = NumberField{parent=net_c_2,x=1,y=9,width=7,default=ini_cfg.SVR_Channel,min=1,max=65535,fg_bg=bw_fg_bg}
|
||||||
|
TextBox{parent=net_c_2,x=9,y=9,height=4,text="[SVR_CHANNEL]",fg_bg=g_lg_fg_bg}
|
||||||
|
TextBox{parent=net_c_2,x=1,y=11,text="PLC Channel"}
|
||||||
|
local plc_chan = NumberField{parent=net_c_2,x=1,y=12,width=7,default=ini_cfg.PLC_Channel,min=1,max=65535,fg_bg=bw_fg_bg}
|
||||||
|
TextBox{parent=net_c_2,x=9,y=12,height=4,text="[PLC_CHANNEL]",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
|
local chan_err = TextBox{parent=net_c_2,x=8,y=14,width=35,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||||
|
|
||||||
local function submit_channels()
|
local function submit_channels()
|
||||||
local svr_c = tonumber(svr_chan.get_value())
|
local svr_c = tonumber(svr_chan.get_value())
|
||||||
@@ -191,7 +272,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit)
|
|||||||
if svr_c ~= nil and plc_c ~= nil then
|
if svr_c ~= nil and plc_c ~= nil then
|
||||||
tmp_cfg.SVR_Channel = svr_c
|
tmp_cfg.SVR_Channel = svr_c
|
||||||
tmp_cfg.PLC_Channel = plc_c
|
tmp_cfg.PLC_Channel = plc_c
|
||||||
net_pane.set_value(2)
|
net_pane.set_value(3)
|
||||||
chan_err.hide(true)
|
chan_err.hide(true)
|
||||||
elseif svr_c == nil then
|
elseif svr_c == nil then
|
||||||
chan_err.set_value("Please set the supervisor channel.")
|
chan_err.set_value("Please set the supervisor channel.")
|
||||||
@@ -202,54 +283,62 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
PushButton{parent=net_c_1,x=1,y=14,text="\x1b Back",callback=function()main_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=net_c_2,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
PushButton{parent=net_c_1,x=44,y=14,text="Next \x1a",callback=submit_channels,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=net_c_2,x=44,y=14,text="Next \x1a",callback=submit_channels,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=net_c_2,x=1,y=1,text="Connection Timeout"}
|
TextBox{parent=net_c_3,x=1,y=1,text="Connection Timeout"}
|
||||||
local timeout = NumberField{parent=net_c_2,x=1,y=2,width=7,default=ini_cfg.ConnTimeout,min=2,max=25,max_chars=6,max_frac_digits=2,allow_decimal=true,fg_bg=bw_fg_bg}
|
local timeout = NumberField{parent=net_c_3,x=1,y=2,width=7,default=ini_cfg.ConnTimeout,min=2,max=25,max_chars=6,max_frac_digits=2,allow_decimal=true,fg_bg=bw_fg_bg}
|
||||||
TextBox{parent=net_c_2,x=9,y=2,height=2,text="seconds (default 5)",fg_bg=g_lg_fg_bg}
|
TextBox{parent=net_c_3,x=9,y=2,height=2,text="seconds (default 5)",fg_bg=g_lg_fg_bg}
|
||||||
TextBox{parent=net_c_2,x=1,y=3,height=4,text="You generally do not want or need to modify this. On slow servers, you can increase this to make the system wait longer before assuming a disconnection.",fg_bg=g_lg_fg_bg}
|
TextBox{parent=net_c_3,x=1,y=3,height=4,text="You generally do not want or need to modify this. On slow servers, you can increase this to make the system wait longer before assuming a disconnection.",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=net_c_2,x=1,y=8,text="Trusted Range"}
|
TextBox{parent=net_c_3,x=1,y=8,text="Trusted Range (Wireless Only)"}
|
||||||
local range = NumberField{parent=net_c_2,x=1,y=9,width=10,default=ini_cfg.TrustedRange,min=0,max_chars=20,allow_decimal=true,fg_bg=bw_fg_bg}
|
self.range = NumberField{parent=net_c_3,x=1,y=9,width=10,default=ini_cfg.TrustedRange,min=0,max_chars=20,allow_decimal=true,fg_bg=bw_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)}
|
||||||
TextBox{parent=net_c_2,x=1,y=10,height=4,text="Setting this to a value larger than 0 prevents connections with devices that many meters (blocks) away in any direction.",fg_bg=g_lg_fg_bg}
|
TextBox{parent=net_c_3,x=1,y=10,height=4,text="Setting this to a value larger than 0 prevents wireless connections with devices that many meters (blocks) away in any direction.",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
local p2_err = TextBox{parent=net_c_2,x=8,y=14,width=35,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
local n3_err = TextBox{parent=net_c_3,x=8,y=14,width=35,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||||
|
|
||||||
local function submit_ct_tr()
|
local function submit_ct_tr()
|
||||||
local timeout_val = tonumber(timeout.get_value())
|
local timeout_val = tonumber(timeout.get_value())
|
||||||
local range_val = tonumber(range.get_value())
|
local range_val = tonumber(self.range.get_value())
|
||||||
if timeout_val ~= nil and range_val ~= nil then
|
|
||||||
tmp_cfg.ConnTimeout = timeout_val
|
if timeout_val == nil then
|
||||||
tmp_cfg.TrustedRange = range_val
|
n3_err.set_value("Please set the connection timeout.")
|
||||||
net_pane.set_value(3)
|
n3_err.show()
|
||||||
p2_err.hide(true)
|
elseif tmp_cfg.WirelessModem and (range_val == nil) then
|
||||||
elseif timeout_val == nil then
|
n3_err.set_value("Please set the trusted range.")
|
||||||
p2_err.set_value("Please set the connection timeout.")
|
n3_err.show()
|
||||||
p2_err.show()
|
|
||||||
else
|
else
|
||||||
p2_err.set_value("Please set the trusted range.")
|
tmp_cfg.ConnTimeout = timeout_val
|
||||||
p2_err.show()
|
tmp_cfg.TrustedRange = tri(tmp_cfg.WirelessModem, range_val, 0)
|
||||||
|
|
||||||
|
if tmp_cfg.WirelessModem then
|
||||||
|
net_pane.set_value(4)
|
||||||
|
else
|
||||||
|
main_pane.set_value(4)
|
||||||
|
tmp_cfg.AuthKey = ""
|
||||||
|
end
|
||||||
|
|
||||||
|
n3_err.hide(true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
PushButton{parent=net_c_2,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=net_c_3,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
PushButton{parent=net_c_2,x=44,y=14,text="Next \x1a",callback=submit_ct_tr,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=net_c_3,x=44,y=14,text="Next \x1a",callback=submit_ct_tr,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=net_c_3,x=1,y=1,height=2,text="Optionally, set the facility authentication key below. Do NOT use one of your passwords."}
|
TextBox{parent=net_c_4,x=1,y=1,height=2,text="Optionally, set the facility authentication key below. Do NOT use one of your passwords."}
|
||||||
TextBox{parent=net_c_3,x=1,y=4,height=6,text="This enables verifying that messages are authentic, so it is intended for security on multiplayer servers. All devices on the same network MUST use the same key if any device has a key. This does result in some extra computation (can slow things down).",fg_bg=g_lg_fg_bg}
|
TextBox{parent=net_c_4,x=1,y=4,height=6,text="This enables verifying that messages are authentic, so it is intended for wireless security on multiplayer servers. All devices on the same wireless network MUST use the same key if any device has a key. This does result in some extra computation (can slow things down).",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=net_c_3,x=1,y=11,text="Facility Auth Key"}
|
TextBox{parent=net_c_4,x=1,y=11,text="Auth Key (Wireless Only, Not Used for Wired)"}
|
||||||
local key, _ = TextField{parent=net_c_3,x=1,y=12,max_len=64,value=ini_cfg.AuthKey,width=32,height=1,fg_bg=bw_fg_bg}
|
local key, _ = TextField{parent=net_c_4,x=1,y=12,max_len=64,value=ini_cfg.AuthKey,width=32,height=1,fg_bg=bw_fg_bg}
|
||||||
|
|
||||||
local function censor_key(enable) key.censor(util.trinary(enable, "*", nil)) end
|
local function censor_key(enable) key.censor(tri(enable, "*", nil)) end
|
||||||
|
|
||||||
local hide_key = Checkbox{parent=net_c_3,x=34,y=12,label="Hide",box_fg_bg=cpair(colors.lightBlue,colors.black),callback=censor_key}
|
local hide_key = Checkbox{parent=net_c_4,x=34,y=12,label="Hide",box_fg_bg=cpair(colors.lightBlue,colors.black),callback=censor_key}
|
||||||
|
|
||||||
hide_key.set_value(true)
|
hide_key.set_value(true)
|
||||||
censor_key(true)
|
censor_key(true)
|
||||||
|
|
||||||
local key_err = TextBox{parent=net_c_3,x=8,y=14,width=35,text="Key must be at least 8 characters.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
local key_err = TextBox{parent=net_c_4,x=8,y=14,width=35,text="Key must be at least 8 characters.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||||
|
|
||||||
local function submit_auth()
|
local function submit_auth()
|
||||||
local v = key.get_value()
|
local v = key.get_value()
|
||||||
@@ -260,8 +349,8 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit)
|
|||||||
else key_err.show() end
|
else key_err.show() end
|
||||||
end
|
end
|
||||||
|
|
||||||
PushButton{parent=net_c_3,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=net_c_4,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(3)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
PushButton{parent=net_c_3,x=44,y=14,text="Next \x1a",callback=submit_auth,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=net_c_4,x=44,y=14,text="Next \x1a",callback=submit_auth,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
--#endregion
|
--#endregion
|
||||||
|
|
||||||
@@ -274,7 +363,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit)
|
|||||||
TextBox{parent=log_c_1,x=1,y=1,text="Please configure logging below."}
|
TextBox{parent=log_c_1,x=1,y=1,text="Please configure logging below."}
|
||||||
|
|
||||||
TextBox{parent=log_c_1,x=1,y=3,text="Log File Mode"}
|
TextBox{parent=log_c_1,x=1,y=3,text="Log File Mode"}
|
||||||
local mode = RadioButton{parent=log_c_1,x=1,y=4,default=ini_cfg.LogMode+1,options={"Append on Startup","Replace on Startup"},callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.pink}
|
local mode = RadioButton{parent=log_c_1,x=1,y=4,default=ini_cfg.LogMode+1,options={"Append on Startup","Replace on Startup"},radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.pink}
|
||||||
|
|
||||||
TextBox{parent=log_c_1,x=1,y=7,text="Log File Path"}
|
TextBox{parent=log_c_1,x=1,y=7,text="Log File Path"}
|
||||||
local path = TextField{parent=log_c_1,x=1,y=8,width=49,height=1,value=ini_cfg.LogPath,max_len=128,fg_bg=bw_fg_bg}
|
local path = TextField{parent=log_c_1,x=1,y=8,width=49,height=1,value=ini_cfg.LogPath,max_len=128,fg_bg=bw_fg_bg}
|
||||||
@@ -320,7 +409,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit)
|
|||||||
TextBox{parent=clr_c_1,x=1,y=4,height=2,text="Click 'Accessibility' below to access colorblind assistive options.",fg_bg=g_lg_fg_bg}
|
TextBox{parent=clr_c_1,x=1,y=4,height=2,text="Click 'Accessibility' below to access colorblind assistive options.",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=clr_c_1,x=1,y=7,text="Front Panel Theme"}
|
TextBox{parent=clr_c_1,x=1,y=7,text="Front Panel Theme"}
|
||||||
local fp_theme = RadioButton{parent=clr_c_1,x=1,y=8,default=ini_cfg.FrontPanelTheme,options=themes.FP_THEME_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.magenta}
|
local fp_theme = RadioButton{parent=clr_c_1,x=1,y=8,default=ini_cfg.FrontPanelTheme,options=themes.FP_THEME_NAMES,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=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."}
|
||||||
|
|
||||||
@@ -359,7 +448,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit)
|
|||||||
PushButton{parent=clr_c_2,x=44,y=14,min_width=6,text="Done",callback=function()clr_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=clr_c_2,x=44,y=14,min_width=6,text="Done",callback=function()clr_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
local function back_from_colors()
|
local function back_from_colors()
|
||||||
main_pane.set_value(util.trinary(tool_ctl.jumped_to_color, 1, 4))
|
main_pane.set_value(tri(tool_ctl.jumped_to_color, 1, 4))
|
||||||
tool_ctl.jumped_to_color = false
|
tool_ctl.jumped_to_color = false
|
||||||
recolor(1)
|
recolor(1)
|
||||||
end
|
end
|
||||||
@@ -461,10 +550,14 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit)
|
|||||||
try_set(side, side_to_idx(ini_cfg.EmerCoolSide))
|
try_set(side, side_to_idx(ini_cfg.EmerCoolSide))
|
||||||
try_set(bundled, ini_cfg.EmerCoolColor ~= nil)
|
try_set(bundled, ini_cfg.EmerCoolColor ~= nil)
|
||||||
if ini_cfg.EmerCoolColor ~= nil then try_set(color, color_to_idx(ini_cfg.EmerCoolColor)) end
|
if ini_cfg.EmerCoolColor ~= nil then try_set(color, color_to_idx(ini_cfg.EmerCoolColor)) end
|
||||||
|
try_set(invert, ini_cfg.EmerCoolInvert)
|
||||||
|
try_set(self.wireless, ini_cfg.WirelessModem)
|
||||||
|
try_set(self.wired, ini_cfg.WiredModem ~= false)
|
||||||
|
try_set(self.wl_pref, ini_cfg.PreferWireless)
|
||||||
try_set(svr_chan, ini_cfg.SVR_Channel)
|
try_set(svr_chan, ini_cfg.SVR_Channel)
|
||||||
try_set(plc_chan, ini_cfg.PLC_Channel)
|
try_set(plc_chan, ini_cfg.PLC_Channel)
|
||||||
try_set(timeout, ini_cfg.ConnTimeout)
|
try_set(timeout, ini_cfg.ConnTimeout)
|
||||||
try_set(range, ini_cfg.TrustedRange)
|
try_set(self.range, ini_cfg.TrustedRange)
|
||||||
try_set(key, ini_cfg.AuthKey)
|
try_set(key, ini_cfg.AuthKey)
|
||||||
try_set(mode, ini_cfg.LogMode)
|
try_set(mode, ini_cfg.LogMode)
|
||||||
try_set(path, ini_cfg.LogPath)
|
try_set(path, ini_cfg.LogPath)
|
||||||
@@ -533,9 +626,11 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit)
|
|||||||
if tmp_cfg.EmerCoolEnable then
|
if tmp_cfg.EmerCoolEnable then
|
||||||
tmp_cfg.EmerCoolSide = config.EMERGENCY_COOL.side
|
tmp_cfg.EmerCoolSide = config.EMERGENCY_COOL.side
|
||||||
tmp_cfg.EmerCoolColor = config.EMERGENCY_COOL.color
|
tmp_cfg.EmerCoolColor = config.EMERGENCY_COOL.color
|
||||||
|
tmp_cfg.EmerCoolInvert = false
|
||||||
else
|
else
|
||||||
tmp_cfg.EmerCoolSide = nil
|
tmp_cfg.EmerCoolSide = nil
|
||||||
tmp_cfg.EmerCoolColor = nil
|
tmp_cfg.EmerCoolColor = nil
|
||||||
|
tmp_cfg.EmerCoolInvert = false
|
||||||
end
|
end
|
||||||
|
|
||||||
tmp_cfg.SVR_Channel = config.SVR_CHANNEL
|
tmp_cfg.SVR_Channel = config.SVR_CHANNEL
|
||||||
@@ -579,7 +674,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit)
|
|||||||
local val = util.strval(raw)
|
local val = util.strval(raw)
|
||||||
|
|
||||||
if f[1] == "AuthKey" and raw then val = string.rep("*", string.len(val))
|
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] == "LogMode" then val = tri(raw == log.MODE.APPEND, "append", "replace")
|
||||||
elseif f[1] == "EmerCoolColor" and raw ~= nil then val = rsio.color_name(raw)
|
elseif f[1] == "EmerCoolColor" and raw ~= nil then val = rsio.color_name(raw)
|
||||||
elseif f[1] == "FrontPanelTheme" then
|
elseif f[1] == "FrontPanelTheme" then
|
||||||
val = util.strval(themes.fp_theme_name(raw))
|
val = util.strval(themes.fp_theme_name(raw))
|
||||||
@@ -589,7 +684,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit)
|
|||||||
|
|
||||||
if val == "nil" then val = "<not set>" end
|
if val == "nil" then val = "<not set>" end
|
||||||
|
|
||||||
local c = util.trinary(alternate, g_lg_fg_bg, cpair(colors.gray,colors.white))
|
local c = tri(alternate, g_lg_fg_bg, cpair(colors.gray,colors.white))
|
||||||
alternate = not alternate
|
alternate = not alternate
|
||||||
|
|
||||||
if (string.len(val) > val_max_w) or string.find(val, "\n") then
|
if (string.len(val) > val_max_w) or string.find(val, "\n") then
|
||||||
@@ -611,6 +706,59 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- generate the list of available/assigned wired modems
|
||||||
|
function tool_ctl.gen_modem_list()
|
||||||
|
modem_list.remove_all()
|
||||||
|
|
||||||
|
local enable = self.wired.get_value()
|
||||||
|
|
||||||
|
local function select(iface)
|
||||||
|
tmp_cfg.WiredModem = iface
|
||||||
|
tool_ctl.gen_modem_list()
|
||||||
|
end
|
||||||
|
|
||||||
|
local modems = ppm.get_wired_modem_list()
|
||||||
|
local missing = { tmp = true, ini = true }
|
||||||
|
|
||||||
|
for iface, _ in pairs(modems) do
|
||||||
|
if ini_cfg.WiredModem == iface then missing.ini = false end
|
||||||
|
if tmp_cfg.WiredModem == iface then missing.tmp = false end
|
||||||
|
end
|
||||||
|
|
||||||
|
if missing.tmp and tmp_cfg.WiredModem then
|
||||||
|
local line = Div{parent=modem_list,x=1,y=1,height=1}
|
||||||
|
|
||||||
|
TextBox{parent=line,x=1,y=1,width=4,text="Used",fg_bg=cpair(tri(enable,colors.blue,colors.gray),colors.white)}
|
||||||
|
PushButton{parent=line,x=6,y=1,min_width=8,height=1,text="SELECT",callback=function()end,fg_bg=cpair(colors.black,colors.lightBlue),active_fg_bg=btn_act_fg_bg,dis_fg_bg=g_lg_fg_bg}.disable()
|
||||||
|
TextBox{parent=line,x=15,y=1,text="[missing]",fg_bg=cpair(colors.red,colors.white)}
|
||||||
|
TextBox{parent=line,x=25,y=1,text=tmp_cfg.WiredModem}
|
||||||
|
end
|
||||||
|
|
||||||
|
if missing.ini and ini_cfg.WiredModem and (tmp_cfg.WiredModem ~= ini_cfg.WiredModem) then
|
||||||
|
local line = Div{parent=modem_list,x=1,y=1,height=1}
|
||||||
|
local used = tmp_cfg.WiredModem == ini_cfg.WiredModem
|
||||||
|
|
||||||
|
TextBox{parent=line,x=1,y=1,width=4,text=tri(used,"Used","----"),fg_bg=cpair(tri(used and enable,colors.blue,colors.gray),colors.white)}
|
||||||
|
local select_btn = PushButton{parent=line,x=6,y=1,min_width=8,height=1,text="SELECT",callback=function()select(ini_cfg.WiredModem)end,fg_bg=cpair(colors.black,colors.lightBlue),active_fg_bg=btn_act_fg_bg,dis_fg_bg=g_lg_fg_bg}
|
||||||
|
TextBox{parent=line,x=15,y=1,text="[missing]",fg_bg=cpair(colors.red,colors.white)}
|
||||||
|
TextBox{parent=line,x=25,y=1,text=ini_cfg.WiredModem}
|
||||||
|
|
||||||
|
if used or not enable then select_btn.disable() end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- list wired modems
|
||||||
|
for iface, _ in pairs(modems) do
|
||||||
|
local line = Div{parent=modem_list,x=1,y=1,height=1}
|
||||||
|
local used = tmp_cfg.WiredModem == iface
|
||||||
|
|
||||||
|
TextBox{parent=line,x=1,y=1,width=4,text=tri(used,"Used","----"),fg_bg=cpair(tri(used and enable,colors.blue,colors.gray),colors.white)}
|
||||||
|
local select_btn = PushButton{parent=line,x=6,y=1,min_width=8,height=1,text="SELECT",callback=function()select(iface)end,fg_bg=cpair(colors.black,colors.lightBlue),active_fg_bg=btn_act_fg_bg,dis_fg_bg=g_lg_fg_bg}
|
||||||
|
TextBox{parent=line,x=15,y=1,text=iface}
|
||||||
|
|
||||||
|
if used or not enable then select_btn.disable() end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
--#endregion
|
--#endregion
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
--
|
--
|
||||||
|
|
||||||
local log = require("scada-common.log")
|
local log = require("scada-common.log")
|
||||||
|
local ppm = require("scada-common.ppm")
|
||||||
local tcd = require("scada-common.tcd")
|
local tcd = require("scada-common.tcd")
|
||||||
local util = require("scada-common.util")
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
@@ -32,7 +33,9 @@ local changes = {
|
|||||||
{ "v1.6.2", { "AuthKey minimum length is now 8 (if set)" } },
|
{ "v1.6.2", { "AuthKey minimum length is now 8 (if set)" } },
|
||||||
{ "v1.6.8", { "ConnTimeout can now have a fractional part" } },
|
{ "v1.6.8", { "ConnTimeout can now have a fractional part" } },
|
||||||
{ "v1.6.15", { "Added front panel UI theme", "Added color accessibility modes" } },
|
{ "v1.6.15", { "Added front panel UI theme", "Added color accessibility modes" } },
|
||||||
{ "v1.7.3", { "Added standard with black off state color mode", "Added blue indicator color modes" } }
|
{ "v1.7.3", { "Added standard with black off state color mode", "Added blue indicator color modes" } },
|
||||||
|
{ "v1.8.21", { "Added option to invert emergency coolant redstone control" } },
|
||||||
|
{ "v1.10.0", { "Added support for wired communications modems" } }
|
||||||
}
|
}
|
||||||
|
|
||||||
---@class plc_configurator
|
---@class plc_configurator
|
||||||
@@ -67,6 +70,8 @@ local tool_ctl = {
|
|||||||
|
|
||||||
gen_summary = nil, ---@type function
|
gen_summary = nil, ---@type function
|
||||||
load_legacy = nil, ---@type function
|
load_legacy = nil, ---@type function
|
||||||
|
|
||||||
|
gen_modem_list = function () end
|
||||||
}
|
}
|
||||||
|
|
||||||
---@class plc_config
|
---@class plc_config
|
||||||
@@ -76,6 +81,10 @@ local tmp_cfg = {
|
|||||||
EmerCoolEnable = false,
|
EmerCoolEnable = false,
|
||||||
EmerCoolSide = nil, ---@type string|nil
|
EmerCoolSide = nil, ---@type string|nil
|
||||||
EmerCoolColor = nil, ---@type color|nil
|
EmerCoolColor = nil, ---@type color|nil
|
||||||
|
EmerCoolInvert = false, ---@type boolean
|
||||||
|
WirelessModem = true,
|
||||||
|
WiredModem = false, ---@type string|false
|
||||||
|
PreferWireless = true,
|
||||||
SVR_Channel = nil, ---@type integer
|
SVR_Channel = nil, ---@type integer
|
||||||
PLC_Channel = nil, ---@type integer
|
PLC_Channel = nil, ---@type integer
|
||||||
ConnTimeout = nil, ---@type number
|
ConnTimeout = nil, ---@type number
|
||||||
@@ -100,6 +109,10 @@ local fields = {
|
|||||||
{ "EmerCoolEnable", "Emergency Coolant", false },
|
{ "EmerCoolEnable", "Emergency Coolant", false },
|
||||||
{ "EmerCoolSide", "Emergency Coolant Side", nil },
|
{ "EmerCoolSide", "Emergency Coolant Side", nil },
|
||||||
{ "EmerCoolColor", "Emergency Coolant Color", nil },
|
{ "EmerCoolColor", "Emergency Coolant Color", nil },
|
||||||
|
{ "EmerCoolInvert", "Emergency Coolant Invert", false },
|
||||||
|
{ "WirelessModem", "Wireless/Ender Comms Modem", true },
|
||||||
|
{ "WiredModem", "Wired Comms Modem", false },
|
||||||
|
{ "PreferWireless", "Prefer Wireless Modem", true },
|
||||||
{ "SVR_Channel", "SVR Channel", 16240 },
|
{ "SVR_Channel", "SVR Channel", 16240 },
|
||||||
{ "PLC_Channel", "PLC Channel", 16241 },
|
{ "PLC_Channel", "PLC Channel", 16241 },
|
||||||
{ "ConnTimeout", "Connection Timeout", 5 },
|
{ "ConnTimeout", "Connection Timeout", 5 },
|
||||||
@@ -258,8 +271,13 @@ function configurator.configure(ask_config)
|
|||||||
load_settings(settings_cfg, true)
|
load_settings(settings_cfg, true)
|
||||||
tool_ctl.has_config = load_settings(ini_cfg)
|
tool_ctl.has_config = load_settings(ini_cfg)
|
||||||
|
|
||||||
|
-- set tmp_cfg so interface lists are correct
|
||||||
|
tmp_cfg.WiredModem = ini_cfg.WiredModem
|
||||||
|
|
||||||
reset_term()
|
reset_term()
|
||||||
|
|
||||||
|
ppm.mount_all()
|
||||||
|
|
||||||
-- set overridden colors
|
-- set overridden colors
|
||||||
for i = 1, #style.colors do
|
for i = 1, #style.colors do
|
||||||
term.setPaletteColor(style.colors[i].c, style.colors[i].hex)
|
term.setPaletteColor(style.colors[i].c, style.colors[i].hex)
|
||||||
@@ -269,6 +287,8 @@ function configurator.configure(ask_config)
|
|||||||
local display = DisplayBox{window=term.current(),fg_bg=style.root}
|
local display = DisplayBox{window=term.current(),fg_bg=style.root}
|
||||||
config_view(display)
|
config_view(display)
|
||||||
|
|
||||||
|
tool_ctl.gen_modem_list()
|
||||||
|
|
||||||
while true do
|
while true do
|
||||||
local event, param1, param2, param3, param4, param5 = util.pull_event()
|
local event, param1, param2, param3, param4, param5 = util.pull_event()
|
||||||
|
|
||||||
@@ -285,6 +305,16 @@ function configurator.configure(ask_config)
|
|||||||
display.handle_paste(param1)
|
display.handle_paste(param1)
|
||||||
elseif event == "modem_message" then
|
elseif event == "modem_message" then
|
||||||
check.receive_sv(param1, param2, param3, param4, param5)
|
check.receive_sv(param1, param2, param3, param4, param5)
|
||||||
|
elseif event == "conn_test_complete" then
|
||||||
|
check.conn_test_callback(param1)
|
||||||
|
elseif event == "peripheral_detach" then
|
||||||
|
---@diagnostic disable-next-line: discard-returns
|
||||||
|
ppm.handle_unmount(param1)
|
||||||
|
tool_ctl.gen_modem_list()
|
||||||
|
elseif event == "peripheral" then
|
||||||
|
---@diagnostic disable-next-line: discard-returns
|
||||||
|
ppm.mount(param1)
|
||||||
|
tool_ctl.gen_modem_list()
|
||||||
end
|
end
|
||||||
|
|
||||||
if event == "terminate" then return end
|
if event == "terminate" then return end
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ function databus.rps_scram() dbus_iface.rps_scram() end
|
|||||||
-- transmit a command to the RPS to reset
|
-- transmit a command to the RPS to reset
|
||||||
function databus.rps_reset() dbus_iface.rps_reset() end
|
function databus.rps_reset() dbus_iface.rps_reset() end
|
||||||
|
|
||||||
-- transmit firmware versions across the bus
|
-- transmit firmware versions
|
||||||
---@param plc_v string PLC version
|
---@param plc_v string PLC version
|
||||||
---@param comms_v string comms version
|
---@param comms_v string comms version
|
||||||
function databus.tx_versions(plc_v, comms_v)
|
function databus.tx_versions(plc_v, comms_v)
|
||||||
@@ -41,19 +41,19 @@ function databus.tx_versions(plc_v, comms_v)
|
|||||||
databus.ps.publish("comms_version", comms_v)
|
databus.ps.publish("comms_version", comms_v)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- transmit unit ID across the bus
|
-- transmit unit ID
|
||||||
---@param id integer unit ID
|
---@param id integer unit ID
|
||||||
function databus.tx_id(id)
|
function databus.tx_id(id)
|
||||||
databus.ps.publish("unit_id", id)
|
databus.ps.publish("unit_id", id)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- transmit hardware status across the bus
|
-- transmit hardware status
|
||||||
---@param plc_state plc_state
|
---@param plc_state plc_state
|
||||||
function databus.tx_hw_status(plc_state)
|
function databus.tx_hw_status(plc_state)
|
||||||
databus.ps.publish("reactor_dev_state", util.trinary(plc_state.no_reactor, 1, util.trinary(plc_state.reactor_formed, 3, 2)))
|
|
||||||
databus.ps.publish("has_modem", not plc_state.no_modem)
|
|
||||||
databus.ps.publish("degraded", plc_state.degraded)
|
databus.ps.publish("degraded", plc_state.degraded)
|
||||||
databus.ps.publish("init_ok", plc_state.init_ok)
|
databus.ps.publish("reactor_dev_state", util.trinary(plc_state.no_reactor, 1, util.trinary(plc_state.reactor_formed, 3, 2)))
|
||||||
|
databus.ps.publish("has_wd_modem", plc_state.wd_modem)
|
||||||
|
databus.ps.publish("has_wl_modem", plc_state.wl_modem)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- transmit thread (routine) statuses
|
-- transmit thread (routine) statuses
|
||||||
@@ -63,19 +63,19 @@ function databus.tx_rt_status(thread, ok)
|
|||||||
databus.ps.publish(util.c("routine__", thread), ok)
|
databus.ps.publish(util.c("routine__", thread), ok)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- transmit supervisor link state across the bus
|
-- transmit supervisor link state
|
||||||
---@param state integer
|
---@param state integer
|
||||||
function databus.tx_link_state(state)
|
function databus.tx_link_state(state)
|
||||||
databus.ps.publish("link_state", state)
|
databus.ps.publish("link_state", state)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- transmit reactor enable state across the bus
|
-- transmit reactor enable state
|
||||||
---@param active any reactor active
|
---@param active any reactor active
|
||||||
function databus.tx_reactor_state(active)
|
function databus.tx_reactor_state(active)
|
||||||
databus.ps.publish("reactor_active", active == true)
|
databus.ps.publish("reactor_active", active == true)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- transmit RPS data across the bus
|
-- transmit RPS data
|
||||||
---@param tripped boolean RPS tripped
|
---@param tripped boolean RPS tripped
|
||||||
---@param status boolean[] RPS status
|
---@param status boolean[] RPS status
|
||||||
---@param emer_cool_active boolean RPS activated the emergency coolant
|
---@param emer_cool_active boolean RPS activated the emergency coolant
|
||||||
@@ -95,11 +95,4 @@ function databus.tx_rps(tripped, status, emer_cool_active)
|
|||||||
databus.ps.publish("emer_cool", emer_cool_active)
|
databus.ps.publish("emer_cool", emer_cool_active)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- link a function to receive data from the bus
|
|
||||||
---@param field string field name
|
|
||||||
---@param func function function to link
|
|
||||||
function databus.rx_field(field, func)
|
|
||||||
databus.ps.subscribe(field, func)
|
|
||||||
end
|
|
||||||
|
|
||||||
return databus
|
return databus
|
||||||
|
|||||||
@@ -35,12 +35,11 @@ local ind_red = style.ind_red
|
|||||||
|
|
||||||
-- create new front panel view
|
-- create new front panel view
|
||||||
---@param panel DisplayBox main displaybox
|
---@param panel DisplayBox main displaybox
|
||||||
local function init(panel)
|
---@param config plc_config configuraiton
|
||||||
|
local function init(panel, config)
|
||||||
local s_hi_box = style.theme.highlight_box
|
local s_hi_box = style.theme.highlight_box
|
||||||
|
|
||||||
local disabled_fg = style.fp.disabled_fg
|
local term_w, _ = term.getSize()
|
||||||
|
|
||||||
local term_w, term_h = term.getSize()
|
|
||||||
|
|
||||||
local header = TextBox{parent=panel,y=1,text="FISSION REACTOR PLC - UNIT ?",alignment=ALIGN.CENTER,fg_bg=style.theme.header}
|
local header = TextBox{parent=panel,y=1,text="FISSION REACTOR PLC - UNIT ?",alignment=ALIGN.CENTER,fg_bg=style.theme.header}
|
||||||
header.register(databus.ps, "unit_id", function (id) header.set_value(util.c("FISSION REACTOR PLC - UNIT ", id)) end)
|
header.register(databus.ps, "unit_id", function (id) header.set_value(util.c("FISSION REACTOR PLC - UNIT ", id)) end)
|
||||||
@@ -51,15 +50,29 @@ local function init(panel)
|
|||||||
|
|
||||||
local system = Div{parent=panel,width=14,height=18,x=2,y=3}
|
local system = Div{parent=panel,width=14,height=18,x=2,y=3}
|
||||||
|
|
||||||
local init_ok = LED{parent=system,label="STATUS",colors=cpair(colors.green,colors.red)}
|
local degraded = LED{parent=system,label="STATUS",colors=cpair(colors.red,colors.green)}
|
||||||
local heartbeat = LED{parent=system,label="HEARTBEAT",colors=ind_grn}
|
local heartbeat = LED{parent=system,label="HEARTBEAT",colors=ind_grn}
|
||||||
system.line_break()
|
system.line_break()
|
||||||
|
|
||||||
init_ok.register(databus.ps, "init_ok", init_ok.update)
|
degraded.register(databus.ps, "degraded", degraded.update)
|
||||||
heartbeat.register(databus.ps, "heartbeat", heartbeat.update)
|
heartbeat.register(databus.ps, "heartbeat", heartbeat.update)
|
||||||
|
|
||||||
local reactor = LEDPair{parent=system,label="REACTOR",off=colors.red,c1=colors.yellow,c2=colors.green}
|
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}
|
reactor.register(databus.ps, "reactor_dev_state", reactor.update)
|
||||||
|
|
||||||
|
if config.Networked then
|
||||||
|
if config.WirelessModem and config.WiredModem then
|
||||||
|
local wd_modem = LED{parent=system,label="WD MODEM",colors=ind_grn}
|
||||||
|
local wl_modem = LED{parent=system,label="WL MODEM",colors=ind_grn}
|
||||||
|
wd_modem.register(databus.ps, "has_wd_modem", wd_modem.update)
|
||||||
|
wl_modem.register(databus.ps, "has_wl_modem", wl_modem.update)
|
||||||
|
else
|
||||||
|
local modem = LED{parent=system,label="MODEM",colors=ind_grn}
|
||||||
|
modem.register(databus.ps, util.trinary(config.WirelessModem, "has_wl_modem", "has_wd_modem"), modem.update)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local _ = LED{parent=system,label="MODEM",colors=ind_grn}
|
||||||
|
end
|
||||||
|
|
||||||
if not style.colorblind then
|
if not style.colorblind then
|
||||||
local network = RGBLED{parent=system,label="NETWORK",colors={colors.green,colors.red,colors.yellow,colors.orange,style.ind_bkg}}
|
local network = RGBLED{parent=system,label="NETWORK",colors={colors.green,colors.red,colors.yellow,colors.orange,style.ind_bkg}}
|
||||||
@@ -99,9 +112,6 @@ local function init(panel)
|
|||||||
|
|
||||||
system.line_break()
|
system.line_break()
|
||||||
|
|
||||||
reactor.register(databus.ps, "reactor_dev_state", reactor.update)
|
|
||||||
modem.register(databus.ps, "has_modem", modem.update)
|
|
||||||
|
|
||||||
local rt_main = LED{parent=system,label="RT MAIN",colors=ind_grn}
|
local rt_main = LED{parent=system,label="RT MAIN",colors=ind_grn}
|
||||||
local rt_rps = LED{parent=system,label="RT RPS",colors=ind_grn}
|
local rt_rps = LED{parent=system,label="RT RPS",colors=ind_grn}
|
||||||
local rt_cmtx = LED{parent=system,label="RT COMMS TX",colors=ind_grn}
|
local rt_cmtx = LED{parent=system,label="RT COMMS TX",colors=ind_grn}
|
||||||
@@ -115,12 +125,8 @@ local function init(panel)
|
|||||||
rt_cmrx.register(databus.ps, "routine__comms_rx", rt_cmrx.update)
|
rt_cmrx.register(databus.ps, "routine__comms_rx", rt_cmrx.update)
|
||||||
rt_sctl.register(databus.ps, "routine__spctl", rt_sctl.update)
|
rt_sctl.register(databus.ps, "routine__spctl", rt_sctl.update)
|
||||||
|
|
||||||
---@diagnostic disable-next-line: undefined-field
|
|
||||||
local comp_id = util.sprintf("(%d)", os.getComputerID())
|
|
||||||
TextBox{parent=system,x=9,y=5,width=6,text=comp_id,fg_bg=disabled_fg}
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- status & controls
|
-- status & controls & hardware labeling
|
||||||
--
|
--
|
||||||
|
|
||||||
local status = Div{parent=panel,width=term_w-32,height=18,x=17,y=3}
|
local status = Div{parent=panel,width=term_w-32,height=18,x=17,y=3}
|
||||||
@@ -146,16 +152,14 @@ local function init(panel)
|
|||||||
active.register(databus.ps, "reactor_active", active.update)
|
active.register(databus.ps, "reactor_active", active.update)
|
||||||
scram.register(databus.ps, "rps_scram", scram.update)
|
scram.register(databus.ps, "rps_scram", scram.update)
|
||||||
|
|
||||||
--
|
local hw_labels = Rectangle{parent=status,width=status.get_width()-2,height=5,x=1,border=border(1,s_hi_box.bkg,true),even_inner=true}
|
||||||
-- about footer
|
|
||||||
--
|
|
||||||
|
|
||||||
local about = Div{parent=panel,width=15,height=2,y=term_h-1,fg_bg=disabled_fg}
|
---@diagnostic disable-next-line: undefined-field
|
||||||
local fw_v = TextBox{parent=about,text="FW: v00.00.00"}
|
local comp_id = util.sprintf("%03d", os.getComputerID())
|
||||||
local comms_v = TextBox{parent=about,text="NT: v00.00.00"}
|
|
||||||
|
|
||||||
fw_v.register(databus.ps, "version", function (version) fw_v.set_value(util.c("FW: ", version)) end)
|
TextBox{parent=hw_labels,text="FW "..databus.ps.get("version"),fg_bg=s_hi_box}
|
||||||
comms_v.register(databus.ps, "comms_version", function (version) comms_v.set_value(util.c("NT: v", version)) end)
|
TextBox{parent=hw_labels,text="NT v"..databus.ps.get("comms_version"),fg_bg=s_hi_box}
|
||||||
|
TextBox{parent=hw_labels,text="SN "..comp_id.."-PLC",fg_bg=s_hi_box}
|
||||||
|
|
||||||
--
|
--
|
||||||
-- rps list
|
-- rps list
|
||||||
|
|||||||
@@ -43,7 +43,11 @@ function plc.load_config()
|
|||||||
config.EmerCoolEnable = settings.get("EmerCoolEnable")
|
config.EmerCoolEnable = settings.get("EmerCoolEnable")
|
||||||
config.EmerCoolSide = settings.get("EmerCoolSide")
|
config.EmerCoolSide = settings.get("EmerCoolSide")
|
||||||
config.EmerCoolColor = settings.get("EmerCoolColor")
|
config.EmerCoolColor = settings.get("EmerCoolColor")
|
||||||
|
config.EmerCoolInvert = settings.get("EmerCoolInvert")
|
||||||
|
|
||||||
|
config.WirelessModem = settings.get("WirelessModem")
|
||||||
|
config.WiredModem = settings.get("WiredModem")
|
||||||
|
config.PreferWireless = settings.get("PreferWireless")
|
||||||
config.SVR_Channel = settings.get("SVR_Channel")
|
config.SVR_Channel = settings.get("SVR_Channel")
|
||||||
config.PLC_Channel = settings.get("PLC_Channel")
|
config.PLC_Channel = settings.get("PLC_Channel")
|
||||||
config.ConnTimeout = settings.get("ConnTimeout")
|
config.ConnTimeout = settings.get("ConnTimeout")
|
||||||
@@ -69,7 +73,11 @@ function plc.validate_config(cfg)
|
|||||||
cfv.assert_type_int(cfg.UnitID)
|
cfv.assert_type_int(cfg.UnitID)
|
||||||
cfv.assert_type_bool(cfg.EmerCoolEnable)
|
cfv.assert_type_bool(cfg.EmerCoolEnable)
|
||||||
|
|
||||||
if cfg.Networked == true then
|
if cfg.Networked then
|
||||||
|
cfv.assert_type_bool(cfg.WirelessModem)
|
||||||
|
cfv.assert((cfg.WiredModem == false) or (type(cfg.WiredModem) == "string"))
|
||||||
|
cfv.assert(cfg.WirelessModem or (type(cfg.WiredModem) == "string"))
|
||||||
|
cfv.assert_type_bool(cfg.PreferWireless)
|
||||||
cfv.assert_channel(cfg.SVR_Channel)
|
cfv.assert_channel(cfg.SVR_Channel)
|
||||||
cfv.assert_channel(cfg.PLC_Channel)
|
cfv.assert_channel(cfg.PLC_Channel)
|
||||||
cfv.assert_type_num(cfg.ConnTimeout)
|
cfv.assert_type_num(cfg.ConnTimeout)
|
||||||
@@ -98,6 +106,7 @@ function plc.validate_config(cfg)
|
|||||||
if cfg.EmerCoolEnable then
|
if cfg.EmerCoolEnable then
|
||||||
cfv.assert_eq(rsio.is_valid_side(cfg.EmerCoolSide), true)
|
cfv.assert_eq(rsio.is_valid_side(cfg.EmerCoolSide), true)
|
||||||
cfv.assert_eq(cfg.EmerCoolColor == nil or rsio.is_color(cfg.EmerCoolColor), true)
|
cfv.assert_eq(cfg.EmerCoolColor == nil or rsio.is_color(cfg.EmerCoolColor), true)
|
||||||
|
cfv.assert_type_bool(cfg.EmerCoolInvert)
|
||||||
end
|
end
|
||||||
|
|
||||||
return cfv.valid()
|
return cfv.valid()
|
||||||
@@ -116,7 +125,7 @@ function plc.rps_init(reactor, is_formed)
|
|||||||
reactor_enabled = false,
|
reactor_enabled = false,
|
||||||
enabled_at = 0,
|
enabled_at = 0,
|
||||||
emer_cool_active = nil, ---@type boolean
|
emer_cool_active = nil, ---@type boolean
|
||||||
formed = is_formed,
|
formed = is_formed, ---@type boolean|nil
|
||||||
force_disabled = false,
|
force_disabled = false,
|
||||||
tripped = false,
|
tripped = false,
|
||||||
trip_cause = "ok" ---@type rps_trip_cause
|
trip_cause = "ok" ---@type rps_trip_cause
|
||||||
@@ -166,7 +175,8 @@ function plc.rps_init(reactor, is_formed)
|
|||||||
local function _set_emer_cool(state)
|
local function _set_emer_cool(state)
|
||||||
-- check if this was configured: if it's a table, fields have already been validated.
|
-- check if this was configured: if it's a table, fields have already been validated.
|
||||||
if config.EmerCoolEnable then
|
if config.EmerCoolEnable then
|
||||||
local level = rsio.digital_write_active(rsio.IO.U_EMER_COOL, state)
|
-- use ~= as XOR for simple inversion
|
||||||
|
local level = rsio.digital_write_active(rsio.IO.U_EMER_COOL, config.EmerCoolInvert ~= state)
|
||||||
|
|
||||||
if level ~= false then
|
if level ~= false then
|
||||||
if rsio.is_color(config.EmerCoolColor) then
|
if rsio.is_color(config.EmerCoolColor) then
|
||||||
@@ -361,29 +371,35 @@ function plc.rps_init(reactor, is_formed)
|
|||||||
return public.activate()
|
return public.activate()
|
||||||
end
|
end
|
||||||
|
|
||||||
-- check all safety conditions
|
-- check all safety conditions if we have a formed reactor, otherwise handle a subset of conditions
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
|
---@param has_reactor boolean if the PLC state indicates we have a reactor
|
||||||
---@return boolean tripped, rps_trip_cause trip_status, boolean first_trip
|
---@return boolean tripped, rps_trip_cause trip_status, boolean first_trip
|
||||||
function public.check()
|
function public.check(has_reactor)
|
||||||
local status = RPS_TRIP_CAUSE.OK
|
local status = RPS_TRIP_CAUSE.OK
|
||||||
local was_tripped = self.tripped
|
local was_tripped = self.tripped
|
||||||
local first_trip = false
|
local first_trip = false
|
||||||
|
|
||||||
if self.formed then
|
if has_reactor then
|
||||||
-- update state
|
if self.formed then
|
||||||
parallel.waitForAll(
|
-- update state
|
||||||
_is_formed,
|
parallel.waitForAll(
|
||||||
_is_force_disabled,
|
_is_formed,
|
||||||
_high_damage,
|
_is_force_disabled,
|
||||||
_high_temp,
|
_high_damage,
|
||||||
_low_coolant,
|
_high_temp,
|
||||||
_excess_waste,
|
_low_coolant,
|
||||||
_excess_heated_coolant,
|
_excess_waste,
|
||||||
_insufficient_fuel
|
_excess_heated_coolant,
|
||||||
)
|
_insufficient_fuel
|
||||||
|
)
|
||||||
|
else
|
||||||
|
-- check to see if its now formed
|
||||||
|
_is_formed()
|
||||||
|
end
|
||||||
else
|
else
|
||||||
-- check to see if its now formed
|
self.formed = nil
|
||||||
_is_formed()
|
self.state[CHK.SYS_FAIL] = true
|
||||||
end
|
end
|
||||||
|
|
||||||
-- check system states in order of severity
|
-- check system states in order of severity
|
||||||
@@ -471,6 +487,7 @@ function plc.rps_init(reactor, is_formed)
|
|||||||
---@nodiscard
|
---@nodiscard
|
||||||
function public.is_active() return self.reactor_enabled end
|
function public.is_active() return self.reactor_enabled end
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
|
---@return boolean|nil formed true if formed, false if not, nil if unknown
|
||||||
function public.is_formed() return self.formed end
|
function public.is_formed() return self.formed end
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
function public.is_force_disabled() return self.force_disabled end
|
function public.is_force_disabled() return self.force_disabled end
|
||||||
@@ -492,14 +509,14 @@ function plc.rps_init(reactor, is_formed)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- partial RPS reset that only clears fault and sys_fail
|
-- partial RPS reset that only clears fault and sys_fail
|
||||||
function public.reset_formed()
|
function public.reset_reattach()
|
||||||
self.tripped = false
|
self.tripped = false
|
||||||
self.trip_cause = RPS_TRIP_CAUSE.OK
|
self.trip_cause = RPS_TRIP_CAUSE.OK
|
||||||
|
|
||||||
self.state[CHK.FAULT] = false
|
self.state[CHK.FAULT] = false
|
||||||
self.state[CHK.SYS_FAIL] = false
|
self.state[CHK.SYS_FAIL] = false
|
||||||
|
|
||||||
log.info("RPS: partial reset on formed")
|
log.info("RPS: partial reset on connected or formed")
|
||||||
end
|
end
|
||||||
|
|
||||||
-- reset the automatic and timeout trip flags, then clear trip if that was the trip cause
|
-- reset the automatic and timeout trip flags, then clear trip if that was the trip cause
|
||||||
@@ -542,13 +559,11 @@ function plc.comms(version, nic, reactor, rps, conn_watchdog)
|
|||||||
max_burn_rate = nil
|
max_burn_rate = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
comms.set_trusted_range(config.TrustedRange)
|
if config.WirelessModem then
|
||||||
|
comms.set_trusted_range(config.TrustedRange)
|
||||||
|
end
|
||||||
|
|
||||||
-- PRIVATE FUNCTIONS --
|
--#region PRIVATE FUNCTIONS --
|
||||||
|
|
||||||
-- configure network channels
|
|
||||||
nic.closeAll()
|
|
||||||
nic.open(config.PLC_Channel)
|
|
||||||
|
|
||||||
-- send an RPLC packet
|
-- send an RPLC packet
|
||||||
---@param msg_type RPLC_TYPE
|
---@param msg_type RPLC_TYPE
|
||||||
@@ -581,11 +596,7 @@ function plc.comms(version, nic, reactor, rps, conn_watchdog)
|
|||||||
-- dynamic reactor status information, excluding heating rate
|
-- dynamic reactor status information, excluding heating rate
|
||||||
---@return table data_table, boolean faulted
|
---@return table data_table, boolean faulted
|
||||||
local function _get_reactor_status()
|
local function _get_reactor_status()
|
||||||
local fuel = nil
|
local fuel, waste, coolant, hcoolant = nil, nil, nil, nil
|
||||||
local waste = nil
|
|
||||||
local coolant = nil
|
|
||||||
local hcoolant = nil
|
|
||||||
|
|
||||||
local data_table = {}
|
local data_table = {}
|
||||||
|
|
||||||
reactor.__p_disable_afc()
|
reactor.__p_disable_afc()
|
||||||
@@ -704,11 +715,126 @@ function plc.comms(version, nic, reactor, rps, conn_watchdog)
|
|||||||
reactor.__p_enable_afc()
|
reactor.__p_enable_afc()
|
||||||
end
|
end
|
||||||
|
|
||||||
-- PUBLIC FUNCTIONS --
|
-- handle a burn rate command
|
||||||
|
---@param packet rplc_frame
|
||||||
|
---@param setpoints plc_setpoints
|
||||||
|
--- EVENT_CONSUMER: this function consumes events
|
||||||
|
local function _handle_burn_rate(packet, setpoints)
|
||||||
|
if (packet.length == 2) and (type(packet.data[1]) == "number") then
|
||||||
|
local success = false
|
||||||
|
local burn_rate = math.floor(packet.data[1] * 10) / 10
|
||||||
|
local ramp = packet.data[2]
|
||||||
|
|
||||||
|
-- if no known max burn rate, check again
|
||||||
|
if self.max_burn_rate == nil then
|
||||||
|
self.max_burn_rate = reactor.getMaxBurnRate()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- if we know our max burn rate, update current burn rate setpoint if in range
|
||||||
|
if self.max_burn_rate ~= ppm.ACCESS_FAULT then
|
||||||
|
if burn_rate > 0 and burn_rate <= self.max_burn_rate then
|
||||||
|
if ramp then
|
||||||
|
setpoints.burn_rate_en = true
|
||||||
|
setpoints.burn_rate = burn_rate
|
||||||
|
success = true
|
||||||
|
else
|
||||||
|
reactor.setBurnRate(burn_rate)
|
||||||
|
success = reactor.__p_is_ok()
|
||||||
|
end
|
||||||
|
else
|
||||||
|
log.debug(burn_rate .. " rate outside of 0 < x <= " .. self.max_burn_rate)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
_send_ack(packet.type, success)
|
||||||
|
else
|
||||||
|
log.debug("RPLC set burn rate packet length mismatch or non-numeric burn rate")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- handle an auto burn rate command
|
||||||
|
---@param packet rplc_frame
|
||||||
|
---@param setpoints plc_setpoints
|
||||||
|
--- EVENT_CONSUMER: this function consumes events
|
||||||
|
local function _handle_auto_burn_rate(packet, setpoints)
|
||||||
|
if (packet.length == 3) and (type(packet.data[1]) == "number") and (type(packet.data[3]) == "number") then
|
||||||
|
local ack = AUTO_ACK.FAIL
|
||||||
|
local burn_rate = math.floor(packet.data[1] * 100) / 100
|
||||||
|
local ramp = packet.data[2]
|
||||||
|
self.auto_ack_token = packet.data[3]
|
||||||
|
|
||||||
|
-- if no known max burn rate, check again
|
||||||
|
if self.max_burn_rate == nil then
|
||||||
|
self.max_burn_rate = reactor.getMaxBurnRate()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- if we know our max burn rate, update current burn rate setpoint if in range
|
||||||
|
if self.max_burn_rate ~= ppm.ACCESS_FAULT then
|
||||||
|
if burn_rate < 0.01 then
|
||||||
|
if rps.is_active() then
|
||||||
|
-- auto scram to disable
|
||||||
|
log.debug("AUTO: stopping the reactor to meet 0.0 burn rate")
|
||||||
|
if rps.scram() then
|
||||||
|
ack = AUTO_ACK.ZERO_DIS_OK
|
||||||
|
else
|
||||||
|
log.warning("AUTO: automatic reactor stop failed")
|
||||||
|
end
|
||||||
|
else
|
||||||
|
ack = AUTO_ACK.ZERO_DIS_OK
|
||||||
|
end
|
||||||
|
elseif burn_rate <= self.max_burn_rate then
|
||||||
|
if not rps.is_active() then
|
||||||
|
-- activate the reactor
|
||||||
|
log.debug("AUTO: activating the reactor")
|
||||||
|
|
||||||
|
reactor.setBurnRate(0.01)
|
||||||
|
if reactor.__p_is_faulted() then
|
||||||
|
log.warning("AUTO: failed to reset burn rate for auto activation")
|
||||||
|
else
|
||||||
|
if not rps.auto_activate() then
|
||||||
|
log.warning("AUTO: automatic reactor activation failed")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- if active, set/ramp burn rate
|
||||||
|
if rps.is_active() then
|
||||||
|
if ramp then
|
||||||
|
log.debug(util.c("AUTO: setting burn rate ramp to ", burn_rate))
|
||||||
|
setpoints.burn_rate_en = true
|
||||||
|
setpoints.burn_rate = burn_rate
|
||||||
|
ack = AUTO_ACK.RAMP_SET_OK
|
||||||
|
else
|
||||||
|
log.debug(util.c("AUTO: setting burn rate directly to ", burn_rate))
|
||||||
|
reactor.setBurnRate(burn_rate)
|
||||||
|
ack = util.trinary(reactor.__p_is_faulted(), AUTO_ACK.FAIL, AUTO_ACK.DIRECT_SET_OK)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
log.debug(util.c(burn_rate, " rate outside of 0 < x <= ", self.max_burn_rate))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
_send_ack(packet.type, ack)
|
||||||
|
else
|
||||||
|
log.debug("RPLC set automatic burn rate packet length mismatch or non-numeric burn rate")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
|
--#region PUBLIC FUNCTIONS --
|
||||||
|
|
||||||
---@class plc_comms
|
---@class plc_comms
|
||||||
local public = {}
|
local public = {}
|
||||||
|
|
||||||
|
-- switch the current active NIC
|
||||||
|
---@param act_nic nic
|
||||||
|
function public.switch_nic(act_nic)
|
||||||
|
public.close()
|
||||||
|
nic = act_nic
|
||||||
|
end
|
||||||
|
|
||||||
-- reconnect a newly connected reactor
|
-- reconnect a newly connected reactor
|
||||||
---@param new_reactor table
|
---@param new_reactor table
|
||||||
function public.reconnect_reactor(new_reactor)
|
function public.reconnect_reactor(new_reactor)
|
||||||
@@ -745,8 +871,8 @@ function plc.comms(version, nic, reactor, rps, conn_watchdog)
|
|||||||
---@param formed boolean reactor formed (from PLC state)
|
---@param formed boolean reactor formed (from PLC state)
|
||||||
function public.send_status(no_reactor, formed)
|
function public.send_status(no_reactor, formed)
|
||||||
if self.linked then
|
if self.linked then
|
||||||
local mek_data = nil ---@type table
|
local mek_data = nil ---@type table
|
||||||
local heating_rate = 0.0 ---@type number
|
local heating_rate = 0.0 ---@type number
|
||||||
|
|
||||||
if (not no_reactor) and rps.is_formed() then
|
if (not no_reactor) and rps.is_formed() then
|
||||||
if _update_status_cache() then mek_data = self.status_cache end
|
if _update_status_cache() then mek_data = self.status_cache end
|
||||||
@@ -800,15 +926,11 @@ function plc.comms(version, nic, reactor, rps, conn_watchdog)
|
|||||||
-- get as RPLC packet
|
-- get as RPLC packet
|
||||||
if s_pkt.protocol() == PROTOCOL.RPLC then
|
if s_pkt.protocol() == PROTOCOL.RPLC then
|
||||||
local rplc_pkt = comms.rplc_packet()
|
local rplc_pkt = comms.rplc_packet()
|
||||||
if rplc_pkt.decode(s_pkt) then
|
if rplc_pkt.decode(s_pkt) then pkt = rplc_pkt.get() end
|
||||||
pkt = rplc_pkt.get()
|
|
||||||
end
|
|
||||||
-- get as SCADA management packet
|
-- get as SCADA management packet
|
||||||
elseif s_pkt.protocol() == PROTOCOL.SCADA_MGMT then
|
elseif s_pkt.protocol() == PROTOCOL.SCADA_MGMT then
|
||||||
local mgmt_pkt = comms.mgmt_packet()
|
local mgmt_pkt = comms.mgmt_packet()
|
||||||
if mgmt_pkt.decode(s_pkt) then
|
if mgmt_pkt.decode(s_pkt) then pkt = mgmt_pkt.get() end
|
||||||
pkt = mgmt_pkt.get()
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
log.debug("unsupported packet type " .. s_pkt.protocol(), true)
|
log.debug("unsupported packet type " .. s_pkt.protocol(), true)
|
||||||
end
|
end
|
||||||
@@ -820,16 +942,13 @@ function plc.comms(version, nic, reactor, rps, conn_watchdog)
|
|||||||
-- handle RPLC and MGMT packets
|
-- handle RPLC and MGMT packets
|
||||||
---@param packet rplc_frame|mgmt_frame packet frame
|
---@param packet rplc_frame|mgmt_frame packet frame
|
||||||
---@param plc_state plc_state PLC state
|
---@param plc_state plc_state PLC state
|
||||||
---@param setpoints setpoints setpoint control table
|
---@param setpoints plc_setpoints setpoint control table
|
||||||
function public.handle_packet(packet, plc_state, setpoints)
|
---@param println_ts function console print, when UI isn't running
|
||||||
-- print a log message to the terminal as long as the UI isn't running
|
function public.handle_packet(packet, plc_state, setpoints, println_ts)
|
||||||
local function println_ts(message) if not plc_state.fp_ok then util.println_ts(message) end end
|
|
||||||
|
|
||||||
local protocol = packet.scada_frame.protocol()
|
local protocol = packet.scada_frame.protocol()
|
||||||
local l_chan = packet.scada_frame.local_channel()
|
local l_chan = packet.scada_frame.local_channel()
|
||||||
local src_addr = packet.scada_frame.src_addr()
|
local src_addr = packet.scada_frame.src_addr()
|
||||||
|
|
||||||
-- handle packets now that we have prints setup
|
|
||||||
if l_chan == config.PLC_Channel then
|
if l_chan == config.PLC_Channel then
|
||||||
-- check sequence number
|
-- check sequence number
|
||||||
if self.r_seq_num == nil then
|
if self.r_seq_num == nil then
|
||||||
@@ -864,36 +983,7 @@ function plc.comms(version, nic, reactor, rps, conn_watchdog)
|
|||||||
log.debug("sent out structure again, did supervisor miss it?")
|
log.debug("sent out structure again, did supervisor miss it?")
|
||||||
elseif packet.type == RPLC_TYPE.MEK_BURN_RATE then
|
elseif packet.type == RPLC_TYPE.MEK_BURN_RATE then
|
||||||
-- set the burn rate
|
-- set the burn rate
|
||||||
if (packet.length == 2) and (type(packet.data[1]) == "number") then
|
_handle_burn_rate(packet, setpoints)
|
||||||
local success = false
|
|
||||||
local burn_rate = math.floor(packet.data[1] * 10) / 10
|
|
||||||
local ramp = packet.data[2]
|
|
||||||
|
|
||||||
-- if no known max burn rate, check again
|
|
||||||
if self.max_burn_rate == nil then
|
|
||||||
self.max_burn_rate = reactor.getMaxBurnRate()
|
|
||||||
end
|
|
||||||
|
|
||||||
-- if we know our max burn rate, update current burn rate setpoint if in range
|
|
||||||
if self.max_burn_rate ~= ppm.ACCESS_FAULT then
|
|
||||||
if burn_rate > 0 and burn_rate <= self.max_burn_rate then
|
|
||||||
if ramp then
|
|
||||||
setpoints.burn_rate_en = true
|
|
||||||
setpoints.burn_rate = burn_rate
|
|
||||||
success = true
|
|
||||||
else
|
|
||||||
reactor.setBurnRate(burn_rate)
|
|
||||||
success = reactor.__p_is_ok()
|
|
||||||
end
|
|
||||||
else
|
|
||||||
log.debug(burn_rate .. " rate outside of 0 < x <= " .. self.max_burn_rate)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
_send_ack(packet.type, success)
|
|
||||||
else
|
|
||||||
log.debug("RPLC set burn rate packet length mismatch or non-numeric burn rate")
|
|
||||||
end
|
|
||||||
elseif packet.type == RPLC_TYPE.RPS_ENABLE then
|
elseif packet.type == RPLC_TYPE.RPS_ENABLE then
|
||||||
-- enable the reactor
|
-- enable the reactor
|
||||||
self.scrammed = false
|
self.scrammed = false
|
||||||
@@ -922,68 +1012,7 @@ function plc.comms(version, nic, reactor, rps, conn_watchdog)
|
|||||||
_send_ack(packet.type, true)
|
_send_ack(packet.type, true)
|
||||||
elseif packet.type == RPLC_TYPE.AUTO_BURN_RATE then
|
elseif packet.type == RPLC_TYPE.AUTO_BURN_RATE then
|
||||||
-- automatic control requested a new burn rate
|
-- automatic control requested a new burn rate
|
||||||
if (packet.length == 3) and (type(packet.data[1]) == "number") and (type(packet.data[3]) == "number") then
|
_handle_auto_burn_rate(packet, setpoints)
|
||||||
local ack = AUTO_ACK.FAIL
|
|
||||||
local burn_rate = math.floor(packet.data[1] * 100) / 100
|
|
||||||
local ramp = packet.data[2]
|
|
||||||
self.auto_ack_token = packet.data[3]
|
|
||||||
|
|
||||||
-- if no known max burn rate, check again
|
|
||||||
if self.max_burn_rate == nil then
|
|
||||||
self.max_burn_rate = reactor.getMaxBurnRate()
|
|
||||||
end
|
|
||||||
|
|
||||||
-- if we know our max burn rate, update current burn rate setpoint if in range
|
|
||||||
if self.max_burn_rate ~= ppm.ACCESS_FAULT then
|
|
||||||
if burn_rate < 0.01 then
|
|
||||||
if rps.is_active() then
|
|
||||||
-- auto scram to disable
|
|
||||||
log.debug("AUTO: stopping the reactor to meet 0.0 burn rate")
|
|
||||||
if rps.scram() then
|
|
||||||
ack = AUTO_ACK.ZERO_DIS_OK
|
|
||||||
else
|
|
||||||
log.warning("AUTO: automatic reactor stop failed")
|
|
||||||
end
|
|
||||||
else
|
|
||||||
ack = AUTO_ACK.ZERO_DIS_OK
|
|
||||||
end
|
|
||||||
elseif burn_rate <= self.max_burn_rate then
|
|
||||||
if not rps.is_active() then
|
|
||||||
-- activate the reactor
|
|
||||||
log.debug("AUTO: activating the reactor")
|
|
||||||
|
|
||||||
reactor.setBurnRate(0.01)
|
|
||||||
if reactor.__p_is_faulted() then
|
|
||||||
log.warning("AUTO: failed to reset burn rate for auto activation")
|
|
||||||
else
|
|
||||||
if not rps.auto_activate() then
|
|
||||||
log.warning("AUTO: automatic reactor activation failed")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- if active, set/ramp burn rate
|
|
||||||
if rps.is_active() then
|
|
||||||
if ramp then
|
|
||||||
log.debug(util.c("AUTO: setting burn rate ramp to ", burn_rate))
|
|
||||||
setpoints.burn_rate_en = true
|
|
||||||
setpoints.burn_rate = burn_rate
|
|
||||||
ack = AUTO_ACK.RAMP_SET_OK
|
|
||||||
else
|
|
||||||
log.debug(util.c("AUTO: setting burn rate directly to ", burn_rate))
|
|
||||||
reactor.setBurnRate(burn_rate)
|
|
||||||
ack = util.trinary(reactor.__p_is_faulted(), AUTO_ACK.FAIL, AUTO_ACK.DIRECT_SET_OK)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
log.debug(util.c(burn_rate, " rate outside of 0 < x <= ", self.max_burn_rate))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
_send_ack(packet.type, ack)
|
|
||||||
else
|
|
||||||
log.debug("RPLC set automatic burn rate packet length mismatch or non-numeric burn rate")
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
log.debug("received unknown RPLC packet type " .. packet.type)
|
log.debug("received unknown RPLC packet type " .. packet.type)
|
||||||
end
|
end
|
||||||
@@ -1083,6 +1112,8 @@ function plc.comms(version, nic, reactor, rps, conn_watchdog)
|
|||||||
---@nodiscard
|
---@nodiscard
|
||||||
function public.is_linked() return self.linked end
|
function public.is_linked() return self.linked end
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
return public
|
return public
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -18,15 +18,14 @@ local ui = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
-- try to start the UI
|
-- try to start the UI
|
||||||
---@param theme FP_THEME front panel theme
|
---@param config plc_config configuration
|
||||||
---@param color_mode COLOR_MODE color mode
|
|
||||||
---@return boolean success, any error_msg
|
---@return boolean success, any error_msg
|
||||||
function renderer.try_start_ui(theme, color_mode)
|
function renderer.try_start_ui(config)
|
||||||
local status, msg = true, nil
|
local status, msg = true, nil
|
||||||
|
|
||||||
if ui.display == nil then
|
if ui.display == nil then
|
||||||
-- set theme
|
-- set theme
|
||||||
style.set_theme(theme, color_mode)
|
style.set_theme(config.FrontPanelTheme, config.ColorMode)
|
||||||
|
|
||||||
-- reset terminal
|
-- reset terminal
|
||||||
term.setTextColor(colors.white)
|
term.setTextColor(colors.white)
|
||||||
@@ -40,7 +39,7 @@ function renderer.try_start_ui(theme, color_mode)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- apply color mode
|
-- apply color mode
|
||||||
local c_mode_overrides = style.theme.color_modes[color_mode]
|
local c_mode_overrides = style.theme.color_modes[config.ColorMode]
|
||||||
for i = 1, #c_mode_overrides do
|
for i = 1, #c_mode_overrides do
|
||||||
term.setPaletteColor(c_mode_overrides[i].c, c_mode_overrides[i].hex)
|
term.setPaletteColor(c_mode_overrides[i].c, c_mode_overrides[i].hex)
|
||||||
end
|
end
|
||||||
@@ -48,7 +47,7 @@ function renderer.try_start_ui(theme, color_mode)
|
|||||||
-- init front panel view
|
-- init front panel view
|
||||||
status, msg = pcall(function ()
|
status, msg = pcall(function ()
|
||||||
ui.display = DisplayBox{window=term.current(),fg_bg=style.fp.root}
|
ui.display = DisplayBox{window=term.current(),fg_bg=style.fp.root}
|
||||||
panel_view(ui.display)
|
panel_view(ui.display, config)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
if status then
|
if status then
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
--
|
--
|
||||||
|
|
||||||
require("/initenv").init_env()
|
require("/initenv").init_env()
|
||||||
|
local backplane = require("reactor-plc.backplane")
|
||||||
|
|
||||||
local comms = require("scada-common.comms")
|
local comms = require("scada-common.comms")
|
||||||
local crash = require("scada-common.crash")
|
local crash = require("scada-common.crash")
|
||||||
@@ -18,7 +19,7 @@ local plc = require("reactor-plc.plc")
|
|||||||
local renderer = require("reactor-plc.renderer")
|
local renderer = require("reactor-plc.renderer")
|
||||||
local threads = require("reactor-plc.threads")
|
local threads = require("reactor-plc.threads")
|
||||||
|
|
||||||
local R_PLC_VERSION = "v1.8.19"
|
local R_PLC_VERSION = "v1.10.0"
|
||||||
|
|
||||||
local println = util.println
|
local println = util.println
|
||||||
local println_ts = util.println_ts
|
local println_ts = util.println_ts
|
||||||
@@ -66,7 +67,7 @@ local function main()
|
|||||||
-- startup
|
-- startup
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
|
|
||||||
-- record firmware versions and ID
|
-- report versions and ID
|
||||||
databus.tx_versions(R_PLC_VERSION, comms.version)
|
databus.tx_versions(R_PLC_VERSION, comms.version)
|
||||||
databus.tx_id(config.UnitID)
|
databus.tx_id(config.UnitID)
|
||||||
|
|
||||||
@@ -87,32 +88,32 @@ local function main()
|
|||||||
-- PLC system state flags
|
-- PLC system state flags
|
||||||
---@class plc_state
|
---@class plc_state
|
||||||
plc_state = {
|
plc_state = {
|
||||||
init_ok = true,
|
|
||||||
fp_ok = false,
|
fp_ok = false,
|
||||||
shutdown = false,
|
shutdown = false,
|
||||||
degraded = true,
|
degraded = true,
|
||||||
reactor_formed = true,
|
|
||||||
no_reactor = true,
|
no_reactor = true,
|
||||||
no_modem = true
|
reactor_formed = true,
|
||||||
|
wd_modem = false,
|
||||||
|
wl_modem = false
|
||||||
},
|
},
|
||||||
|
|
||||||
-- control setpoints
|
-- control setpoints
|
||||||
---@class setpoints
|
---@class plc_setpoints
|
||||||
setpoints = {
|
setpoints = {
|
||||||
burn_rate_en = false,
|
burn_rate_en = false,
|
||||||
burn_rate = 0.0
|
burn_rate = 0.0
|
||||||
},
|
},
|
||||||
|
|
||||||
-- core PLC devices
|
-- global PLC devices, still initialized by the backplane
|
||||||
|
---@class plc_dev
|
||||||
plc_dev = {
|
plc_dev = {
|
||||||
reactor = ppm.get_fission_reactor(),
|
reactor = nil ---@type table
|
||||||
modem = ppm.get_wireless_modem()
|
|
||||||
},
|
},
|
||||||
|
|
||||||
-- system objects
|
-- system objects
|
||||||
|
---@class plc_sys
|
||||||
plc_sys = {
|
plc_sys = {
|
||||||
rps = nil, ---@type rps
|
rps = nil, ---@type rps
|
||||||
nic = nil, ---@type nic
|
|
||||||
plc_comms = nil, ---@type plc_comms
|
plc_comms = nil, ---@type plc_comms
|
||||||
conn_watchdog = nil ---@type watchdog
|
conn_watchdog = nil ---@type watchdog
|
||||||
},
|
},
|
||||||
@@ -122,6 +123,18 @@ local function main()
|
|||||||
mq_rps = mqueue.new(),
|
mq_rps = mqueue.new(),
|
||||||
mq_comms_tx = mqueue.new(),
|
mq_comms_tx = mqueue.new(),
|
||||||
mq_comms_rx = mqueue.new()
|
mq_comms_rx = mqueue.new()
|
||||||
|
},
|
||||||
|
|
||||||
|
-- message queue message types
|
||||||
|
q_types = {
|
||||||
|
MQ__RPS_CMD = {
|
||||||
|
SCRAM = 1,
|
||||||
|
DEGRADED_SCRAM = 2,
|
||||||
|
TRIP_TIMEOUT = 3
|
||||||
|
},
|
||||||
|
MQ__COMM_CMD = {
|
||||||
|
SEND_STATUS = 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,110 +143,65 @@ local function main()
|
|||||||
|
|
||||||
local plc_state = __shared_memory.plc_state
|
local plc_state = __shared_memory.plc_state
|
||||||
|
|
||||||
-- initial state evaluation
|
-- reactor and modem initialization
|
||||||
plc_state.no_reactor = smem_dev.reactor == nil
|
backplane.init(config, __shared_memory)
|
||||||
plc_state.no_modem = smem_dev.modem == nil
|
|
||||||
|
|
||||||
-- we need a reactor, can at least do some things even if it isn't formed though
|
-- scram on boot if networked, otherwise leave the reactor be
|
||||||
if plc_state.no_reactor then
|
if __shared_memory.networked and (not plc_state.no_reactor) and plc_state.reactor_formed and smem_dev.reactor.getStatus() then
|
||||||
println("init> fission reactor not found")
|
log.debug("startup> power-on SCRAM")
|
||||||
log.warning("init> no reactor on startup")
|
smem_dev.reactor.scram()
|
||||||
|
|
||||||
plc_state.init_ok = false
|
|
||||||
plc_state.degraded = true
|
|
||||||
elseif not smem_dev.reactor.isFormed() then
|
|
||||||
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
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- modem is required if networked
|
-- setup front panel
|
||||||
if __shared_memory.networked and plc_state.no_modem then
|
local message
|
||||||
println("init> wireless modem not found")
|
plc_state.fp_ok, message = renderer.try_start_ui(config)
|
||||||
log.warning("init> no wireless modem on startup")
|
|
||||||
|
|
||||||
-- scram reactor if present and enabled
|
-- ...or not
|
||||||
if (smem_dev.reactor ~= nil) and plc_state.reactor_formed and smem_dev.reactor.getStatus() then
|
if not plc_state.fp_ok then
|
||||||
smem_dev.reactor.scram()
|
println_ts(util.c("UI error: ", message))
|
||||||
end
|
println("startup> running without front panel")
|
||||||
|
log.error(util.c("front panel GUI render failed with error ", message))
|
||||||
plc_state.init_ok = false
|
log.info("startup> running in headless mode without front panel")
|
||||||
plc_state.degraded = true
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- print a log message to the terminal as long as the UI isn't running
|
-- print a log message to the terminal as long as the UI isn't running
|
||||||
local function _println_no_fp(message) if not plc_state.fp_ok then println(message) end end
|
local function _println_no_fp(msg) if not plc_state.fp_ok then println(msg) end end
|
||||||
|
|
||||||
-- PLC init<br>
|
----------------------------------------
|
||||||
--- EVENT_CONSUMER: this function consumes events
|
-- initialize PLC
|
||||||
local function init()
|
----------------------------------------
|
||||||
-- scram on boot if networked, otherwise leave the reactor be
|
|
||||||
if __shared_memory.networked and (not plc_state.no_reactor) and plc_state.reactor_formed and smem_dev.reactor.getStatus() then
|
|
||||||
smem_dev.reactor.scram()
|
|
||||||
end
|
|
||||||
|
|
||||||
-- setup front panel
|
-- init reactor protection system
|
||||||
if not renderer.ui_ready() then
|
smem_sys.rps = plc.rps_init(smem_dev.reactor, util.trinary(plc_state.no_reactor, nil, plc_state.reactor_formed))
|
||||||
local message
|
log.debug("startup> rps init")
|
||||||
plc_state.fp_ok, message = renderer.try_start_ui(config.FrontPanelTheme, config.ColorMode)
|
|
||||||
|
|
||||||
-- ...or not
|
-- notify user of emergency coolant configuration status
|
||||||
if not plc_state.fp_ok then
|
if config.EmerCoolEnable then
|
||||||
println_ts(util.c("UI error: ", message))
|
_println_no_fp("startup> emergency coolant control ready")
|
||||||
println("init> running without front panel")
|
log.info("startup> emergency coolant control available")
|
||||||
log.error(util.c("front panel GUI render failed with error ", message))
|
|
||||||
log.info("init> running in headless mode without front panel")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if plc_state.init_ok then
|
|
||||||
-- init reactor protection system
|
|
||||||
smem_sys.rps = plc.rps_init(smem_dev.reactor, plc_state.reactor_formed)
|
|
||||||
log.debug("init> rps init")
|
|
||||||
|
|
||||||
if __shared_memory.networked then
|
|
||||||
-- comms watchdog
|
|
||||||
smem_sys.conn_watchdog = util.new_watchdog(config.ConnTimeout)
|
|
||||||
log.debug("init> conn watchdog started")
|
|
||||||
|
|
||||||
-- create network interface then setup comms
|
|
||||||
smem_sys.nic = network.nic(smem_dev.modem)
|
|
||||||
smem_sys.plc_comms = plc.comms(R_PLC_VERSION, smem_sys.nic, smem_dev.reactor, smem_sys.rps, smem_sys.conn_watchdog)
|
|
||||||
log.debug("init> comms init")
|
|
||||||
else
|
|
||||||
_println_no_fp("init> starting in offline mode")
|
|
||||||
log.info("init> running without networking")
|
|
||||||
end
|
|
||||||
|
|
||||||
-- notify user of emergency coolant configuration status
|
|
||||||
if config.EmerCoolEnable then
|
|
||||||
println("init> emergency coolant control ready")
|
|
||||||
log.info("init> running with emergency coolant control available")
|
|
||||||
end
|
|
||||||
|
|
||||||
util.push_event("clock_start")
|
|
||||||
|
|
||||||
_println_no_fp("init> completed")
|
|
||||||
log.info("init> startup completed")
|
|
||||||
else
|
|
||||||
_println_no_fp("init> system in degraded state, awaiting devices...")
|
|
||||||
log.warning("init> started in a degraded state, awaiting peripheral connections...")
|
|
||||||
end
|
|
||||||
|
|
||||||
databus.tx_hw_status(plc_state)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
----------------------------------------
|
-- conditionally init comms
|
||||||
-- start system
|
if __shared_memory.networked then
|
||||||
----------------------------------------
|
-- comms watchdog
|
||||||
|
smem_sys.conn_watchdog = util.new_watchdog(config.ConnTimeout)
|
||||||
|
log.debug("startup> conn watchdog started")
|
||||||
|
|
||||||
-- initialize PLC
|
-- create network interface then setup comms
|
||||||
init()
|
smem_sys.plc_comms = plc.comms(R_PLC_VERSION, backplane.active_nic(), smem_dev.reactor, smem_sys.rps, smem_sys.conn_watchdog)
|
||||||
|
log.debug("startup> comms init")
|
||||||
|
else
|
||||||
|
_println_no_fp("startup> starting in non-networked mode")
|
||||||
|
log.info("startup> starting without networking")
|
||||||
|
end
|
||||||
|
|
||||||
|
databus.tx_hw_status(plc_state)
|
||||||
|
|
||||||
|
_println_no_fp("startup> completed")
|
||||||
|
log.info("startup> completed")
|
||||||
|
|
||||||
-- init threads
|
-- init threads
|
||||||
local main_thread = threads.thread__main(__shared_memory, init)
|
local main_thread = threads.thread__main(__shared_memory)
|
||||||
local rps_thread = threads.thread__rps(__shared_memory)
|
local rps_thread = threads.thread__rps(__shared_memory)
|
||||||
|
|
||||||
if __shared_memory.networked then
|
if __shared_memory.networked then
|
||||||
@@ -247,14 +215,12 @@ local function main()
|
|||||||
-- run threads
|
-- run threads
|
||||||
parallel.waitForAll(main_thread.p_exec, rps_thread.p_exec, comms_thread_tx.p_exec, comms_thread_rx.p_exec, sp_ctrl_thread.p_exec)
|
parallel.waitForAll(main_thread.p_exec, rps_thread.p_exec, comms_thread_tx.p_exec, comms_thread_rx.p_exec, sp_ctrl_thread.p_exec)
|
||||||
|
|
||||||
if plc_state.init_ok then
|
-- send status one last time after RPS shutdown
|
||||||
-- send status one last time after RPS shutdown
|
smem_sys.plc_comms.send_status(plc_state.no_reactor, plc_state.reactor_formed)
|
||||||
smem_sys.plc_comms.send_status(plc_state.no_reactor, plc_state.reactor_formed)
|
smem_sys.plc_comms.send_rps_status()
|
||||||
smem_sys.plc_comms.send_rps_status()
|
|
||||||
|
|
||||||
-- close connection
|
-- close connection
|
||||||
smem_sys.plc_comms.close()
|
smem_sys.plc_comms.close()
|
||||||
end
|
|
||||||
else
|
else
|
||||||
-- run threads, excluding comms
|
-- run threads, excluding comms
|
||||||
parallel.waitForAll(main_thread.p_exec, rps_thread.p_exec)
|
parallel.waitForAll(main_thread.p_exec, rps_thread.p_exec)
|
||||||
|
|||||||
@@ -1,38 +1,28 @@
|
|||||||
local log = require("scada-common.log")
|
local log = require("scada-common.log")
|
||||||
local mqueue = require("scada-common.mqueue")
|
local mqueue = require("scada-common.mqueue")
|
||||||
local ppm = require("scada-common.ppm")
|
local ppm = require("scada-common.ppm")
|
||||||
local tcd = require("scada-common.tcd")
|
local tcd = require("scada-common.tcd")
|
||||||
local util = require("scada-common.util")
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
local databus = require("reactor-plc.databus")
|
local backplane = require("reactor-plc.backplane")
|
||||||
local renderer = require("reactor-plc.renderer")
|
local databus = require("reactor-plc.databus")
|
||||||
|
local renderer = require("reactor-plc.renderer")
|
||||||
|
|
||||||
local core = require("graphics.core")
|
local core = require("graphics.core")
|
||||||
|
|
||||||
local threads = {}
|
local threads = {}
|
||||||
|
|
||||||
local MAIN_CLOCK = 0.5 -- (2Hz, 10 ticks)
|
local MAIN_CLOCK = 0.5 -- 2Hz, 10 ticks
|
||||||
local RPS_SLEEP = 250 -- (250ms, 5 ticks)
|
local RPS_SLEEP = 250 -- 250ms, 5 ticks
|
||||||
local COMMS_SLEEP = 150 -- (150ms, 3 ticks)
|
local COMMS_SLEEP = 150 -- 150ms, 3 ticks
|
||||||
local SP_CTRL_SLEEP = 250 -- (250ms, 5 ticks)
|
local SP_CTRL_SLEEP = 250 -- 250ms, 5 ticks
|
||||||
|
|
||||||
local BURN_RATE_RAMP_mB_s = 5.0
|
local BURN_RATE_RAMP_mB_s = 5.0
|
||||||
|
|
||||||
local MQ__RPS_CMD = {
|
|
||||||
SCRAM = 1,
|
|
||||||
DEGRADED_SCRAM = 2,
|
|
||||||
TRIP_TIMEOUT = 3
|
|
||||||
}
|
|
||||||
|
|
||||||
local MQ__COMM_CMD = {
|
|
||||||
SEND_STATUS = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
-- main thread
|
-- main thread
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
---@param smem plc_shared_memory
|
---@param smem plc_shared_memory
|
||||||
---@param init function
|
function threads.thread__main(smem)
|
||||||
function threads.thread__main(smem, init)
|
|
||||||
-- print a log message to the terminal as long as the UI isn't running
|
-- print a log message to the terminal as long as the UI isn't running
|
||||||
local function println_ts(message) if not smem.plc_state.fp_ok then util.println_ts(message) end end
|
local function println_ts(message) if not smem.plc_state.fp_ok then util.println_ts(message) end end
|
||||||
|
|
||||||
@@ -42,32 +32,33 @@ function threads.thread__main(smem, init)
|
|||||||
-- execute thread
|
-- execute thread
|
||||||
function public.exec()
|
function public.exec()
|
||||||
databus.tx_rt_status("main", true)
|
databus.tx_rt_status("main", true)
|
||||||
log.debug("main thread init, clock inactive")
|
log.debug("OS: main thread start")
|
||||||
|
|
||||||
-- send status updates at 2Hz (every 10 server ticks) (every loop tick)
|
local LINK_TICKS = 2
|
||||||
-- send link requests at 0.5Hz (every 40 server ticks) (every 8 loop ticks)
|
|
||||||
local LINK_TICKS = 8
|
|
||||||
local ticks_to_update = 0
|
local ticks_to_update = 0
|
||||||
|
|
||||||
local loop_clock = util.new_clock(MAIN_CLOCK)
|
local loop_clock = util.new_clock(MAIN_CLOCK)
|
||||||
|
|
||||||
-- load in from shared memory
|
-- load in from shared memory
|
||||||
local networked = smem.networked
|
local networked = smem.networked
|
||||||
local plc_state = smem.plc_state
|
local plc_state = smem.plc_state
|
||||||
local plc_dev = smem.plc_dev
|
|
||||||
|
local rps = smem.plc_sys.rps
|
||||||
|
local plc_comms = smem.plc_sys.plc_comms
|
||||||
|
local conn_watchdog = smem.plc_sys.conn_watchdog
|
||||||
|
|
||||||
|
local MQ__RPS_CMD = smem.q_types.MQ__RPS_CMD
|
||||||
|
local MQ__COMM_CMD = smem.q_types.MQ__COMM_CMD
|
||||||
|
|
||||||
|
-- start clock
|
||||||
|
loop_clock.start()
|
||||||
|
|
||||||
-- event loop
|
-- event loop
|
||||||
while true do
|
while true do
|
||||||
-- get plc_sys fields (may have been set late due to degraded boot)
|
|
||||||
local rps = smem.plc_sys.rps
|
|
||||||
local nic = smem.plc_sys.nic
|
|
||||||
local plc_comms = smem.plc_sys.plc_comms
|
|
||||||
local conn_watchdog = smem.plc_sys.conn_watchdog
|
|
||||||
|
|
||||||
local event, param1, param2, param3, param4, param5 = util.pull_event()
|
local event, param1, param2, param3, param4, param5 = util.pull_event()
|
||||||
|
|
||||||
-- handle event
|
-- handle event
|
||||||
if event == "timer" and loop_clock.is_clock(param1) then
|
if event == "timer" and loop_clock.is_clock(param1) then
|
||||||
-- note: loop clock is only running if init_ok = true
|
|
||||||
-- blink heartbeat indicator
|
-- blink heartbeat indicator
|
||||||
databus.heartbeat()
|
databus.heartbeat()
|
||||||
|
|
||||||
@@ -75,10 +66,10 @@ function threads.thread__main(smem, init)
|
|||||||
loop_clock.start()
|
loop_clock.start()
|
||||||
|
|
||||||
-- send updated data
|
-- send updated data
|
||||||
if networked and nic.is_connected() then
|
if networked then
|
||||||
if plc_comms.is_linked() then
|
if plc_comms.is_linked() then
|
||||||
smem.q.mq_comms_tx.push_command(MQ__COMM_CMD.SEND_STATUS)
|
smem.q.mq_comms_tx.push_command(MQ__COMM_CMD.SEND_STATUS)
|
||||||
else
|
elseif backplane.active_nic().is_connected() then
|
||||||
if ticks_to_update == 0 then
|
if ticks_to_update == 0 then
|
||||||
plc_comms.send_link_req()
|
plc_comms.send_link_req()
|
||||||
ticks_to_update = LINK_TICKS
|
ticks_to_update = LINK_TICKS
|
||||||
@@ -93,23 +84,23 @@ function threads.thread__main(smem, init)
|
|||||||
-- reactor now formed
|
-- reactor now formed
|
||||||
plc_state.reactor_formed = true
|
plc_state.reactor_formed = true
|
||||||
|
|
||||||
println_ts("reactor is now formed.")
|
println_ts("reactor is now formed")
|
||||||
log.info("reactor is now formed")
|
log.info("reactor is now formed")
|
||||||
|
|
||||||
-- SCRAM newly formed reactor
|
-- SCRAM newly formed reactor
|
||||||
smem.q.mq_rps.push_command(MQ__RPS_CMD.SCRAM)
|
smem.q.mq_rps.push_command(MQ__RPS_CMD.SCRAM)
|
||||||
|
|
||||||
-- determine if we are still in a degraded state
|
-- determine if we are still in a degraded state
|
||||||
if (not networked) or nic.is_connected() then
|
if (not networked) or backplane.active_nic().is_connected() then
|
||||||
plc_state.degraded = false
|
plc_state.degraded = false
|
||||||
end
|
end
|
||||||
|
|
||||||
-- partial reset of RPS, specific to becoming formed
|
-- partial reset of RPS, specific to becoming formed
|
||||||
-- without this, auto control can't resume on chunk load
|
-- without this, auto control can't resume on chunk load
|
||||||
rps.reset_formed()
|
rps.reset_reattach()
|
||||||
elseif plc_state.reactor_formed and not rps.is_formed() then
|
elseif plc_state.reactor_formed and (rps.is_formed() == false) then
|
||||||
-- reactor no longer formed
|
-- reactor no longer formed
|
||||||
println_ts("reactor is no longer formed.")
|
println_ts("reactor is no longer formed")
|
||||||
log.info("reactor is no longer formed")
|
log.info("reactor is no longer formed")
|
||||||
|
|
||||||
plc_state.reactor_formed = false
|
plc_state.reactor_formed = false
|
||||||
@@ -118,14 +109,14 @@ function threads.thread__main(smem, init)
|
|||||||
|
|
||||||
-- update indicators
|
-- update indicators
|
||||||
databus.tx_hw_status(plc_state)
|
databus.tx_hw_status(plc_state)
|
||||||
elseif event == "modem_message" and networked and plc_state.init_ok and nic.is_connected() then
|
elseif event == "modem_message" and networked then
|
||||||
-- got a packet
|
-- got a packet
|
||||||
local packet = plc_comms.parse_packet(param1, param2, param3, param4, param5)
|
local packet = plc_comms.parse_packet(param1, param2, param3, param4, param5)
|
||||||
if packet ~= nil then
|
if packet ~= nil then
|
||||||
-- pass the packet onto the comms message queue
|
-- pass the packet onto the comms message queue
|
||||||
smem.q.mq_comms_rx.push_packet(packet)
|
smem.q.mq_comms_rx.push_packet(packet)
|
||||||
end
|
end
|
||||||
elseif event == "timer" and networked and plc_state.init_ok and conn_watchdog.is_timer(param1) then
|
elseif event == "timer" and networked and conn_watchdog.is_timer(param1) then
|
||||||
-- haven't heard from server recently? close connection and shutdown reactor
|
-- haven't heard from server recently? close connection and shutdown reactor
|
||||||
plc_comms.close()
|
plc_comms.close()
|
||||||
smem.q.mq_rps.push_command(MQ__RPS_CMD.TRIP_TIMEOUT)
|
smem.q.mq_rps.push_command(MQ__RPS_CMD.TRIP_TIMEOUT)
|
||||||
@@ -135,41 +126,8 @@ function threads.thread__main(smem, init)
|
|||||||
elseif event == "peripheral_detach" then
|
elseif event == "peripheral_detach" then
|
||||||
-- peripheral disconnect
|
-- peripheral disconnect
|
||||||
local type, device = ppm.handle_unmount(param1)
|
local type, device = ppm.handle_unmount(param1)
|
||||||
|
|
||||||
if type ~= nil and device ~= nil then
|
if type ~= nil and device ~= nil then
|
||||||
if device == plc_dev.reactor then
|
backplane.detach(param1, type, device, println_ts)
|
||||||
println_ts("reactor disconnected!")
|
|
||||||
log.error("reactor logic adapter disconnected")
|
|
||||||
|
|
||||||
plc_state.no_reactor = true
|
|
||||||
plc_state.degraded = true
|
|
||||||
elseif networked and type == "modem" then
|
|
||||||
---@cast device Modem
|
|
||||||
-- we only care if this is our wireless modem
|
|
||||||
-- note, check init_ok first since nic will be nil if it is false
|
|
||||||
if plc_state.init_ok and nic.is_modem(device) then
|
|
||||||
nic.disconnect()
|
|
||||||
|
|
||||||
println_ts("comms modem disconnected!")
|
|
||||||
log.warning("comms modem disconnected")
|
|
||||||
|
|
||||||
local other_modem = ppm.get_wireless_modem()
|
|
||||||
if other_modem then
|
|
||||||
log.info("found another wireless modem, using it for comms")
|
|
||||||
nic.connect(other_modem)
|
|
||||||
else
|
|
||||||
plc_state.no_modem = true
|
|
||||||
plc_state.degraded = true
|
|
||||||
|
|
||||||
if plc_state.init_ok then
|
|
||||||
-- try to scram reactor if it is still connected
|
|
||||||
smem.q.mq_rps.push_command(MQ__RPS_CMD.DEGRADED_SCRAM)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
log.warning("a modem was disconnected")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- update indicators
|
-- update indicators
|
||||||
@@ -177,66 +135,8 @@ function threads.thread__main(smem, init)
|
|||||||
elseif event == "peripheral" then
|
elseif event == "peripheral" then
|
||||||
-- peripheral connect
|
-- peripheral connect
|
||||||
local type, device = ppm.mount(param1)
|
local type, device = ppm.mount(param1)
|
||||||
|
|
||||||
if type ~= nil and device ~= nil then
|
if type ~= nil and device ~= nil then
|
||||||
if plc_state.no_reactor and (type == "fissionReactorLogicAdapter") then
|
backplane.attach(param1, type, device, println_ts)
|
||||||
-- reconnected reactor
|
|
||||||
plc_dev.reactor = device
|
|
||||||
plc_state.no_reactor = false
|
|
||||||
|
|
||||||
println_ts("reactor reconnected.")
|
|
||||||
log.info("reactor reconnected")
|
|
||||||
|
|
||||||
-- 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
|
|
||||||
|
|
||||||
-- determine if we are still in a degraded state
|
|
||||||
if (not networked or not plc_state.no_modem) and plc_state.reactor_formed then
|
|
||||||
plc_state.degraded = false
|
|
||||||
end
|
|
||||||
|
|
||||||
if plc_state.init_ok then
|
|
||||||
smem.q.mq_rps.push_command(MQ__RPS_CMD.SCRAM)
|
|
||||||
|
|
||||||
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/reconnected
|
|
||||||
-- without this, auto control can't resume on chunk load
|
|
||||||
rps.reset_formed()
|
|
||||||
end
|
|
||||||
elseif networked and type == "modem" then
|
|
||||||
---@cast device Modem
|
|
||||||
-- note, check init_ok first since nic will be nil if it is false
|
|
||||||
if device.isWireless() and not (plc_state.init_ok and nic.is_connected()) then
|
|
||||||
-- reconnected modem
|
|
||||||
plc_dev.modem = device
|
|
||||||
plc_state.no_modem = false
|
|
||||||
|
|
||||||
if plc_state.init_ok then nic.connect(device) end
|
|
||||||
|
|
||||||
println_ts("wireless modem reconnected.")
|
|
||||||
log.info("comms modem reconnected")
|
|
||||||
|
|
||||||
-- determine if we are still in a degraded state
|
|
||||||
if not plc_state.no_reactor then
|
|
||||||
plc_state.degraded = false
|
|
||||||
end
|
|
||||||
elseif device.isWireless() then
|
|
||||||
log.info("unused wireless modem reconnected")
|
|
||||||
else
|
|
||||||
log.info("wired modem reconnected")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- if not init'd and no longer degraded, proceed to init
|
|
||||||
if not plc_state.init_ok and not plc_state.degraded then
|
|
||||||
plc_state.init_ok = true
|
|
||||||
init()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- update indicators
|
-- update indicators
|
||||||
@@ -245,15 +145,11 @@ function threads.thread__main(smem, init)
|
|||||||
event == "double_click" then
|
event == "double_click" then
|
||||||
-- handle a mouse event
|
-- handle a mouse event
|
||||||
renderer.handle_mouse(core.events.new_mouse_event(event, param1, param2, param3))
|
renderer.handle_mouse(core.events.new_mouse_event(event, param1, param2, param3))
|
||||||
elseif event == "clock_start" then
|
|
||||||
-- start loop clock
|
|
||||||
loop_clock.start()
|
|
||||||
log.debug("main thread clock started")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- check for termination request
|
-- check for termination request
|
||||||
if event == "terminate" or ppm.should_terminate() then
|
if event == "terminate" or ppm.should_terminate() then
|
||||||
log.info("terminate requested, main thread exiting")
|
log.info("OS: terminate requested, main thread exiting")
|
||||||
-- rps handles reactor shutdown
|
-- rps handles reactor shutdown
|
||||||
plc_state.shutdown = true
|
plc_state.shutdown = true
|
||||||
break
|
break
|
||||||
@@ -277,8 +173,7 @@ function threads.thread__main(smem, init)
|
|||||||
-- if not, we need to restart the clock
|
-- if not, we need to restart the clock
|
||||||
-- this thread cannot be slept because it will miss events (namely "terminate" otherwise)
|
-- this thread cannot be slept because it will miss events (namely "terminate" otherwise)
|
||||||
if not plc_state.shutdown then
|
if not plc_state.shutdown then
|
||||||
log.info("main thread restarting now...")
|
log.info("OS: main thread restarting now...")
|
||||||
util.push_event("clock_start")
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -299,7 +194,7 @@ function threads.thread__rps(smem)
|
|||||||
-- execute thread
|
-- execute thread
|
||||||
function public.exec()
|
function public.exec()
|
||||||
databus.tx_rt_status("rps", true)
|
databus.tx_rt_status("rps", true)
|
||||||
log.debug("rps thread start")
|
log.debug("OS: rps thread start")
|
||||||
|
|
||||||
-- load in from shared memory
|
-- load in from shared memory
|
||||||
local networked = smem.networked
|
local networked = smem.networked
|
||||||
@@ -308,6 +203,8 @@ function threads.thread__rps(smem)
|
|||||||
|
|
||||||
local rps_queue = smem.q.mq_rps
|
local rps_queue = smem.q.mq_rps
|
||||||
|
|
||||||
|
local MQ__RPS_CMD = smem.q_types.MQ__RPS_CMD
|
||||||
|
|
||||||
local was_linked = false
|
local was_linked = false
|
||||||
local last_update = util.time()
|
local last_update = util.time()
|
||||||
|
|
||||||
@@ -316,49 +213,36 @@ function threads.thread__rps(smem)
|
|||||||
-- get plc_sys fields (may have been set late due to degraded boot)
|
-- get plc_sys fields (may have been set late due to degraded boot)
|
||||||
local rps = smem.plc_sys.rps
|
local rps = smem.plc_sys.rps
|
||||||
local plc_comms = smem.plc_sys.plc_comms
|
local plc_comms = smem.plc_sys.plc_comms
|
||||||
-- get reactor, may have changed do to disconnect/reconnect
|
-- get reactor, it may have changed due to a disconnect/reconnect
|
||||||
local reactor = plc_dev.reactor
|
local reactor = plc_dev.reactor
|
||||||
|
|
||||||
-- RPS checks
|
-- SCRAM if no open connection
|
||||||
if plc_state.init_ok then
|
if networked and not plc_comms.is_linked() then
|
||||||
-- SCRAM if no open connection
|
if was_linked then
|
||||||
if networked and not plc_comms.is_linked() then
|
was_linked = false
|
||||||
if was_linked then
|
rps.trip_timeout()
|
||||||
was_linked = false
|
|
||||||
rps.trip_timeout()
|
|
||||||
end
|
|
||||||
else
|
|
||||||
was_linked = true
|
|
||||||
end
|
end
|
||||||
|
else was_linked = true end
|
||||||
|
|
||||||
if (not plc_state.no_reactor) and rps.is_formed() then
|
-- check reactor status
|
||||||
-- check reactor status
|
if (not plc_state.no_reactor) and rps.is_formed() then
|
||||||
---@diagnostic disable-next-line: need-check-nil
|
local reactor_status = reactor.getStatus()
|
||||||
local reactor_status = reactor.getStatus()
|
databus.tx_reactor_state(reactor_status)
|
||||||
databus.tx_reactor_state(reactor_status)
|
|
||||||
|
|
||||||
-- if we tried to SCRAM but failed, keep trying
|
-- if we tried to SCRAM but failed, keep trying
|
||||||
-- in that case, SCRAM won't be called until it reconnects (this is the expected use of this check)
|
-- in that case, SCRAM won't be called until it reconnects (this is the expected use of this check)
|
||||||
if rps.is_tripped() and reactor_status then
|
if rps.is_tripped() and reactor_status then rps.scram() end
|
||||||
rps.scram()
|
end
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- if we are in standalone mode and the front panel isn't working, continuously reset RPS
|
-- if we are in standalone mode and the front panel isn't working, continuously reset RPS
|
||||||
-- RPS will trip again if there are faults, but if it isn't cleared, the user can't re-enable
|
-- RPS will trip again if there are faults, but if it isn't cleared, the user can't re-enable
|
||||||
if not (networked or smem.plc_state.fp_ok) then rps.reset(true) end
|
if not (networked or smem.plc_state.fp_ok) then rps.reset(true) end
|
||||||
|
|
||||||
-- check safety (SCRAM occurs if tripped)
|
-- check safety (SCRAM occurs if tripped)
|
||||||
if not plc_state.no_reactor then
|
local rps_tripped, rps_status_string, rps_first = rps.check(not plc_state.no_reactor)
|
||||||
local rps_tripped, rps_status_string, rps_first = rps.check()
|
if rps_tripped and rps_first then
|
||||||
|
println_ts("RPS: SCRAM on safety trip (" .. rps_status_string .. ")")
|
||||||
if rps_tripped and rps_first then
|
if networked then plc_comms.send_rps_alarm(rps_status_string) end
|
||||||
println_ts("[RPS] SCRAM! safety trip: " .. rps_status_string)
|
|
||||||
if networked and not plc_state.no_modem then
|
|
||||||
plc_comms.send_rps_alarm(rps_status_string)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- check for messages in the message queue
|
-- check for messages in the message queue
|
||||||
@@ -368,19 +252,19 @@ function threads.thread__rps(smem)
|
|||||||
if msg ~= nil then
|
if msg ~= nil then
|
||||||
if msg.qtype == mqueue.TYPE.COMMAND then
|
if msg.qtype == mqueue.TYPE.COMMAND then
|
||||||
-- received a command
|
-- received a command
|
||||||
if plc_state.init_ok then
|
if msg.message == MQ__RPS_CMD.SCRAM then
|
||||||
if msg.message == MQ__RPS_CMD.SCRAM then
|
-- SCRAM
|
||||||
-- SCRAM
|
log.info("RPS: OS requested SCRAM")
|
||||||
rps.scram()
|
rps.scram()
|
||||||
elseif msg.message == MQ__RPS_CMD.DEGRADED_SCRAM then
|
elseif msg.message == MQ__RPS_CMD.DEGRADED_SCRAM then
|
||||||
-- lost peripheral(s)
|
-- lost peripheral(s)
|
||||||
rps.trip_fault()
|
log.info("RPS: received PLC degraded alert")
|
||||||
elseif msg.message == MQ__RPS_CMD.TRIP_TIMEOUT then
|
rps.trip_fault()
|
||||||
-- watchdog tripped
|
elseif msg.message == MQ__RPS_CMD.TRIP_TIMEOUT then
|
||||||
rps.trip_timeout()
|
-- watchdog tripped
|
||||||
println_ts("server timeout")
|
println_ts("RPS: supervisor timeout")
|
||||||
log.warning("server timeout")
|
log.warning("RPS: received supervisor timeout alert")
|
||||||
end
|
rps.trip_timeout()
|
||||||
end
|
end
|
||||||
elseif msg.qtype == mqueue.TYPE.DATA then
|
elseif msg.qtype == mqueue.TYPE.DATA then
|
||||||
-- received data
|
-- received data
|
||||||
@@ -396,17 +280,17 @@ function threads.thread__rps(smem)
|
|||||||
-- check for termination request
|
-- check for termination request
|
||||||
if plc_state.shutdown then
|
if plc_state.shutdown then
|
||||||
-- safe exit
|
-- safe exit
|
||||||
log.info("rps thread shutdown initiated")
|
log.info("OS: rps thread shutdown initiated")
|
||||||
if plc_state.init_ok then
|
|
||||||
if rps.scram() then
|
if rps.scram() then
|
||||||
println_ts("reactor disabled")
|
println_ts("exiting, reactor disabled")
|
||||||
log.info("rps thread reactor SCRAM OK")
|
log.info("OS: rps thread reactor SCRAM OK on exit")
|
||||||
else
|
else
|
||||||
println_ts("exiting, reactor failed to disable")
|
println_ts("exiting, reactor failed to disable")
|
||||||
log.error("rps thread failed to SCRAM reactor on exit")
|
log.error("OS: rps thread failed to SCRAM reactor on exit")
|
||||||
end
|
|
||||||
end
|
end
|
||||||
log.info("rps thread exiting")
|
|
||||||
|
log.info("OS: rps thread exiting")
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -428,8 +312,8 @@ function threads.thread__rps(smem)
|
|||||||
databus.tx_rt_status("rps", false)
|
databus.tx_rt_status("rps", false)
|
||||||
|
|
||||||
if not plc_state.shutdown then
|
if not plc_state.shutdown then
|
||||||
if plc_state.init_ok then smem.plc_sys.rps.scram() end
|
smem.plc_sys.rps.scram()
|
||||||
log.info("rps thread restarting in 5 seconds...")
|
log.info("OS: rps thread restarting in 5 seconds...")
|
||||||
util.psleep(5)
|
util.psleep(5)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -448,11 +332,13 @@ function threads.thread__comms_tx(smem)
|
|||||||
-- execute thread
|
-- execute thread
|
||||||
function public.exec()
|
function public.exec()
|
||||||
databus.tx_rt_status("comms_tx", true)
|
databus.tx_rt_status("comms_tx", true)
|
||||||
log.debug("comms tx thread start")
|
log.debug("OS: comms tx thread start")
|
||||||
|
|
||||||
-- load in from shared memory
|
-- load in from shared memory
|
||||||
local plc_state = smem.plc_state
|
local plc_state = smem.plc_state
|
||||||
local comms_queue = smem.q.mq_comms_tx
|
local comms_queue = smem.q.mq_comms_tx
|
||||||
|
|
||||||
|
local MQ__COMM_CMD = smem.q_types.MQ__COMM_CMD
|
||||||
|
|
||||||
local last_update = util.time()
|
local last_update = util.time()
|
||||||
|
|
||||||
@@ -465,7 +351,7 @@ function threads.thread__comms_tx(smem)
|
|||||||
while comms_queue.ready() and not plc_state.shutdown do
|
while comms_queue.ready() and not plc_state.shutdown do
|
||||||
local msg = comms_queue.pop()
|
local msg = comms_queue.pop()
|
||||||
|
|
||||||
if msg ~= nil and plc_state.init_ok then
|
if msg ~= nil then
|
||||||
if msg.qtype == mqueue.TYPE.COMMAND then
|
if msg.qtype == mqueue.TYPE.COMMAND then
|
||||||
-- received a command
|
-- received a command
|
||||||
if msg.message == MQ__COMM_CMD.SEND_STATUS then
|
if msg.message == MQ__COMM_CMD.SEND_STATUS then
|
||||||
@@ -486,7 +372,7 @@ function threads.thread__comms_tx(smem)
|
|||||||
|
|
||||||
-- check for termination request
|
-- check for termination request
|
||||||
if plc_state.shutdown then
|
if plc_state.shutdown then
|
||||||
log.info("comms tx thread exiting")
|
log.info("OS: comms tx thread exiting")
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -508,7 +394,7 @@ function threads.thread__comms_tx(smem)
|
|||||||
databus.tx_rt_status("comms_tx", false)
|
databus.tx_rt_status("comms_tx", false)
|
||||||
|
|
||||||
if not plc_state.shutdown then
|
if not plc_state.shutdown then
|
||||||
log.info("comms tx thread restarting in 5 seconds...")
|
log.info("OS: comms tx thread restarting in 5 seconds...")
|
||||||
util.psleep(5)
|
util.psleep(5)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -521,13 +407,16 @@ end
|
|||||||
---@nodiscard
|
---@nodiscard
|
||||||
---@param smem plc_shared_memory
|
---@param smem plc_shared_memory
|
||||||
function threads.thread__comms_rx(smem)
|
function threads.thread__comms_rx(smem)
|
||||||
|
-- print a log message to the terminal as long as the UI isn't running
|
||||||
|
local function println_ts(message) if not smem.plc_state.fp_ok then util.println_ts(message) end end
|
||||||
|
|
||||||
---@class parallel_thread
|
---@class parallel_thread
|
||||||
local public = {}
|
local public = {}
|
||||||
|
|
||||||
-- execute thread
|
-- execute thread
|
||||||
function public.exec()
|
function public.exec()
|
||||||
databus.tx_rt_status("comms_rx", true)
|
databus.tx_rt_status("comms_rx", true)
|
||||||
log.debug("comms rx thread start")
|
log.debug("OS: comms rx thread start")
|
||||||
|
|
||||||
-- load in from shared memory
|
-- load in from shared memory
|
||||||
local plc_state = smem.plc_state
|
local plc_state = smem.plc_state
|
||||||
@@ -546,7 +435,7 @@ function threads.thread__comms_rx(smem)
|
|||||||
while comms_queue.ready() and not plc_state.shutdown do
|
while comms_queue.ready() and not plc_state.shutdown do
|
||||||
local msg = comms_queue.pop()
|
local msg = comms_queue.pop()
|
||||||
|
|
||||||
if msg ~= nil and plc_state.init_ok then
|
if msg ~= nil then
|
||||||
if msg.qtype == mqueue.TYPE.COMMAND then
|
if msg.qtype == mqueue.TYPE.COMMAND then
|
||||||
-- received a command
|
-- received a command
|
||||||
elseif msg.qtype == mqueue.TYPE.DATA then
|
elseif msg.qtype == mqueue.TYPE.DATA then
|
||||||
@@ -555,7 +444,7 @@ function threads.thread__comms_rx(smem)
|
|||||||
-- received a packet
|
-- received a packet
|
||||||
-- handle the packet (setpoints passed to update burn rate setpoint)
|
-- handle the packet (setpoints passed to update burn rate setpoint)
|
||||||
-- (plc_state passed to check if degraded)
|
-- (plc_state passed to check if degraded)
|
||||||
plc_comms.handle_packet(msg.message, plc_state, setpoints)
|
plc_comms.handle_packet(msg.message, plc_state, setpoints, println_ts)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -565,7 +454,7 @@ function threads.thread__comms_rx(smem)
|
|||||||
|
|
||||||
-- check for termination request
|
-- check for termination request
|
||||||
if plc_state.shutdown then
|
if plc_state.shutdown then
|
||||||
log.info("comms rx thread exiting")
|
log.info("OS: comms rx thread exiting")
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -587,7 +476,7 @@ function threads.thread__comms_rx(smem)
|
|||||||
databus.tx_rt_status("comms_rx", false)
|
databus.tx_rt_status("comms_rx", false)
|
||||||
|
|
||||||
if not plc_state.shutdown then
|
if not plc_state.shutdown then
|
||||||
log.info("comms rx thread restarting in 5 seconds...")
|
log.info("OS: comms rx thread restarting in 5 seconds...")
|
||||||
util.psleep(5)
|
util.psleep(5)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -606,7 +495,7 @@ function threads.thread__setpoint_control(smem)
|
|||||||
-- execute thread
|
-- execute thread
|
||||||
function public.exec()
|
function public.exec()
|
||||||
databus.tx_rt_status("spctl", true)
|
databus.tx_rt_status("spctl", true)
|
||||||
log.debug("setpoint control thread start")
|
log.debug("OS: setpoint control thread start")
|
||||||
|
|
||||||
-- load in from shared memory
|
-- load in from shared memory
|
||||||
local plc_state = smem.plc_state
|
local plc_state = smem.plc_state
|
||||||
@@ -629,9 +518,7 @@ function threads.thread__setpoint_control(smem)
|
|||||||
-- get reactor, may have changed do to disconnect/reconnect
|
-- get reactor, may have changed do to disconnect/reconnect
|
||||||
local reactor = plc_dev.reactor
|
local reactor = plc_dev.reactor
|
||||||
|
|
||||||
if plc_state.init_ok and (not plc_state.no_reactor) then
|
if not plc_state.no_reactor then
|
||||||
---@cast reactor table won't be nil
|
|
||||||
|
|
||||||
-- check if we should start ramping
|
-- check if we should start ramping
|
||||||
if setpoints.burn_rate_en and (setpoints.burn_rate ~= last_burn_sp) then
|
if setpoints.burn_rate_en and (setpoints.burn_rate ~= last_burn_sp) then
|
||||||
local cur_burn_rate = reactor.getBurnRate()
|
local cur_burn_rate = reactor.getBurnRate()
|
||||||
@@ -698,7 +585,7 @@ function threads.thread__setpoint_control(smem)
|
|||||||
|
|
||||||
-- check for termination request
|
-- check for termination request
|
||||||
if plc_state.shutdown then
|
if plc_state.shutdown then
|
||||||
log.info("setpoint control thread exiting")
|
log.info("OS: setpoint control thread exiting")
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -720,7 +607,7 @@ function threads.thread__setpoint_control(smem)
|
|||||||
databus.tx_rt_status("spctl", false)
|
databus.tx_rt_status("spctl", false)
|
||||||
|
|
||||||
if not plc_state.shutdown then
|
if not plc_state.shutdown then
|
||||||
log.info("setpoint control thread restarting in 5 seconds...")
|
log.info("OS: setpoint control thread restarting in 5 seconds...")
|
||||||
util.psleep(5)
|
util.psleep(5)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
275
rtu/backplane.lua
Normal file
275
rtu/backplane.lua
Normal file
@@ -0,0 +1,275 @@
|
|||||||
|
--
|
||||||
|
-- RTU Gateway System Core Peripheral Backplane
|
||||||
|
--
|
||||||
|
|
||||||
|
local log = require("scada-common.log")
|
||||||
|
local network = require("scada-common.network")
|
||||||
|
local ppm = require("scada-common.ppm")
|
||||||
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
|
local databus = require("rtu.databus")
|
||||||
|
local rtu = require("rtu.rtu")
|
||||||
|
|
||||||
|
local println = util.println
|
||||||
|
|
||||||
|
---@class rtu_backplane
|
||||||
|
local backplane = {}
|
||||||
|
|
||||||
|
local _bp = {
|
||||||
|
smem = nil, ---@type rtu_shared_memory
|
||||||
|
|
||||||
|
wlan_pref = true,
|
||||||
|
lan_iface = "",
|
||||||
|
|
||||||
|
act_nic = nil, ---@type nic
|
||||||
|
wd_nic = nil, ---@type nic|nil
|
||||||
|
wl_nic = nil, ---@type nic|nil
|
||||||
|
|
||||||
|
sounders = {} ---@type rtu_speaker_sounder[]
|
||||||
|
}
|
||||||
|
|
||||||
|
-- initialize the system peripheral backplane
|
||||||
|
---@param config rtu_config
|
||||||
|
---@param __shared_memory rtu_shared_memory
|
||||||
|
---@return boolean success
|
||||||
|
function backplane.init(config, __shared_memory)
|
||||||
|
_bp.smem = __shared_memory
|
||||||
|
_bp.wlan_pref = config.PreferWireless
|
||||||
|
_bp.lan_iface = config.WiredModem
|
||||||
|
|
||||||
|
-- Modem Init
|
||||||
|
|
||||||
|
-- init wired NIC
|
||||||
|
if type(_bp.lan_iface) == "string" then
|
||||||
|
local modem = ppm.get_modem(_bp.lan_iface)
|
||||||
|
local wd_nic = network.nic(modem)
|
||||||
|
|
||||||
|
log.info("BKPLN: WIRED PHY_" .. util.trinary(modem, "UP ", "DOWN ") .. _bp.lan_iface)
|
||||||
|
|
||||||
|
_bp.wd_nic = wd_nic
|
||||||
|
_bp.act_nic = wd_nic -- set this as active for now
|
||||||
|
|
||||||
|
wd_nic.closeAll()
|
||||||
|
wd_nic.open(config.RTU_Channel)
|
||||||
|
|
||||||
|
databus.tx_hw_wd_modem(modem ~= nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- init wireless NIC(s)
|
||||||
|
if config.WirelessModem then
|
||||||
|
local modem, iface = ppm.get_wireless_modem()
|
||||||
|
local wl_nic = network.nic(modem)
|
||||||
|
|
||||||
|
log.info("BKPLN: WIRELESS PHY_" .. util.trinary(modem, "UP ", "DOWN") .. (iface or ""))
|
||||||
|
|
||||||
|
-- set this as active if connected or if both modems are disconnected and this is preferred
|
||||||
|
if (modem and _bp.wlan_pref) or not (_bp.act_nic and _bp.act_nic.is_connected()) then
|
||||||
|
_bp.act_nic = wl_nic
|
||||||
|
end
|
||||||
|
|
||||||
|
_bp.wl_nic = wl_nic
|
||||||
|
|
||||||
|
wl_nic.closeAll()
|
||||||
|
wl_nic.open(config.RTU_Channel)
|
||||||
|
|
||||||
|
databus.tx_hw_wl_modem(modem ~= nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- at least one comms modem is required
|
||||||
|
if not ((_bp.wd_nic and _bp.wd_nic.is_connected()) or (_bp.wl_nic and _bp.wl_nic.is_connected())) then
|
||||||
|
println("startup> no comms modem found")
|
||||||
|
log.warning("BKPLN: no comms modem on startup")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Speaker Init
|
||||||
|
|
||||||
|
-- find and setup all speakers
|
||||||
|
local speakers = ppm.get_all_devices("speaker")
|
||||||
|
for _, s in pairs(speakers) do
|
||||||
|
log.info("BKPLN: SPEAKER LINK_UP " .. ppm.get_iface(s))
|
||||||
|
|
||||||
|
local sounder = rtu.init_sounder(s)
|
||||||
|
table.insert(_bp.sounders, sounder)
|
||||||
|
|
||||||
|
log.debug(util.c("BKPLN: added speaker sounder, attached as ", sounder.name))
|
||||||
|
end
|
||||||
|
|
||||||
|
databus.tx_hw_spkr_count(#_bp.sounders)
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- get the active NIC
|
||||||
|
function backplane.active_nic() return _bp.act_nic end
|
||||||
|
|
||||||
|
-- get the sounder interfaces
|
||||||
|
function backplane.sounders() return _bp.sounders end
|
||||||
|
|
||||||
|
-- handle a backplane peripheral attach
|
||||||
|
---@param type string
|
||||||
|
---@param device table
|
||||||
|
---@param iface string
|
||||||
|
---@param print_no_fp function
|
||||||
|
function backplane.attach(type, device, iface, print_no_fp)
|
||||||
|
local wl_nic, wd_nic = _bp.wl_nic, _bp.wd_nic
|
||||||
|
|
||||||
|
local comms = _bp.smem.rtu_sys.rtu_comms
|
||||||
|
|
||||||
|
if type == "modem" then
|
||||||
|
---@cast device Modem
|
||||||
|
|
||||||
|
local m_is_wl = device.isWireless()
|
||||||
|
|
||||||
|
log.info(util.c("BKPLN: ", util.trinary(m_is_wl, "WIRELESS", "WIRED"), " PHY_ATTACH ", iface))
|
||||||
|
|
||||||
|
if wd_nic and (_bp.lan_iface == iface) then
|
||||||
|
-- connect this as the wired NIC
|
||||||
|
wd_nic.connect(device)
|
||||||
|
|
||||||
|
log.info("BKPLN: WIRED PHY_UP " .. iface)
|
||||||
|
print_no_fp("wired comms modem reconnected")
|
||||||
|
|
||||||
|
databus.tx_hw_wd_modem(true)
|
||||||
|
|
||||||
|
if (_bp.act_nic ~= wd_nic) and not _bp.wlan_pref then
|
||||||
|
-- switch back to preferred wired
|
||||||
|
_bp.act_nic = wd_nic
|
||||||
|
|
||||||
|
comms.switch_nic(_bp.act_nic, _bp.smem.rtu_state)
|
||||||
|
log.info("BKPLN: switched comms to wired modem (preferred)")
|
||||||
|
end
|
||||||
|
elseif wl_nic and (not wl_nic.is_connected()) and m_is_wl then
|
||||||
|
-- connect this as the wireless NIC
|
||||||
|
wl_nic.connect(device)
|
||||||
|
|
||||||
|
log.info("BKPLN: WIRELESS PHY_UP " .. iface)
|
||||||
|
print_no_fp("wireless comms modem reconnected")
|
||||||
|
|
||||||
|
databus.tx_hw_wl_modem(true)
|
||||||
|
|
||||||
|
if (_bp.act_nic ~= wl_nic) and _bp.wlan_pref then
|
||||||
|
-- switch back to preferred wireless
|
||||||
|
_bp.act_nic = wl_nic
|
||||||
|
|
||||||
|
comms.switch_nic(_bp.act_nic, _bp.smem.rtu_state)
|
||||||
|
log.info("BKPLN: switched comms to wireless modem (preferred)")
|
||||||
|
end
|
||||||
|
elseif wl_nic and m_is_wl then
|
||||||
|
-- the wireless NIC already has a modem
|
||||||
|
device.closeAll()
|
||||||
|
|
||||||
|
print_no_fp("standby wireless modem connected")
|
||||||
|
log.info("BKPLN: standby wireless modem connected")
|
||||||
|
else
|
||||||
|
device.closeAll()
|
||||||
|
|
||||||
|
print_no_fp("unassigned modem connected")
|
||||||
|
log.warning("BKPLN: unassigned modem connected")
|
||||||
|
end
|
||||||
|
elseif type == "speaker" then
|
||||||
|
---@cast device Speaker
|
||||||
|
|
||||||
|
log.info("BKPLN: SPEAKER LINK_UP " .. iface)
|
||||||
|
|
||||||
|
table.insert(_bp.sounders, rtu.init_sounder(device))
|
||||||
|
|
||||||
|
print_no_fp("a speaker was connected")
|
||||||
|
log.info("BKPLN: setup speaker sounder for speaker " .. iface)
|
||||||
|
|
||||||
|
databus.tx_hw_spkr_count(#_bp.sounders)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- handle a backplane peripheral detach
|
||||||
|
---@param type string
|
||||||
|
---@param device table
|
||||||
|
---@param iface string
|
||||||
|
---@param print_no_fp function
|
||||||
|
function backplane.detach(type, device, iface, print_no_fp)
|
||||||
|
local wl_nic, wd_nic = _bp.wl_nic, _bp.wd_nic
|
||||||
|
|
||||||
|
local comms = _bp.smem.rtu_sys.rtu_comms
|
||||||
|
|
||||||
|
if type == "modem" then
|
||||||
|
---@cast device Modem
|
||||||
|
|
||||||
|
log.info(util.c("BKPLN: PHY_DETACH ", iface))
|
||||||
|
|
||||||
|
if wd_nic and wd_nic.is_modem(device) then
|
||||||
|
wd_nic.disconnect()
|
||||||
|
log.info("BKPLN: WIRED PHY_DOWN " .. iface)
|
||||||
|
|
||||||
|
databus.tx_hw_wd_modem(false)
|
||||||
|
elseif wl_nic and wl_nic.is_modem(device) then
|
||||||
|
wl_nic.disconnect()
|
||||||
|
log.info("BKPLN: WIRELESS PHY_DOWN " .. iface)
|
||||||
|
|
||||||
|
databus.tx_hw_wl_modem(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- we only care if this is our active comms modem
|
||||||
|
if _bp.act_nic.is_modem(device) then
|
||||||
|
print_no_fp("active comms modem disconnected")
|
||||||
|
log.warning("BKPLN: active comms modem disconnected")
|
||||||
|
|
||||||
|
-- failover and try to find a new comms modem
|
||||||
|
if _bp.act_nic == wl_nic then
|
||||||
|
-- wireless active disconnected
|
||||||
|
-- try to find another wireless modem, otherwise switch to wired
|
||||||
|
local modem, m_iface = ppm.get_wireless_modem()
|
||||||
|
if wl_nic and modem then
|
||||||
|
log.info("BKPLN: found another wireless modem, using it for comms")
|
||||||
|
|
||||||
|
wl_nic.connect(modem)
|
||||||
|
|
||||||
|
log.info("BKPLN: WIRELESS PHY_UP " .. m_iface)
|
||||||
|
|
||||||
|
databus.tx_hw_wl_modem(true)
|
||||||
|
elseif wd_nic and wd_nic.is_connected() then
|
||||||
|
_bp.act_nic = wd_nic
|
||||||
|
|
||||||
|
comms.switch_nic(_bp.act_nic, _bp.smem.rtu_state)
|
||||||
|
log.info("BKPLN: switched comms to wired modem")
|
||||||
|
end
|
||||||
|
elseif wl_nic and wl_nic.is_connected() then
|
||||||
|
-- wired active disconnected, wireless available
|
||||||
|
_bp.act_nic = wl_nic
|
||||||
|
|
||||||
|
comms.switch_nic(_bp.act_nic, _bp.smem.rtu_state)
|
||||||
|
log.info("BKPLN: switched comms to wireless modem")
|
||||||
|
else
|
||||||
|
-- wired active disconnected, wireless unavailable
|
||||||
|
end
|
||||||
|
elseif wd_nic and wd_nic.is_modem(device) then
|
||||||
|
-- wired, but not active
|
||||||
|
print_no_fp("standby wired modem disconnected")
|
||||||
|
log.info("BKPLN: standby wired modem disconnected")
|
||||||
|
elseif wl_nic and wl_nic.is_modem(device) then
|
||||||
|
-- wireless, but not active
|
||||||
|
print_no_fp("standby wireless modem disconnected")
|
||||||
|
log.info("BKPLN: standby wireless modem disconnected")
|
||||||
|
else
|
||||||
|
print_no_fp("unassigned modem disconnected")
|
||||||
|
log.warning("BKPLN: unassigned modem disconnected")
|
||||||
|
end
|
||||||
|
elseif type == "speaker" then
|
||||||
|
---@cast device Speaker
|
||||||
|
|
||||||
|
log.info("BKPLN: SPEAKER LINK_DOWN " .. iface)
|
||||||
|
|
||||||
|
for i = 1, #_bp.sounders do
|
||||||
|
if _bp.sounders[i].speaker == device then
|
||||||
|
table.remove(_bp.sounders, i)
|
||||||
|
|
||||||
|
print_no_fp("a speaker was disconnected")
|
||||||
|
log.warning("BKPLN: speaker sounder " .. iface .. " disconnected")
|
||||||
|
|
||||||
|
databus.tx_hw_spkr_count(#_bp.sounders)
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return backplane
|
||||||
378
rtu/config/check.lua
Normal file
378
rtu/config/check.lua
Normal file
@@ -0,0 +1,378 @@
|
|||||||
|
local comms = require("scada-common.comms")
|
||||||
|
local network = require("scada-common.network")
|
||||||
|
local ppm = require("scada-common.ppm")
|
||||||
|
local rsio = require("scada-common.rsio")
|
||||||
|
local tcd = require("scada-common.tcd")
|
||||||
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
|
local rtu = require("rtu.rtu")
|
||||||
|
|
||||||
|
local redstone = require("rtu.config.redstone")
|
||||||
|
|
||||||
|
local core = require("graphics.core")
|
||||||
|
|
||||||
|
local Div = require("graphics.elements.Div")
|
||||||
|
local ListBox = require("graphics.elements.ListBox")
|
||||||
|
local TextBox = require("graphics.elements.TextBox")
|
||||||
|
|
||||||
|
local PushButton = require("graphics.elements.controls.PushButton")
|
||||||
|
|
||||||
|
local tri = util.trinary
|
||||||
|
|
||||||
|
local cpair = core.cpair
|
||||||
|
|
||||||
|
local PROTOCOL = comms.PROTOCOL
|
||||||
|
local DEVICE_TYPE = comms.DEVICE_TYPE
|
||||||
|
local ESTABLISH_ACK = comms.ESTABLISH_ACK
|
||||||
|
local MGMT_TYPE = comms.MGMT_TYPE
|
||||||
|
|
||||||
|
local self = {
|
||||||
|
checking_wl = true,
|
||||||
|
wd_modem = nil, ---@type Modem|nil
|
||||||
|
wl_modem = nil, ---@type Modem|nil
|
||||||
|
|
||||||
|
nic = nil, ---@type nic
|
||||||
|
net_listen = false,
|
||||||
|
|
||||||
|
self_check_pass = true,
|
||||||
|
|
||||||
|
self_check_wireless = true,
|
||||||
|
|
||||||
|
settings = nil, ---@type rtu_config
|
||||||
|
|
||||||
|
run_test_btn = nil, ---@type PushButton
|
||||||
|
sc_log = nil, ---@type ListBox
|
||||||
|
self_check_msg = nil ---@type function
|
||||||
|
}
|
||||||
|
|
||||||
|
-- report successful completion of the check
|
||||||
|
local function check_complete()
|
||||||
|
TextBox{parent=self.sc_log,text="> all tests passed!",fg_bg=cpair(colors.blue,colors._INHERIT)}
|
||||||
|
TextBox{parent=self.sc_log,text=""}
|
||||||
|
local more = Div{parent=self.sc_log,height=3,fg_bg=cpair(colors.gray,colors._INHERIT)}
|
||||||
|
TextBox{parent=more,text="if you still have a problem:"}
|
||||||
|
TextBox{parent=more,text="- check the wiki on GitHub"}
|
||||||
|
TextBox{parent=more,text="- ask for help on GitHub discussions or Discord"}
|
||||||
|
end
|
||||||
|
|
||||||
|
-- send a management packet to the supervisor
|
||||||
|
---@param msg_type MGMT_TYPE
|
||||||
|
---@param msg table
|
||||||
|
local function send_sv(msg_type, msg)
|
||||||
|
local s_pkt = comms.scada_packet()
|
||||||
|
local pkt = comms.mgmt_packet()
|
||||||
|
|
||||||
|
pkt.make(msg_type, msg)
|
||||||
|
s_pkt.make(comms.BROADCAST, util.time_ms() * 10, PROTOCOL.SCADA_MGMT, pkt.raw_sendable())
|
||||||
|
|
||||||
|
self.nic.transmit(self.settings.SVR_Channel, self.settings.RTU_Channel, s_pkt)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- handle an establish message from the supervisor
|
||||||
|
---@param packet mgmt_frame
|
||||||
|
local function handle_packet(packet)
|
||||||
|
local error_msg = nil
|
||||||
|
|
||||||
|
if packet.scada_frame.local_channel() ~= self.settings.RTU_Channel then
|
||||||
|
error_msg = "error: unknown receive channel"
|
||||||
|
elseif packet.scada_frame.remote_channel() == self.settings.SVR_Channel and packet.scada_frame.protocol() == PROTOCOL.SCADA_MGMT then
|
||||||
|
if packet.type == MGMT_TYPE.ESTABLISH then
|
||||||
|
if packet.length == 1 then
|
||||||
|
local est_ack = packet.data[1]
|
||||||
|
|
||||||
|
if est_ack== ESTABLISH_ACK.ALLOW then
|
||||||
|
-- OK
|
||||||
|
elseif est_ack == ESTABLISH_ACK.DENY then
|
||||||
|
error_msg = "error: supervisor connection denied"
|
||||||
|
elseif est_ack == ESTABLISH_ACK.BAD_VERSION then
|
||||||
|
error_msg = "RTU gateway comms version does not match supervisor comms version, make sure both devices are up-to-date (ccmsi update)"
|
||||||
|
else
|
||||||
|
error_msg = "error: invalid reply from supervisor"
|
||||||
|
end
|
||||||
|
else
|
||||||
|
error_msg = "error: invalid reply length from supervisor"
|
||||||
|
end
|
||||||
|
else
|
||||||
|
error_msg = "error: didn't get an establish reply from supervisor"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
self.net_listen = false
|
||||||
|
|
||||||
|
if error_msg then
|
||||||
|
self.self_check_msg(nil, false, error_msg)
|
||||||
|
else
|
||||||
|
self.self_check_msg(nil, true, "")
|
||||||
|
end
|
||||||
|
|
||||||
|
util.push_event("conn_test_complete", error_msg == nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- handle supervisor connection failure
|
||||||
|
local function handle_timeout()
|
||||||
|
self.net_listen = false
|
||||||
|
util.push_event("conn_test_complete", false)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- check if a value is an integer within a range (inclusive)
|
||||||
|
---@param x any
|
||||||
|
---@param min integer
|
||||||
|
---@param max integer
|
||||||
|
local function is_int_min_max(x, min, max) return util.is_int(x) and x >= min and x <= max end
|
||||||
|
|
||||||
|
-- execute the self-check
|
||||||
|
local function self_check()
|
||||||
|
self.run_test_btn.disable()
|
||||||
|
|
||||||
|
self.sc_log.remove_all()
|
||||||
|
ppm.mount_all()
|
||||||
|
|
||||||
|
self.self_check_pass = true
|
||||||
|
|
||||||
|
local cfg = self.settings
|
||||||
|
self.wd_modem = ppm.get_modem(cfg.WiredModem)
|
||||||
|
self.wl_modem = ppm.get_wireless_modem()
|
||||||
|
local valid_cfg = rtu.validate_config(cfg)
|
||||||
|
|
||||||
|
if cfg.WiredModem then
|
||||||
|
self.self_check_msg("> check wired comms modem connected...", self.wd_modem, "please connect the wired comms modem " .. cfg.WiredModem)
|
||||||
|
end
|
||||||
|
|
||||||
|
if cfg.WirelessModem then
|
||||||
|
self.self_check_msg("> check wireless/ender modem connected...", self.wl_modem, "please connect an ender or wireless modem for wireless comms")
|
||||||
|
end
|
||||||
|
|
||||||
|
self.self_check_msg("> check gateway configuration...", valid_cfg, "go through Configure Gateway and apply settings to set any missing settings and repair any corrupted ones")
|
||||||
|
|
||||||
|
-- check redstone configurations
|
||||||
|
|
||||||
|
local phys = {} ---@type rtu_rs_definition[][]
|
||||||
|
local inputs = { [0] = {}, {}, {}, {}, {} }
|
||||||
|
|
||||||
|
for i = 1, #cfg.Redstone do
|
||||||
|
local entry = cfg.Redstone[i]
|
||||||
|
local name = entry.relay or "local"
|
||||||
|
|
||||||
|
if phys[name] == nil then phys[name] = {} end
|
||||||
|
table.insert(phys[entry.relay or "local"], entry)
|
||||||
|
end
|
||||||
|
|
||||||
|
for name, entries in pairs(phys) do
|
||||||
|
TextBox{parent=self.sc_log,text="> checking redstone @ "..name.."...",fg_bg=cpair(colors.blue,colors.white)}
|
||||||
|
|
||||||
|
local ifaces = {}
|
||||||
|
local bundled_sides = {}
|
||||||
|
|
||||||
|
for i = 1, #entries do
|
||||||
|
local entry = entries[i]
|
||||||
|
local ident = entry.side .. tri(entry.color, ":" .. rsio.color_name(entry.color), "")
|
||||||
|
|
||||||
|
local sc_dupe = util.table_contains(ifaces, ident)
|
||||||
|
local mixed = (bundled_sides[entry.side] and (entry.color == nil)) or (bundled_sides[entry.side] == false and (entry.color ~= nil))
|
||||||
|
|
||||||
|
local mixed_msg = util.trinary(bundled_sides[entry.side], "bundled entry(s) but this entry is not", "non-bundled entry(s) but this entry is")
|
||||||
|
|
||||||
|
self.self_check_msg("> check redstone " .. ident .. " unique...", not sc_dupe, "only one port should be set to a side/color combination")
|
||||||
|
self.self_check_msg("> check redstone " .. ident .. " bundle...", not mixed, "this side has " .. mixed_msg .. " bundled, which will not work")
|
||||||
|
self.self_check_msg("> check redstone " .. ident .. " valid...", redstone.validate(entry), "configuration invalid, please re-configure redstone entry")
|
||||||
|
|
||||||
|
if rsio.get_io_dir(entry.port) == rsio.IO_DIR.IN then
|
||||||
|
local in_dupe = util.table_contains(inputs[entry.unit or 0], entry.port)
|
||||||
|
self.self_check_msg("> check redstone " .. ident .. " input...", not in_dupe, "you cannot have multiple of the same input for a given unit or the facility ("..rsio.to_string(entry.port)..")")
|
||||||
|
end
|
||||||
|
|
||||||
|
bundled_sides[entry.side] = bundled_sides[entry.side] or entry.color ~= nil
|
||||||
|
table.insert(ifaces, ident)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- check peripheral configurations
|
||||||
|
for i = 1, #cfg.Peripherals do
|
||||||
|
local entry = cfg.Peripherals[i]
|
||||||
|
local valid = false
|
||||||
|
|
||||||
|
if type(entry.name) == "string" then
|
||||||
|
self.self_check_msg("> check " .. entry.name .. " connected...", ppm.get_periph(entry.name), "please connect this device via a wired modem or direct contact and ensure the configuration matches what it connects as")
|
||||||
|
|
||||||
|
local p_type = ppm.get_type(entry.name)
|
||||||
|
|
||||||
|
if p_type == "boilerValve" then
|
||||||
|
valid = is_int_min_max(entry.index, 1, 2) and is_int_min_max(entry.unit, 1, 4)
|
||||||
|
elseif p_type == "turbineValve" then
|
||||||
|
valid = is_int_min_max(entry.index, 1, 3) and is_int_min_max(entry.unit, 1, 4)
|
||||||
|
elseif p_type == "solarNeutronActivator" then
|
||||||
|
valid = is_int_min_max(entry.unit, 1, 4)
|
||||||
|
elseif p_type == "dynamicValve" then
|
||||||
|
valid = (entry.unit == nil and is_int_min_max(entry.index, 1, 4)) or is_int_min_max(entry.unit, 1, 4)
|
||||||
|
elseif p_type == "environmentDetector" or p_type == "environment_detector" then
|
||||||
|
valid = (entry.unit == nil or is_int_min_max(entry.unit, 1, 4)) and util.is_int(entry.index)
|
||||||
|
else
|
||||||
|
valid = true
|
||||||
|
|
||||||
|
if p_type ~= nil and not (p_type == "inductionPort" or p_type == "reinforcedInductionPort" or p_type == "spsPort") then
|
||||||
|
self.self_check_msg("> check " .. entry.name .. " valid...", false, "unrecognized device type")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not valid then
|
||||||
|
self.self_check_msg("> check " .. entry.name .. " valid...", false, "configuration invalid, please re-configure peripheral entry")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if valid_cfg then
|
||||||
|
self.checking_wl = true
|
||||||
|
|
||||||
|
if cfg.WirelessModem and self.wl_modem then
|
||||||
|
self.self_check_msg("> check wireless supervisor connection...")
|
||||||
|
|
||||||
|
-- init mac as needed
|
||||||
|
if cfg.AuthKey and string.len(cfg.AuthKey) >= 8 then
|
||||||
|
network.init_mac(cfg.AuthKey)
|
||||||
|
else
|
||||||
|
network.deinit_mac()
|
||||||
|
end
|
||||||
|
|
||||||
|
comms.set_trusted_range(cfg.TrustedRange)
|
||||||
|
|
||||||
|
self.nic = network.nic(self.wl_modem)
|
||||||
|
|
||||||
|
self.nic.closeAll()
|
||||||
|
self.nic.open(cfg.RTU_Channel)
|
||||||
|
|
||||||
|
self.net_listen = true
|
||||||
|
|
||||||
|
send_sv(MGMT_TYPE.ESTABLISH, { comms.version, comms.CONN_TEST_FWV, DEVICE_TYPE.RTU, {} })
|
||||||
|
|
||||||
|
tcd.dispatch_unique(8, handle_timeout)
|
||||||
|
elseif cfg.WiredModem and self.wd_modem then
|
||||||
|
-- skip to wired
|
||||||
|
util.push_event("conn_test_complete", true)
|
||||||
|
else
|
||||||
|
self.self_check_msg("> no modem, can't test supervisor connection", false)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if self.self_check_pass then check_complete() end
|
||||||
|
self.run_test_btn.enable()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- exit self check back home
|
||||||
|
---@param main_pane MultiPane
|
||||||
|
local function exit_self_check(main_pane)
|
||||||
|
tcd.abort(handle_timeout)
|
||||||
|
self.net_listen = false
|
||||||
|
self.run_test_btn.enable()
|
||||||
|
self.sc_log.remove_all()
|
||||||
|
main_pane.set_value(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
local check = {}
|
||||||
|
|
||||||
|
-- create the self-check view
|
||||||
|
---@param main_pane MultiPane
|
||||||
|
---@param settings_cfg rtu_config
|
||||||
|
---@param check_sys Div
|
||||||
|
---@param style { [string]: cpair }
|
||||||
|
function check.create(main_pane, settings_cfg, check_sys, style)
|
||||||
|
local bw_fg_bg = style.bw_fg_bg
|
||||||
|
local g_lg_fg_bg = style.g_lg_fg_bg
|
||||||
|
local nav_fg_bg = style.nav_fg_bg
|
||||||
|
local btn_act_fg_bg = style.btn_act_fg_bg
|
||||||
|
local btn_dis_fg_bg = style.btn_dis_fg_bg
|
||||||
|
|
||||||
|
self.settings = settings_cfg
|
||||||
|
|
||||||
|
local sc = Div{parent=check_sys,x=2,y=4,width=49}
|
||||||
|
|
||||||
|
TextBox{parent=check_sys,x=1,y=2,text=" RTU Gateway Self-Check",fg_bg=bw_fg_bg}
|
||||||
|
|
||||||
|
self.sc_log = ListBox{parent=sc,x=1,y=1,height=12,width=49,scroll_height=1000,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
|
||||||
|
|
||||||
|
local last_check = { nil, nil }
|
||||||
|
|
||||||
|
function self.self_check_msg(msg, success, fail_msg)
|
||||||
|
if type(msg) == "string" then
|
||||||
|
last_check[1] = Div{parent=self.sc_log,height=1}
|
||||||
|
local e = TextBox{parent=last_check[1],text=msg,fg_bg=bw_fg_bg}
|
||||||
|
last_check[2] = e.get_x()+string.len(msg)
|
||||||
|
end
|
||||||
|
|
||||||
|
if type(fail_msg) == "string" then
|
||||||
|
TextBox{parent=last_check[1],x=last_check[2],y=1,text=tri(success,"PASS","FAIL"),fg_bg=tri(success,cpair(colors.green,colors._INHERIT),cpair(colors.red,colors._INHERIT))}
|
||||||
|
|
||||||
|
if not success then
|
||||||
|
local fail = Div{parent=self.sc_log,height=#util.strwrap(fail_msg, 46)}
|
||||||
|
TextBox{parent=fail,x=3,text=fail_msg,fg_bg=cpair(colors.gray,colors.white)}
|
||||||
|
end
|
||||||
|
|
||||||
|
self.self_check_pass = self.self_check_pass and success
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
PushButton{parent=sc,x=1,y=14,text="\x1b Back",callback=function()exit_self_check(main_pane)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
self.run_test_btn = PushButton{parent=sc,x=40,y=14,min_width=10,text="Run Test",callback=function()self_check()end,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
||||||
|
end
|
||||||
|
|
||||||
|
-- handle incoming modem messages
|
||||||
|
---@param side string
|
||||||
|
---@param sender integer
|
||||||
|
---@param reply_to integer
|
||||||
|
---@param message any
|
||||||
|
---@param distance integer
|
||||||
|
function check.receive_sv(side, sender, reply_to, message, distance)
|
||||||
|
if self.nic ~= nil and self.net_listen then
|
||||||
|
local s_pkt = self.nic.receive(side, sender, reply_to, message, distance)
|
||||||
|
|
||||||
|
if s_pkt and s_pkt.protocol() == PROTOCOL.SCADA_MGMT then
|
||||||
|
local mgmt_pkt = comms.mgmt_packet()
|
||||||
|
if mgmt_pkt.decode(s_pkt) then
|
||||||
|
tcd.abort(handle_timeout)
|
||||||
|
handle_packet(mgmt_pkt.get())
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- handle completed connection tests
|
||||||
|
---@param pass boolean
|
||||||
|
function check.conn_test_callback(pass)
|
||||||
|
local cfg = self.settings
|
||||||
|
|
||||||
|
if self.checking_wl then
|
||||||
|
if not pass then
|
||||||
|
self.self_check_msg(nil, false, "make sure your supervisor is running, listening on the wireless interface, your channels are correct, trusted ranges are set properly (if enabled), facility keys match (if set), and if you are using wireless modems rather than ender modems, that your devices are close together in the same dimension")
|
||||||
|
end
|
||||||
|
|
||||||
|
if cfg.WiredModem and self.wd_modem then
|
||||||
|
self.checking_wl = false
|
||||||
|
self.self_check_msg("> check wired supervisor connection...")
|
||||||
|
|
||||||
|
comms.set_trusted_range(0)
|
||||||
|
|
||||||
|
self.nic = network.nic(self.wd_modem)
|
||||||
|
|
||||||
|
self.nic.closeAll()
|
||||||
|
self.nic.open(cfg.RTU_Channel)
|
||||||
|
|
||||||
|
self.net_listen = true
|
||||||
|
|
||||||
|
send_sv(MGMT_TYPE.ESTABLISH, { comms.version, comms.CONN_TEST_FWV, DEVICE_TYPE.RTU, {} })
|
||||||
|
|
||||||
|
tcd.dispatch_unique(8, handle_timeout)
|
||||||
|
else
|
||||||
|
if self.self_check_pass then check_complete() end
|
||||||
|
self.run_test_btn.enable()
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if not pass then
|
||||||
|
self.self_check_msg(nil, false, "make sure your supervisor is running, listening on the wired interface, the wire is intact, and your channels are correct")
|
||||||
|
end
|
||||||
|
|
||||||
|
if self.self_check_pass then check_complete() end
|
||||||
|
self.run_test_btn.enable()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return check
|
||||||
@@ -43,8 +43,8 @@ local self = {
|
|||||||
|
|
||||||
local peripherals = {}
|
local peripherals = {}
|
||||||
|
|
||||||
local RTU_DEV_TYPES = { "boilerValve", "turbineValve", "dynamicValve", "inductionPort", "spsPort", "solarNeutronActivator", "environmentDetector" }
|
local RTU_DEV_TYPES = { "boilerValve", "turbineValve", "dynamicValve", "inductionPort", "reinforcedInductionPort", "spsPort", "solarNeutronActivator", "environmentDetector", "environment_detector" }
|
||||||
local NEEDS_UNIT = { "boilerValve", "turbineValve", "dynamicValve", "solarNeutronActivator", "environmentDetector" }
|
local NEEDS_UNIT = { "boilerValve", "turbineValve", "dynamicValve", "solarNeutronActivator", "environmentDetector", "environment_detector" }
|
||||||
|
|
||||||
-- create the peripherals configuration view
|
-- create the peripherals configuration view
|
||||||
---@param tool_ctl _rtu_cfg_tool_ctl
|
---@param tool_ctl _rtu_cfg_tool_ctl
|
||||||
@@ -165,14 +165,14 @@ function peripherals.create(tool_ctl, main_pane, cfg_sys, peri_cfg, style)
|
|||||||
end
|
end
|
||||||
|
|
||||||
self.p_desc.set_value("Each reactor unit can have at most 1 tank and the facility can have at most 4. Each facility tank must have a unique # 1 through 4, regardless of where it is connected. Only a total of 4 tanks can be displayed on the flow monitor.")
|
self.p_desc.set_value("Each reactor unit can have at most 1 tank and the facility can have at most 4. Each facility tank must have a unique # 1 through 4, regardless of where it is connected. Only a total of 4 tanks can be displayed on the flow monitor.")
|
||||||
elseif type == "environmentDetector" then
|
elseif type == "environmentDetector" or type == "environment_detector" then
|
||||||
reposition("This is the below system's # env. detector.", 29, 99, 17, 6, 8)
|
reposition("This is the below system's # env. detector.", 29, 99, 17, 6, 8)
|
||||||
self.p_assign_btn.show()
|
self.p_assign_btn.show()
|
||||||
self.p_assign_btn.redraw()
|
self.p_assign_btn.redraw()
|
||||||
if self.p_assign_btn.get_value() == 1 then self.p_unit.disable() else self.p_unit.enable() end
|
if self.p_assign_btn.get_value() == 1 then self.p_unit.disable() else self.p_unit.enable() end
|
||||||
self.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.")
|
self.p_desc.set_value("You can connect more than one environment detector for a particular unit or the facility. In that case, the maximum radiation reading from those assigned to that particular unit or the facility will be used for alarms and display.")
|
||||||
elseif type == "inductionPort" or type == "spsPort" then
|
elseif type == "inductionPort" or type == "reinforcedInductionPort" or type == "spsPort" then
|
||||||
local dev = tri(type == "inductionPort", "induction matrix", "SPS")
|
local dev = tri(type == "inductionPort" or type == "reinforcedInductionPort", "induction matrix", "SPS")
|
||||||
self.p_idx.hide(true)
|
self.p_idx.hide(true)
|
||||||
self.p_unit.hide(true)
|
self.p_unit.hide(true)
|
||||||
self.p_prompt.set_value("This is the " .. dev .. " for the facility.")
|
self.p_prompt.set_value("This is the " .. dev .. " for the facility.")
|
||||||
@@ -212,10 +212,10 @@ function peripherals.create(tool_ctl, main_pane, cfg_sys, peri_cfg, style)
|
|||||||
|
|
||||||
tool_ctl.update_peri_list()
|
tool_ctl.update_peri_list()
|
||||||
|
|
||||||
TextBox{parent=peri_c_3,x=1,y=1,height=4,text="This feature is intended for advanced users. If you are clicking this just because your device is not shown, follow the connection instructions in 'I don't see my device!'."}
|
TextBox{parent=peri_c_3,x=1,y=1,height=4,text="This feature is intended for advanced users. If you just can't see your device, click 'I don't see my device!' instead."}
|
||||||
TextBox{parent=peri_c_3,x=1,y=6,height=4,text="Peripheral Name"}
|
TextBox{parent=peri_c_3,x=1,y=5,height=4,text="Peripheral Name"}
|
||||||
local p_name = TextField{parent=peri_c_3,x=1,y=7,width=49,height=1,max_len=128,fg_bg=bw_fg_bg}
|
local p_name = TextField{parent=peri_c_3,x=1,y=6,width=49,height=1,max_len=128,fg_bg=bw_fg_bg}
|
||||||
local p_type = Radio2D{parent=peri_c_3,x=1,y=9,rows=4,columns=2,default=1,options=RTU_DEV_TYPES,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.purple}
|
local p_type = Radio2D{parent=peri_c_3,x=1,y=8,rows=5,columns=2,default=1,options=RTU_DEV_TYPES,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.purple}
|
||||||
local man_p_err = TextBox{parent=peri_c_3,x=8,y=14,width=35,text="Please enter a peripheral name.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
local man_p_err = TextBox{parent=peri_c_3,x=8,y=14,width=35,text="Please enter a peripheral name.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||||
man_p_err.hide(true)
|
man_p_err.hide(true)
|
||||||
|
|
||||||
@@ -281,7 +281,7 @@ function peripherals.create(tool_ctl, main_pane, cfg_sys, peri_cfg, style)
|
|||||||
local idx = tonumber(self.p_idx.get_value())
|
local idx = tonumber(self.p_idx.get_value())
|
||||||
|
|
||||||
if util.table_contains(NEEDS_UNIT, peri_type) then
|
if util.table_contains(NEEDS_UNIT, peri_type) then
|
||||||
if (peri_type == "dynamicValve" or peri_type == "environmentDetector") and for_facility then
|
if (peri_type == "dynamicValve" or peri_type == "environmentDetector" or peri_type == "environment_detector") and for_facility then
|
||||||
-- skip
|
-- skip
|
||||||
elseif not (util.is_int(u) and u > 0 and u < 5) then
|
elseif not (util.is_int(u) and u > 0 and u < 5) then
|
||||||
self.p_err.set_value("Unit ID must be within 1 to 4.")
|
self.p_err.set_value("Unit ID must be within 1 to 4.")
|
||||||
@@ -310,7 +310,7 @@ function peripherals.create(tool_ctl, main_pane, cfg_sys, peri_cfg, style)
|
|||||||
else index = idx end
|
else index = idx end
|
||||||
elseif peri_type == "dynamicValve" then
|
elseif peri_type == "dynamicValve" then
|
||||||
index = 1
|
index = 1
|
||||||
elseif peri_type == "environmentDetector" then
|
elseif peri_type == "environmentDetector" or peri_type == "environment_detector" then
|
||||||
if not (util.is_int(idx) and idx > 0) then
|
if not (util.is_int(idx) and idx > 0) then
|
||||||
self.p_err.set_value("Index must be greater than 0.")
|
self.p_err.set_value("Index must be greater than 0.")
|
||||||
self.p_err.show()
|
self.p_err.show()
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
local constants = require("scada-common.constants")
|
local constants = require("scada-common.constants")
|
||||||
|
local ppm = require("scada-common.ppm")
|
||||||
local rsio = require("scada-common.rsio")
|
local rsio = require("scada-common.rsio")
|
||||||
local util = require("scada-common.util")
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
@@ -18,8 +19,10 @@ local NumberField = require("graphics.elements.form.NumberField")
|
|||||||
---@class rtu_rs_definition
|
---@class rtu_rs_definition
|
||||||
---@field unit integer|nil
|
---@field unit integer|nil
|
||||||
---@field port IO_PORT
|
---@field port IO_PORT
|
||||||
|
---@field relay string|nil
|
||||||
---@field side side
|
---@field side side
|
||||||
---@field color color|nil
|
---@field color color|nil
|
||||||
|
---@field invert true|nil
|
||||||
|
|
||||||
local tri = util.trinary
|
local tri = util.trinary
|
||||||
|
|
||||||
@@ -32,6 +35,7 @@ local IO_MODE = rsio.IO_MODE
|
|||||||
local LEFT = core.ALIGN.LEFT
|
local LEFT = core.ALIGN.LEFT
|
||||||
|
|
||||||
local self = {
|
local self = {
|
||||||
|
rs_cfg_phy = false, ---@type string|nil|false
|
||||||
rs_cfg_port = 1, ---@type IO_PORT
|
rs_cfg_port = 1, ---@type IO_PORT
|
||||||
rs_cfg_editing = false, ---@type integer|false
|
rs_cfg_editing = false, ---@type integer|false
|
||||||
|
|
||||||
@@ -41,7 +45,9 @@ local self = {
|
|||||||
rs_cfg_side_l = nil, ---@type TextBox
|
rs_cfg_side_l = nil, ---@type TextBox
|
||||||
rs_cfg_bundled = nil, ---@type Checkbox
|
rs_cfg_bundled = nil, ---@type Checkbox
|
||||||
rs_cfg_color = nil, ---@type Radio2D
|
rs_cfg_color = nil, ---@type Radio2D
|
||||||
rs_cfg_shortcut = nil ---@type TextBox
|
rs_cfg_inverted = nil, ---@type Checkbox
|
||||||
|
rs_cfg_shortcut = nil, ---@type TextBox
|
||||||
|
rs_cfg_advanced = nil ---@type PushButton
|
||||||
}
|
}
|
||||||
|
|
||||||
-- rsio port descriptions
|
-- rsio port descriptions
|
||||||
@@ -105,8 +111,34 @@ local function color_to_idx(color)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- select the subset of redstone entries assigned to the given phy
|
||||||
|
---@param cfg rtu_rs_definition[] the full redstone entry list
|
||||||
|
---@param phy string|nil which phy to get redstone entries for
|
||||||
|
---@param invert boolean? true to get all except this phy
|
||||||
|
---@return rtu_rs_definition[]
|
||||||
|
local function redstone_subset(cfg, phy, invert)
|
||||||
|
local subset = {}
|
||||||
|
|
||||||
|
for i = 1, #cfg do
|
||||||
|
if ((not invert) and cfg[i].relay == phy) or (invert and cfg[i].relay ~= phy) then
|
||||||
|
table.insert(subset, cfg[i])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return subset
|
||||||
|
end
|
||||||
|
|
||||||
local redstone = {}
|
local redstone = {}
|
||||||
|
|
||||||
|
-- validate a redstone entry
|
||||||
|
---@param def rtu_rs_definition
|
||||||
|
function redstone.validate(def)
|
||||||
|
return tri(PORT_DSGN[def.port] == 1, util.is_int(def.unit) and def.unit > 0 and def.unit <= 4, def.unit == nil) and
|
||||||
|
rsio.is_valid_port(def.port) and
|
||||||
|
rsio.is_valid_side(def.side) and
|
||||||
|
(def.color == nil or (rsio.is_digital(def.port) and rsio.is_color(def.color)))
|
||||||
|
end
|
||||||
|
|
||||||
-- create the redstone configuration view
|
-- create the redstone configuration view
|
||||||
---@param tool_ctl _rtu_cfg_tool_ctl
|
---@param tool_ctl _rtu_cfg_tool_ctl
|
||||||
---@param main_pane MultiPane
|
---@param main_pane MultiPane
|
||||||
@@ -125,20 +157,89 @@ function redstone.create(tool_ctl, main_pane, cfg_sys, rs_cfg, style)
|
|||||||
|
|
||||||
--#region Redstone
|
--#region Redstone
|
||||||
|
|
||||||
local rs_c_1 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
local rs_c_1 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
||||||
local rs_c_2 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
local rs_c_2 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
||||||
local rs_c_3 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
local rs_c_3 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
||||||
local rs_c_4 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
local rs_c_4 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
||||||
local rs_c_5 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
local rs_c_5 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
||||||
local rs_c_6 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
local rs_c_6 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
||||||
local rs_c_7 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
local rs_c_7 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
||||||
|
local rs_c_8 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
||||||
|
local rs_c_9 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
||||||
|
local rs_c_10 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
||||||
|
|
||||||
local rs_pane = MultiPane{parent=rs_cfg,x=1,y=4,panes={rs_c_1,rs_c_2,rs_c_3,rs_c_4,rs_c_5,rs_c_6,rs_c_7}}
|
local rs_pane = MultiPane{parent=rs_cfg,x=1,y=4,panes={rs_c_1,rs_c_2,rs_c_3,rs_c_4,rs_c_5,rs_c_6,rs_c_7,rs_c_8,rs_c_9,rs_c_10}}
|
||||||
|
|
||||||
TextBox{parent=rs_cfg,x=1,y=2,text=" Redstone Connections",fg_bg=cpair(colors.black,colors.red)}
|
local header = TextBox{parent=rs_cfg,x=1,y=2,text=" Redstone Connections",fg_bg=cpair(colors.black,colors.red)}
|
||||||
|
|
||||||
TextBox{parent=rs_c_1,x=1,y=1,text=" port side/color unit/facility",fg_bg=g_lg_fg_bg}
|
--#region Interface Selection
|
||||||
local rs_list = ListBox{parent=rs_c_1,x=1,y=2,height=11,width=49,scroll_height=200,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
|
|
||||||
|
TextBox{parent=rs_c_1,x=1,y=1,text="Configure this computer or a redstone relay."}
|
||||||
|
local iface_list = ListBox{parent=rs_c_1,x=1,y=3,height=10,width=49,scroll_height=1000,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
|
||||||
|
|
||||||
|
-- update relay interface list
|
||||||
|
function tool_ctl.update_relay_list()
|
||||||
|
local mounts = ppm.list_mounts()
|
||||||
|
|
||||||
|
iface_list.remove_all()
|
||||||
|
|
||||||
|
-- assemble list of configured relays
|
||||||
|
local relays = {}
|
||||||
|
for i = 1, #tmp_cfg.Redstone do
|
||||||
|
local def = tmp_cfg.Redstone[i]
|
||||||
|
if def.relay and not util.table_contains(relays, def.relay) then
|
||||||
|
table.insert(relays, def.relay)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- add unconfigured connected relays
|
||||||
|
for name, entry in pairs(mounts) do
|
||||||
|
if entry.type == "redstone_relay" and not util.table_contains(relays, name) then
|
||||||
|
table.insert(relays, name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function config_rs(name)
|
||||||
|
header.set_value(" Redstone Connections (" .. name .. ")")
|
||||||
|
|
||||||
|
self.rs_cfg_phy = tri(name == "local", nil, name)
|
||||||
|
|
||||||
|
tool_ctl.gen_rs_summary()
|
||||||
|
rs_pane.set_value(2)
|
||||||
|
end
|
||||||
|
|
||||||
|
local line = Div{parent=iface_list,height=2,fg_bg=cpair(colors.black,colors.white)}
|
||||||
|
TextBox{parent=line,x=1,y=1,text="@ local",fg_bg=cpair(colors.black,colors.white)}
|
||||||
|
TextBox{parent=line,x=3,y=2,text="This Computer",fg_bg=cpair(colors.gray,colors.white)}
|
||||||
|
local count = #redstone_subset(ini_cfg.Redstone, nil)
|
||||||
|
TextBox{parent=line,x=33,y=2,width=16,alignment=core.ALIGN.RIGHT,text=count.." connections",fg_bg=cpair(colors.gray,colors.white)}
|
||||||
|
|
||||||
|
PushButton{parent=line,x=41,y=1,min_width=8,height=1,text="CONFIG",callback=function()config_rs("local")end,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
||||||
|
|
||||||
|
for i = 1, #relays do
|
||||||
|
local name = relays[i]
|
||||||
|
|
||||||
|
line = Div{parent=iface_list,height=2,fg_bg=cpair(colors.black,colors.white)}
|
||||||
|
TextBox{parent=line,x=1,y=1,text="@ "..name,fg_bg=cpair(colors.black,colors.white)}
|
||||||
|
TextBox{parent=line,x=3,y=2,text="Redstone Relay",fg_bg=cpair(colors.gray,colors.white)}
|
||||||
|
TextBox{parent=line,x=18,y=2,text=tri(mounts[name],"ONLINE","OFFLINE"),fg_bg=cpair(tri(mounts[name],colors.green,colors.red),colors.white)}
|
||||||
|
count = #redstone_subset(ini_cfg.Redstone, name)
|
||||||
|
TextBox{parent=line,x=33,y=2,width=16,alignment=core.ALIGN.RIGHT,text=count.." connections",fg_bg=cpair(colors.gray,colors.white)}
|
||||||
|
|
||||||
|
PushButton{parent=line,x=41,y=1,min_width=8,height=1,text="CONFIG",callback=function()config_rs(name)end,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
tool_ctl.update_relay_list()
|
||||||
|
|
||||||
|
PushButton{parent=rs_c_1,x=1,y=14,text="\x1b Back",callback=function()main_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
PushButton{parent=rs_c_1,x=27,y=14,min_width=23,text="I don't see my relay!",callback=function()rs_pane.set_value(10)end,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
--#region Configuration List
|
||||||
|
|
||||||
|
TextBox{parent=rs_c_2,x=1,y=1,text=" port side/color unit/facility",fg_bg=g_lg_fg_bg}
|
||||||
|
local rs_list = ListBox{parent=rs_c_2,x=1,y=2,height=11,width=49,scroll_height=200,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
|
||||||
|
|
||||||
local function rs_revert()
|
local function rs_revert()
|
||||||
tmp_cfg.Redstone = tool_ctl.deep_copy_rs(ini_cfg.Redstone)
|
tmp_cfg.Redstone = tool_ctl.deep_copy_rs(ini_cfg.Redstone)
|
||||||
@@ -146,43 +247,47 @@ function redstone.create(tool_ctl, main_pane, cfg_sys, rs_cfg, style)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function rs_apply()
|
local function rs_apply()
|
||||||
settings.set("Redstone", tmp_cfg.Redstone)
|
-- add the changed data to the existing saved data
|
||||||
|
local new_data = redstone_subset(tmp_cfg.Redstone, self.rs_cfg_phy)
|
||||||
|
local new_save = redstone_subset(ini_cfg.Redstone, self.rs_cfg_phy, true)
|
||||||
|
for i = 1, #new_data do table.insert(new_save, new_data[i]) end
|
||||||
|
|
||||||
|
settings.set("Redstone", new_save)
|
||||||
|
|
||||||
if settings.save("/rtu.settings") then
|
if settings.save("/rtu.settings") then
|
||||||
load_settings(settings_cfg, true)
|
load_settings(settings_cfg, true)
|
||||||
load_settings(ini_cfg)
|
load_settings(ini_cfg)
|
||||||
rs_pane.set_value(4)
|
rs_pane.set_value(5)
|
||||||
|
|
||||||
-- for return to list from saved screen
|
-- for return to list from saved screen
|
||||||
|
-- this will delete unsaved changes for other phy's, which is acceptable
|
||||||
tmp_cfg.Redstone = tool_ctl.deep_copy_rs(ini_cfg.Redstone)
|
tmp_cfg.Redstone = tool_ctl.deep_copy_rs(ini_cfg.Redstone)
|
||||||
tool_ctl.gen_rs_summary()
|
tool_ctl.gen_rs_summary()
|
||||||
|
tool_ctl.update_relay_list()
|
||||||
else
|
else
|
||||||
rs_pane.set_value(5)
|
rs_pane.set_value(6)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
PushButton{parent=rs_c_1,x=1,y=14,text="\x1b Back",callback=function()main_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
local function rs_back()
|
||||||
local rs_revert_btn = PushButton{parent=rs_c_1,x=8,y=14,min_width=16,text="Revert Changes",callback=rs_revert,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
self.rs_cfg_phy = false
|
||||||
PushButton{parent=rs_c_1,x=35,y=14,min_width=7,text="New +",callback=function()rs_pane.set_value(2)end,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg}
|
rs_pane.set_value(1)
|
||||||
local rs_apply_btn = PushButton{parent=rs_c_1,x=43,y=14,min_width=7,text="Apply",callback=rs_apply,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
header.set_value(" Redstone Connections")
|
||||||
|
end
|
||||||
|
|
||||||
TextBox{parent=rs_c_6,x=1,y=1,height=5,text="You already configured this input. There can only be one entry for each input.\n\nPlease select a different port."}
|
PushButton{parent=rs_c_2,x=1,y=14,text="\x1b Back",callback=rs_back,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
PushButton{parent=rs_c_6,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
local rs_revert_btn = PushButton{parent=rs_c_2,x=8,y=14,min_width=16,text="Revert Changes",callback=rs_revert,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
||||||
|
PushButton{parent=rs_c_2,x=35,y=14,min_width=7,text="New +",callback=function()rs_pane.set_value(3)end,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg}
|
||||||
|
local rs_apply_btn = PushButton{parent=rs_c_2,x=43,y=14,min_width=7,text="Apply",callback=rs_apply,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=rs_c_2,x=1,y=1,text="Select one of the below ports to use."}
|
--#endregion
|
||||||
|
--#region Port Selection
|
||||||
|
|
||||||
local rs_ports = ListBox{parent=rs_c_2,x=1,y=3,height=10,width=49,scroll_height=200,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
|
TextBox{parent=rs_c_3,x=1,y=1,text="Select one of the below ports to use."}
|
||||||
|
|
||||||
|
local rs_ports = ListBox{parent=rs_c_3,x=1,y=3,height=10,width=49,scroll_height=200,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
|
||||||
|
|
||||||
local function new_rs(port)
|
local function new_rs(port)
|
||||||
if (rsio.get_io_dir(port) == rsio.IO_DIR.IN) then
|
|
||||||
for i = 1, #tmp_cfg.Redstone do
|
|
||||||
if tmp_cfg.Redstone[i].port == port then
|
|
||||||
rs_pane.set_value(6)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
self.rs_cfg_editing = false
|
self.rs_cfg_editing = false
|
||||||
|
|
||||||
local text
|
local text
|
||||||
@@ -191,6 +296,8 @@ function redstone.create(tool_ctl, main_pane, cfg_sys, rs_cfg, style)
|
|||||||
self.rs_cfg_color.hide(true)
|
self.rs_cfg_color.hide(true)
|
||||||
self.rs_cfg_shortcut.show()
|
self.rs_cfg_shortcut.show()
|
||||||
self.rs_cfg_side_l.set_value("Output Side")
|
self.rs_cfg_side_l.set_value("Output Side")
|
||||||
|
self.rs_cfg_bundled.enable()
|
||||||
|
self.rs_cfg_advanced.disable()
|
||||||
text = "You selected the ALL_WASTE shortcut."
|
text = "You selected the ALL_WASTE shortcut."
|
||||||
else
|
else
|
||||||
self.rs_cfg_shortcut.hide(true)
|
self.rs_cfg_shortcut.hide(true)
|
||||||
@@ -205,9 +312,13 @@ function redstone.create(tool_ctl, main_pane, cfg_sys, rs_cfg, style)
|
|||||||
self.rs_cfg_bundled.set_value(false)
|
self.rs_cfg_bundled.set_value(false)
|
||||||
self.rs_cfg_bundled.disable()
|
self.rs_cfg_bundled.disable()
|
||||||
self.rs_cfg_color.disable()
|
self.rs_cfg_color.disable()
|
||||||
|
self.rs_cfg_inverted.set_value(false)
|
||||||
|
self.rs_cfg_advanced.disable()
|
||||||
else
|
else
|
||||||
self.rs_cfg_bundled.enable()
|
self.rs_cfg_bundled.enable()
|
||||||
if self.rs_cfg_bundled.get_value() then self.rs_cfg_color.enable() else self.rs_cfg_color.disable() end
|
if self.rs_cfg_bundled.get_value() then self.rs_cfg_color.enable() else self.rs_cfg_color.disable() end
|
||||||
|
self.rs_cfg_inverted.set_value(false)
|
||||||
|
self.rs_cfg_advanced.enable()
|
||||||
end
|
end
|
||||||
|
|
||||||
if io_mode == IO_MODE.DIGITAL_IN then
|
if io_mode == IO_MODE.DIGITAL_IN then
|
||||||
@@ -233,7 +344,7 @@ function redstone.create(tool_ctl, main_pane, cfg_sys, rs_cfg, style)
|
|||||||
|
|
||||||
self.rs_cfg_selection.set_value(text)
|
self.rs_cfg_selection.set_value(text)
|
||||||
self.rs_cfg_port = port
|
self.rs_cfg_port = port
|
||||||
rs_pane.set_value(3)
|
rs_pane.set_value(4)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- add entries to redstone option list
|
-- add entries to redstone option list
|
||||||
@@ -254,43 +365,43 @@ function redstone.create(tool_ctl, main_pane, cfg_sys, rs_cfg, style)
|
|||||||
TextBox{parent=entry,x=22,y=1,text=PORT_DESC_MAP[i][2],fg_bg=cpair(colors.gray,colors.white)}
|
TextBox{parent=entry,x=22,y=1,text=PORT_DESC_MAP[i][2],fg_bg=cpair(colors.gray,colors.white)}
|
||||||
end
|
end
|
||||||
|
|
||||||
PushButton{parent=rs_c_2,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=rs_c_3,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
self.rs_cfg_selection = TextBox{parent=rs_c_3,x=1,y=1,height=2,text=""}
|
--#endregion
|
||||||
|
--#region Port Configuration
|
||||||
|
|
||||||
PushButton{parent=rs_c_3,x=36,y=3,text="What's that?",min_width=14,callback=function()rs_pane.set_value(7)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
self.rs_cfg_selection = TextBox{parent=rs_c_4,x=1,y=1,height=2,text=""}
|
||||||
|
|
||||||
TextBox{parent=rs_c_7,x=1,y=1,height=4,text="(Normal) Digital Input: On if there is a redstone signal, off otherwise\nInverted Digital Input: On without a redstone signal, off otherwise"}
|
PushButton{parent=rs_c_4,x=36,y=3,text="What's that?",min_width=14,callback=function()rs_pane.set_value(8)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
TextBox{parent=rs_c_7,x=1,y=6,height=4,text="(Normal) Digital Output: Redstone signal to 'turn it on', none to 'turn it off'\nInverted Digital Output: No redstone signal to 'turn it on', redstone signal to 'turn it off'"}
|
|
||||||
TextBox{parent=rs_c_7,x=1,y=11,height=2,text="Analog Input: 0-15 redstone power level input\nAnalog Output: 0-15 scaled redstone power level output"}
|
|
||||||
PushButton{parent=rs_c_7,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(3)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
|
||||||
|
|
||||||
self.rs_cfg_side_l = TextBox{parent=rs_c_3,x=1,y=4,width=11,text="Output Side"}
|
self.rs_cfg_side_l = TextBox{parent=rs_c_4,x=1,y=4,width=11,text="Output Side"}
|
||||||
local side = Radio2D{parent=rs_c_3,x=1,y=5,rows=1,columns=6,default=1,options=side_options,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.red}
|
local side = Radio2D{parent=rs_c_4,x=1,y=5,rows=1,columns=6,default=1,options=side_options,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.red}
|
||||||
|
|
||||||
self.rs_cfg_unit_l = TextBox{parent=rs_c_3,x=25,y=7,width=7,text="Unit ID"}
|
self.rs_cfg_unit_l = TextBox{parent=rs_c_4,x=25,y=7,width=7,text="Unit ID"}
|
||||||
self.rs_cfg_unit = NumberField{parent=rs_c_3,x=33,y=7,width=10,max_chars=2,min=1,max=4,fg_bg=bw_fg_bg}
|
self.rs_cfg_unit = NumberField{parent=rs_c_4,x=33,y=7,width=10,max_chars=2,min=1,max=4,fg_bg=bw_fg_bg}
|
||||||
|
|
||||||
local function set_bundled(bundled)
|
local function set_bundled(bundled)
|
||||||
if bundled then self.rs_cfg_color.enable() else self.rs_cfg_color.disable() end
|
if bundled then self.rs_cfg_color.enable() else self.rs_cfg_color.disable() end
|
||||||
end
|
end
|
||||||
|
|
||||||
self.rs_cfg_shortcut = TextBox{parent=rs_c_3,x=1,y=9,height=4,text="This shortcut will add entries for each of the 4 waste outputs. If you select bundled, 4 colors will be assigned to the selected side. Otherwise, 4 default sides will be used."}
|
self.rs_cfg_shortcut = TextBox{parent=rs_c_4,x=1,y=9,height=4,text="This shortcut will add entries for each of the 4 waste outputs. If you select bundled, 4 colors will be assigned to the selected side. Otherwise, 4 default sides will be used."}
|
||||||
self.rs_cfg_shortcut.hide(true)
|
self.rs_cfg_shortcut.hide(true)
|
||||||
|
|
||||||
self.rs_cfg_bundled = Checkbox{parent=rs_c_3,x=1,y=7,label="Is Bundled?",default=false,box_fg_bg=cpair(colors.red,colors.black),callback=set_bundled,disable_fg_bg=g_lg_fg_bg}
|
self.rs_cfg_bundled = Checkbox{parent=rs_c_4,x=1,y=7,label="Is Bundled?",default=false,box_fg_bg=cpair(colors.red,colors.black),callback=set_bundled,disable_fg_bg=g_lg_fg_bg}
|
||||||
self.rs_cfg_color = Radio2D{parent=rs_c_3,x=1,y=9,rows=4,columns=4,default=1,options=color_options,radio_colors=cpair(colors.lightGray,colors.black),color_map=color_options_map,disable_color=colors.gray,disable_fg_bg=g_lg_fg_bg}
|
self.rs_cfg_color = Radio2D{parent=rs_c_4,x=1,y=9,rows=4,columns=4,default=1,options=color_options,radio_colors=cpair(colors.lightGray,colors.black),color_map=color_options_map,disable_color=colors.gray,disable_fg_bg=g_lg_fg_bg}
|
||||||
self.rs_cfg_color.disable()
|
self.rs_cfg_color.disable()
|
||||||
|
|
||||||
local rs_err = TextBox{parent=rs_c_3,x=8,y=14,width=30,text="Unit ID must be within 1 to 4.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
local rs_err = TextBox{parent=rs_c_4,x=8,y=14,width=30,text="Unit ID invalid.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||||
rs_err.hide(true)
|
rs_err.hide(true)
|
||||||
|
|
||||||
local function back_from_rs_opts()
|
local function back_from_rs_opts()
|
||||||
rs_err.hide(true)
|
rs_err.hide(true)
|
||||||
if self.rs_cfg_editing ~= false then rs_pane.set_value(1) else rs_pane.set_value(2) end
|
if self.rs_cfg_editing ~= false then rs_pane.set_value(2) else rs_pane.set_value(3) end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function save_rs_entry()
|
local function save_rs_entry()
|
||||||
|
assert(self.rs_cfg_phy ~= false, "tried to save a redstone entry without a phy")
|
||||||
|
|
||||||
local port = self.rs_cfg_port
|
local port = self.rs_cfg_port
|
||||||
local u = tonumber(self.rs_cfg_unit.get_value())
|
local u = tonumber(self.rs_cfg_unit.get_value())
|
||||||
|
|
||||||
@@ -302,11 +413,23 @@ function redstone.create(tool_ctl, main_pane, cfg_sys, rs_cfg, style)
|
|||||||
local def = {
|
local def = {
|
||||||
unit = tri(PORT_DSGN[port] == 1, u, nil),
|
unit = tri(PORT_DSGN[port] == 1, u, nil),
|
||||||
port = port,
|
port = port,
|
||||||
|
relay = self.rs_cfg_phy,
|
||||||
side = side_options_map[side.get_value()],
|
side = side_options_map[side.get_value()],
|
||||||
color = tri(self.rs_cfg_bundled.get_value() and rsio.is_digital(port), color_options_map[self.rs_cfg_color.get_value()], nil)
|
color = tri(self.rs_cfg_bundled.get_value() and rsio.is_digital(port), color_options_map[self.rs_cfg_color.get_value()], nil),
|
||||||
|
invert = self.rs_cfg_inverted.get_value() or nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.rs_cfg_editing == false then
|
if self.rs_cfg_editing == false then
|
||||||
|
-- check for duplicate inputs for this unit/facility
|
||||||
|
if (rsio.get_io_dir(port) == rsio.IO_DIR.IN) then
|
||||||
|
for i = 1, #tmp_cfg.Redstone do
|
||||||
|
if tmp_cfg.Redstone[i].port == port and tmp_cfg.Redstone[i].unit == def.unit then
|
||||||
|
rs_pane.set_value(7)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
table.insert(tmp_cfg.Redstone, def)
|
table.insert(tmp_cfg.Redstone, def)
|
||||||
else
|
else
|
||||||
def.port = tmp_cfg.Redstone[self.rs_cfg_editing].port
|
def.port = tmp_cfg.Redstone[self.rs_cfg_editing].port
|
||||||
@@ -319,33 +442,55 @@ function redstone.create(tool_ctl, main_pane, cfg_sys, rs_cfg, style)
|
|||||||
table.insert(tmp_cfg.Redstone, {
|
table.insert(tmp_cfg.Redstone, {
|
||||||
unit = tri(PORT_DSGN[IO.WASTE_PU + i] == 1, u, nil),
|
unit = tri(PORT_DSGN[IO.WASTE_PU + i] == 1, u, nil),
|
||||||
port = IO.WASTE_PU + i,
|
port = IO.WASTE_PU + i,
|
||||||
|
relay = self.rs_cfg_phy,
|
||||||
side = tri(self.rs_cfg_bundled.get_value(), side_options_map[side.get_value()], default_sides[i + 1]),
|
side = tri(self.rs_cfg_bundled.get_value(), side_options_map[side.get_value()], default_sides[i + 1]),
|
||||||
color = tri(self.rs_cfg_bundled.get_value(), default_colors[i + 1], nil)
|
color = tri(self.rs_cfg_bundled.get_value(), default_colors[i + 1], nil)
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
rs_pane.set_value(1)
|
rs_pane.set_value(2)
|
||||||
tool_ctl.gen_rs_summary()
|
tool_ctl.gen_rs_summary()
|
||||||
|
|
||||||
side.set_value(1)
|
side.set_value(1)
|
||||||
self.rs_cfg_bundled.set_value(false)
|
self.rs_cfg_bundled.set_value(false)
|
||||||
self.rs_cfg_color.set_value(1)
|
self.rs_cfg_color.set_value(1)
|
||||||
self.rs_cfg_color.disable()
|
self.rs_cfg_color.disable()
|
||||||
|
self.rs_cfg_inverted.set_value(false)
|
||||||
|
self.rs_cfg_advanced.disable()
|
||||||
else rs_err.show() end
|
else rs_err.show() end
|
||||||
end
|
end
|
||||||
|
|
||||||
PushButton{parent=rs_c_3,x=1,y=14,text="\x1b Back",callback=back_from_rs_opts,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=rs_c_4,x=1,y=14,text="\x1b Back",callback=back_from_rs_opts,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
PushButton{parent=rs_c_3,x=41,y=14,min_width=9,text="Confirm",callback=save_rs_entry,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg}
|
self.rs_cfg_advanced = PushButton{parent=rs_c_4,x=30,y=14,min_width=10,text="Advanced",callback=function()rs_pane.set_value(9)end,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
||||||
|
PushButton{parent=rs_c_4,x=41,y=14,min_width=9,text="Confirm",callback=save_rs_entry,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=rs_c_4,x=1,y=1,text="Settings saved!"}
|
--#endregion
|
||||||
PushButton{parent=rs_c_4,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
|
||||||
PushButton{parent=rs_c_4,x=44,y=14,min_width=6,text="Home",callback=function()tool_ctl.go_home()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
|
||||||
|
|
||||||
TextBox{parent=rs_c_5,x=1,y=1,height=5,text="Failed to save the settings file.\n\nThere may not be enough space for the modification or server file permissions may be denying writes."}
|
TextBox{parent=rs_c_5,x=1,y=1,text="Settings saved!"}
|
||||||
PushButton{parent=rs_c_5,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=rs_c_5,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
PushButton{parent=rs_c_5,x=44,y=14,min_width=6,text="Home",callback=function()tool_ctl.go_home()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=rs_c_5,x=44,y=14,min_width=6,text="Home",callback=function()tool_ctl.go_home()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
|
TextBox{parent=rs_c_6,x=1,y=1,height=5,text="Failed to save the settings file.\n\nThere may not be enough space for the modification or server file permissions may be denying writes."}
|
||||||
|
PushButton{parent=rs_c_6,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
PushButton{parent=rs_c_6,x=44,y=14,min_width=6,text="Home",callback=function()tool_ctl.go_home()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
|
TextBox{parent=rs_c_7,x=1,y=1,height=6,text="You already configured this input for this facility/unit assignment. There can only be one entry for each input per each unit or the facility (for facility inputs).\n\nPlease select a different port."}
|
||||||
|
PushButton{parent=rs_c_7,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(3)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
|
TextBox{parent=rs_c_8,x=1,y=1,height=4,text="(Normal) Digital Input: On if there is a redstone signal, off otherwise\nInverted Digital Input: On without a redstone signal, off otherwise"}
|
||||||
|
TextBox{parent=rs_c_8,x=1,y=6,height=4,text="(Normal) Digital Output: Redstone signal to 'turn it on', none to 'turn it off'\nInverted Digital Output: No redstone signal to 'turn it on', redstone signal to 'turn it off'"}
|
||||||
|
TextBox{parent=rs_c_8,x=1,y=11,height=2,text="Analog Input: 0-15 redstone power level input\nAnalog Output: 0-15 scaled redstone power level output"}
|
||||||
|
PushButton{parent=rs_c_8,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(4)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
|
TextBox{parent=rs_c_9,x=1,y=1,height=5,text="Advanced Options"}
|
||||||
|
self.rs_cfg_inverted = Checkbox{parent=rs_c_9,x=1,y=3,label="Invert",default=false,box_fg_bg=cpair(colors.red,colors.black),disable_fg_bg=g_lg_fg_bg}
|
||||||
|
TextBox{parent=rs_c_9,x=3,y=4,height=4,text="Digital I/O is already inverted (or not) based on intended use. If you have a non-standard setup, you can use this option to avoid needing a redstone inverter.",fg_bg=cpair(colors.gray,colors.lightGray)}
|
||||||
|
PushButton{parent=rs_c_9,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(4)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
|
TextBox{parent=rs_c_10,x=1,y=1,height=10,text="Make sure your relay is either touching the RTU gateway or connected via wired modems. There should be a wired modem on a side of the RTU gateway then one on the device, connected by a cable. The modem on the device needs to be right clicked to connect it (which will turn its border red), at which point the peripheral name will be shown in the chat."}
|
||||||
|
PushButton{parent=rs_c_10,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
--#endregion
|
--#endregion
|
||||||
|
|
||||||
--#region Tool Functions
|
--#region Tool Functions
|
||||||
@@ -374,9 +519,11 @@ function redstone.create(tool_ctl, main_pane, cfg_sys, rs_cfg, style)
|
|||||||
if rsio.is_analog(def.port) then
|
if rsio.is_analog(def.port) then
|
||||||
self.rs_cfg_bundled.set_value(false)
|
self.rs_cfg_bundled.set_value(false)
|
||||||
self.rs_cfg_bundled.disable()
|
self.rs_cfg_bundled.disable()
|
||||||
|
self.rs_cfg_advanced.disable()
|
||||||
else
|
else
|
||||||
self.rs_cfg_bundled.enable()
|
self.rs_cfg_bundled.enable()
|
||||||
self.rs_cfg_bundled.set_value(def.color ~= nil)
|
self.rs_cfg_bundled.set_value(def.color ~= nil)
|
||||||
|
self.rs_cfg_advanced.enable()
|
||||||
end
|
end
|
||||||
|
|
||||||
local value = 1
|
local value = 1
|
||||||
@@ -391,7 +538,8 @@ function redstone.create(tool_ctl, main_pane, cfg_sys, rs_cfg, style)
|
|||||||
self.rs_cfg_side_l.set_value(tri(rsio.get_io_dir(def.port) == rsio.IO_DIR.IN, "Input Side", "Output Side"))
|
self.rs_cfg_side_l.set_value(tri(rsio.get_io_dir(def.port) == rsio.IO_DIR.IN, "Input Side", "Output Side"))
|
||||||
side.set_value(side_to_idx(def.side))
|
side.set_value(side_to_idx(def.side))
|
||||||
self.rs_cfg_color.set_value(value)
|
self.rs_cfg_color.set_value(value)
|
||||||
rs_pane.set_value(3)
|
self.rs_cfg_inverted.set_value(def.invert or false)
|
||||||
|
rs_pane.set_value(4)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function delete_rs_entry(idx)
|
local function delete_rs_entry(idx)
|
||||||
@@ -401,33 +549,41 @@ function redstone.create(tool_ctl, main_pane, cfg_sys, rs_cfg, style)
|
|||||||
|
|
||||||
-- generate the redstone summary list
|
-- generate the redstone summary list
|
||||||
function tool_ctl.gen_rs_summary()
|
function tool_ctl.gen_rs_summary()
|
||||||
|
assert(self.rs_cfg_phy ~= false, "tried to generate a summary without a phy set")
|
||||||
|
|
||||||
rs_list.remove_all()
|
rs_list.remove_all()
|
||||||
|
|
||||||
local modified = #ini_cfg.Redstone ~= #tmp_cfg.Redstone
|
local ini = redstone_subset(ini_cfg.Redstone, self.rs_cfg_phy)
|
||||||
|
local tmp = redstone_subset(tmp_cfg.Redstone, self.rs_cfg_phy)
|
||||||
|
|
||||||
|
local modified = #ini ~= #tmp
|
||||||
|
|
||||||
for i = 1, #tmp_cfg.Redstone do
|
for i = 1, #tmp_cfg.Redstone do
|
||||||
local def = tmp_cfg.Redstone[i]
|
local def = tmp_cfg.Redstone[i]
|
||||||
|
|
||||||
local name = rsio.to_string(def.port)
|
if def.relay == self.rs_cfg_phy then
|
||||||
local io_dir = tri(rsio.get_io_mode(def.port) == rsio.IO_DIR.IN, "\x1a", "\x1b")
|
local name = rsio.to_string(def.port)
|
||||||
local conn = def.side
|
local io_dir = tri(rsio.get_io_dir(def.port) == rsio.IO_DIR.IN, "\x1a", "\x1b")
|
||||||
local unit = util.strval(def.unit or "F")
|
local io_c = tri(rsio.is_digital(def.port), colors.blue, colors.purple)
|
||||||
|
local conn = def.side
|
||||||
|
local unit = util.strval(def.unit or "F")
|
||||||
|
|
||||||
if def.color ~= nil then conn = def.side .. "/" .. rsio.color_name(def.color) end
|
if def.color ~= nil then conn = def.side .. "/" .. rsio.color_name(def.color) end
|
||||||
|
|
||||||
local entry = Div{parent=rs_list,height=1}
|
local entry = Div{parent=rs_list,height=1}
|
||||||
TextBox{parent=entry,x=1,y=1,width=1,text=io_dir,fg_bg=cpair(colors.lightGray,colors.white)}
|
TextBox{parent=entry,x=1,y=1,width=1,text=io_dir,fg_bg=cpair(tri(def.invert,colors.orange,io_c),colors.white)}
|
||||||
TextBox{parent=entry,x=2,y=1,width=14,text=name}
|
TextBox{parent=entry,x=2,y=1,width=14,text=name}
|
||||||
TextBox{parent=entry,x=16,y=1,width=string.len(conn),text=conn,fg_bg=cpair(colors.gray,colors.white)}
|
TextBox{parent=entry,x=16,y=1,width=string.len(conn),text=conn,fg_bg=cpair(colors.gray,colors.white)}
|
||||||
TextBox{parent=entry,x=33,y=1,width=1,text=unit,fg_bg=cpair(colors.gray,colors.white)}
|
TextBox{parent=entry,x=33,y=1,width=1,text=unit,fg_bg=cpair(colors.gray,colors.white)}
|
||||||
PushButton{parent=entry,x=35,y=1,min_width=6,height=1,text="EDIT",callback=function()edit_rs_entry(i)end,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=entry,x=35,y=1,min_width=6,height=1,text="EDIT",callback=function()edit_rs_entry(i)end,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg}
|
||||||
PushButton{parent=entry,x=41,y=1,min_width=8,height=1,text="DELETE",callback=function()delete_rs_entry(i)end,fg_bg=cpair(colors.black,colors.red),active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=entry,x=41,y=1,min_width=8,height=1,text="DELETE",callback=function()delete_rs_entry(i)end,fg_bg=cpair(colors.black,colors.red),active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
if not modified then
|
if not modified then
|
||||||
local a = ini_cfg.Redstone[i]
|
local a = ini_cfg.Redstone[i]
|
||||||
local b = tmp_cfg.Redstone[i]
|
local b = tmp_cfg.Redstone[i]
|
||||||
|
|
||||||
modified = (a.unit ~= b.unit) or (a.port ~= b.port) or (a.side ~= b.side) or (a.color ~= b.color)
|
modified = (a.unit ~= b.unit) or (a.port ~= b.port) or (a.relay ~= b.relay) or (a.side ~= b.side) or (a.color ~= b.color) or (a.invert ~= b.invert)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -30,9 +30,13 @@ local self = {
|
|||||||
importing_legacy = false,
|
importing_legacy = false,
|
||||||
importing_any_dc = false,
|
importing_any_dc = false,
|
||||||
|
|
||||||
show_auth_key = nil, ---@type function
|
wireless = nil, ---@type Checkbox
|
||||||
show_key_btn = nil, ---@type PushButton
|
wl_pref = nil, ---@type Checkbox
|
||||||
auth_key_textbox = nil, ---@type TextBox
|
wired = nil, ---@type Checkbox
|
||||||
|
range = nil, ---@type NumberField
|
||||||
|
show_auth_key = nil, ---@type function
|
||||||
|
show_key_btn = nil, ---@type PushButton
|
||||||
|
auth_key_textbox = nil, ---@type TextBox
|
||||||
auth_key_value = ""
|
auth_key_value = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,22 +94,88 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style)
|
|||||||
local net_c_1 = Div{parent=net_cfg,x=2,y=4,width=49}
|
local net_c_1 = Div{parent=net_cfg,x=2,y=4,width=49}
|
||||||
local net_c_2 = Div{parent=net_cfg,x=2,y=4,width=49}
|
local net_c_2 = Div{parent=net_cfg,x=2,y=4,width=49}
|
||||||
local net_c_3 = Div{parent=net_cfg,x=2,y=4,width=49}
|
local net_c_3 = Div{parent=net_cfg,x=2,y=4,width=49}
|
||||||
|
local net_c_4 = Div{parent=net_cfg,x=2,y=4,width=49}
|
||||||
|
|
||||||
local net_pane = MultiPane{parent=net_cfg,x=1,y=4,panes={net_c_1,net_c_2,net_c_3}}
|
local net_pane = MultiPane{parent=net_cfg,x=1,y=4,panes={net_c_1,net_c_2,net_c_3,net_c_4}}
|
||||||
|
|
||||||
TextBox{parent=net_cfg,x=1,y=2,text=" Network Configuration",fg_bg=cpair(colors.black,colors.lightBlue)}
|
TextBox{parent=net_cfg,x=1,y=2,text=" Network Configuration",fg_bg=cpair(colors.black,colors.lightBlue)}
|
||||||
|
|
||||||
TextBox{parent=net_c_1,x=1,y=1,text="Please set the network channels below."}
|
TextBox{parent=net_c_1,x=1,y=1,text="Please select the network interface(s)."}
|
||||||
TextBox{parent=net_c_1,x=1,y=3,height=4,text="Each of the 5 uniquely named channels, including the 2 below, must be the same for each device in this SCADA network. For multiplayer servers, it is recommended to not use the default channels.",fg_bg=g_lg_fg_bg}
|
TextBox{parent=net_c_1,x=41,y=1,text="new!",fg_bg=cpair(colors.red,colors._INHERIT)} ---@todo remove NEW tag on next revision
|
||||||
|
|
||||||
TextBox{parent=net_c_1,x=1,y=8,text="Supervisor Channel"}
|
local function en_dis_pref()
|
||||||
local svr_chan = NumberField{parent=net_c_1,x=1,y=9,width=7,default=ini_cfg.SVR_Channel,min=1,max=65535,fg_bg=bw_fg_bg}
|
if self.wireless.get_value() and self.wired.get_value() then
|
||||||
TextBox{parent=net_c_1,x=9,y=9,height=4,text="[SVR_CHANNEL]",fg_bg=g_lg_fg_bg}
|
self.wl_pref.enable()
|
||||||
TextBox{parent=net_c_1,x=1,y=11,text="RTU Channel"}
|
else
|
||||||
local rtu_chan = NumberField{parent=net_c_1,x=1,y=12,width=7,default=ini_cfg.RTU_Channel,min=1,max=65535,fg_bg=bw_fg_bg}
|
self.wl_pref.set_value(self.wireless.get_value())
|
||||||
TextBox{parent=net_c_1,x=9,y=12,height=4,text="[RTU_CHANNEL]",fg_bg=g_lg_fg_bg}
|
self.wl_pref.disable()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
local chan_err = TextBox{parent=net_c_1,x=8,y=14,width=35,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
local function on_wired_change(_)
|
||||||
|
en_dis_pref()
|
||||||
|
tool_ctl.gen_modem_list()
|
||||||
|
end
|
||||||
|
|
||||||
|
self.wireless = Checkbox{parent=net_c_1,x=1,y=3,label="Wireless/Ender Modem",default=ini_cfg.WirelessModem,box_fg_bg=cpair(colors.lightBlue,colors.black),callback=en_dis_pref}
|
||||||
|
self.wl_pref = Checkbox{parent=net_c_1,x=30,y=3,label="Prefer Wireless",default=ini_cfg.PreferWireless,box_fg_bg=cpair(colors.lightBlue,colors.black),disable_fg_bg=g_lg_fg_bg}
|
||||||
|
self.wired = Checkbox{parent=net_c_1,x=1,y=5,label="Wired Modem",default=ini_cfg.WiredModem~=false,box_fg_bg=cpair(colors.lightBlue,colors.black),callback=on_wired_change}
|
||||||
|
TextBox{parent=net_c_1,x=3,y=6,text="this one MUST ONLY connect to SCADA computers",fg_bg=cpair(colors.red,colors._INHERIT)}
|
||||||
|
TextBox{parent=net_c_1,x=3,y=7,text="connecting it to peripherals will cause issues",fg_bg=g_lg_fg_bg}
|
||||||
|
local modem_list = ListBox{parent=net_c_1,x=1,y=8,height=5,width=49,scroll_height=100,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
|
||||||
|
|
||||||
|
local modem_err = TextBox{parent=net_c_1,x=8,y=14,width=35,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||||
|
|
||||||
|
en_dis_pref()
|
||||||
|
|
||||||
|
local function submit_interfaces()
|
||||||
|
tmp_cfg.WirelessModem = self.wireless.get_value()
|
||||||
|
|
||||||
|
if tmp_cfg.WirelessModem and tmp_cfg.WiredModem then
|
||||||
|
tmp_cfg.PreferWireless = self.wl_pref.get_value()
|
||||||
|
else
|
||||||
|
tmp_cfg.PreferWireless = tmp_cfg.WirelessModem
|
||||||
|
self.wl_pref.set_value(tmp_cfg.PreferWireless)
|
||||||
|
end
|
||||||
|
|
||||||
|
if not self.wired.get_value() then
|
||||||
|
tmp_cfg.WiredModem = false
|
||||||
|
tool_ctl.gen_modem_list()
|
||||||
|
end
|
||||||
|
|
||||||
|
if not (self.wired.get_value() or self.wireless.get_value()) then
|
||||||
|
modem_err.set_value("Please select a modem type.")
|
||||||
|
modem_err.show()
|
||||||
|
elseif self.wired.get_value() and type(tmp_cfg.WiredModem) ~= "string" then
|
||||||
|
modem_err.set_value("Please select a wired modem.")
|
||||||
|
modem_err.show()
|
||||||
|
else
|
||||||
|
if tmp_cfg.WirelessModem then
|
||||||
|
self.range.enable()
|
||||||
|
else
|
||||||
|
self.range.set_value(0)
|
||||||
|
self.range.disable()
|
||||||
|
end
|
||||||
|
|
||||||
|
net_pane.set_value(2)
|
||||||
|
modem_err.hide(true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
PushButton{parent=net_c_1,x=1,y=14,text="\x1b Back",callback=function()main_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
PushButton{parent=net_c_1,x=44,y=14,text="Next \x1a",callback=submit_interfaces,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
|
TextBox{parent=net_c_2,x=1,y=1,text="Please set the network channels below."}
|
||||||
|
TextBox{parent=net_c_2,x=1,y=3,height=4,text="Each of the 5 uniquely named channels, including the 2 below, must be the same for each device in this SCADA network. For multiplayer servers, it is recommended to not use the default channels.",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
|
TextBox{parent=net_c_2,x=1,y=8,text="Supervisor Channel"}
|
||||||
|
local svr_chan = NumberField{parent=net_c_2,x=1,y=9,width=7,default=ini_cfg.SVR_Channel,min=1,max=65535,fg_bg=bw_fg_bg}
|
||||||
|
TextBox{parent=net_c_2,x=9,y=9,height=4,text="[SVR_CHANNEL]",fg_bg=g_lg_fg_bg}
|
||||||
|
TextBox{parent=net_c_2,x=1,y=11,text="RTU Channel"}
|
||||||
|
local rtu_chan = NumberField{parent=net_c_2,x=1,y=12,width=7,default=ini_cfg.RTU_Channel,min=1,max=65535,fg_bg=bw_fg_bg}
|
||||||
|
TextBox{parent=net_c_2,x=9,y=12,height=4,text="[RTU_CHANNEL]",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
|
local chan_err = TextBox{parent=net_c_2,x=8,y=14,width=35,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||||
|
|
||||||
local function submit_channels()
|
local function submit_channels()
|
||||||
local svr_c = tonumber(svr_chan.get_value())
|
local svr_c = tonumber(svr_chan.get_value())
|
||||||
@@ -113,7 +183,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style)
|
|||||||
if svr_c ~= nil and rtu_c ~= nil then
|
if svr_c ~= nil and rtu_c ~= nil then
|
||||||
tmp_cfg.SVR_Channel = svr_c
|
tmp_cfg.SVR_Channel = svr_c
|
||||||
tmp_cfg.RTU_Channel = rtu_c
|
tmp_cfg.RTU_Channel = rtu_c
|
||||||
net_pane.set_value(2)
|
net_pane.set_value(3)
|
||||||
chan_err.hide(true)
|
chan_err.hide(true)
|
||||||
elseif svr_c == nil then
|
elseif svr_c == nil then
|
||||||
chan_err.set_value("Please set the supervisor channel.")
|
chan_err.set_value("Please set the supervisor channel.")
|
||||||
@@ -124,54 +194,62 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
PushButton{parent=net_c_1,x=1,y=14,text="\x1b Back",callback=function()main_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=net_c_2,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
PushButton{parent=net_c_1,x=44,y=14,text="Next \x1a",callback=submit_channels,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=net_c_2,x=44,y=14,text="Next \x1a",callback=submit_channels,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=net_c_2,x=1,y=1,text="Connection Timeout"}
|
TextBox{parent=net_c_3,x=1,y=1,text="Connection Timeout"}
|
||||||
local timeout = NumberField{parent=net_c_2,x=1,y=2,width=7,default=ini_cfg.ConnTimeout,min=2,max=25,max_chars=6,max_frac_digits=2,allow_decimal=true,fg_bg=bw_fg_bg}
|
local timeout = NumberField{parent=net_c_3,x=1,y=2,width=7,default=ini_cfg.ConnTimeout,min=2,max=25,max_chars=6,max_frac_digits=2,allow_decimal=true,fg_bg=bw_fg_bg}
|
||||||
TextBox{parent=net_c_2,x=9,y=2,height=2,text="seconds (default 5)",fg_bg=g_lg_fg_bg}
|
TextBox{parent=net_c_3,x=9,y=2,height=2,text="seconds (default 5)",fg_bg=g_lg_fg_bg}
|
||||||
TextBox{parent=net_c_2,x=1,y=3,height=4,text="You generally do not want or need to modify this. On slow servers, you can increase this to make the system wait longer before assuming a disconnection.",fg_bg=g_lg_fg_bg}
|
TextBox{parent=net_c_3,x=1,y=3,height=4,text="You generally do not want or need to modify this. On slow servers, you can increase this to make the system wait longer before assuming a disconnection.",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=net_c_2,x=1,y=8,text="Trusted Range"}
|
TextBox{parent=net_c_3,x=1,y=8,text="Trusted Range (Wireless Only)"}
|
||||||
local range = NumberField{parent=net_c_2,x=1,y=9,width=10,default=ini_cfg.TrustedRange,min=0,max_chars=20,allow_decimal=true,fg_bg=bw_fg_bg}
|
self.range = NumberField{parent=net_c_3,x=1,y=9,width=10,default=ini_cfg.TrustedRange,min=0,max_chars=20,allow_decimal=true,fg_bg=bw_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)}
|
||||||
TextBox{parent=net_c_2,x=1,y=10,height=4,text="Setting this to a value larger than 0 prevents connections with devices that many meters (blocks) away in any direction.",fg_bg=g_lg_fg_bg}
|
TextBox{parent=net_c_3,x=1,y=10,height=4,text="Setting this to a value larger than 0 prevents wireless connections with devices that many meters (blocks) away in any direction.",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
local p2_err = TextBox{parent=net_c_2,x=8,y=14,width=35,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
local n3_err = TextBox{parent=net_c_3,x=8,y=14,width=35,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||||
|
|
||||||
local function submit_ct_tr()
|
local function submit_ct_tr()
|
||||||
local timeout_val = tonumber(timeout.get_value())
|
local timeout_val = tonumber(timeout.get_value())
|
||||||
local range_val = tonumber(range.get_value())
|
local range_val = tonumber(self.range.get_value())
|
||||||
if timeout_val ~= nil and range_val ~= nil then
|
|
||||||
tmp_cfg.ConnTimeout = timeout_val
|
if timeout_val == nil then
|
||||||
tmp_cfg.TrustedRange = range_val
|
n3_err.set_value("Please set the connection timeout.")
|
||||||
net_pane.set_value(3)
|
n3_err.show()
|
||||||
p2_err.hide(true)
|
elseif tmp_cfg.WirelessModem and (range_val == nil) then
|
||||||
elseif timeout_val == nil then
|
n3_err.set_value("Please set the trusted range.")
|
||||||
p2_err.set_value("Please set the connection timeout.")
|
n3_err.show()
|
||||||
p2_err.show()
|
|
||||||
else
|
else
|
||||||
p2_err.set_value("Please set the trusted range.")
|
tmp_cfg.ConnTimeout = timeout_val
|
||||||
p2_err.show()
|
tmp_cfg.TrustedRange = tri(tmp_cfg.WirelessModem, range_val, 0)
|
||||||
|
|
||||||
|
if tmp_cfg.WirelessModem then
|
||||||
|
net_pane.set_value(4)
|
||||||
|
else
|
||||||
|
main_pane.set_value(4)
|
||||||
|
tmp_cfg.AuthKey = ""
|
||||||
|
end
|
||||||
|
|
||||||
|
n3_err.hide(true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
PushButton{parent=net_c_2,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=net_c_3,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
PushButton{parent=net_c_2,x=44,y=14,text="Next \x1a",callback=submit_ct_tr,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=net_c_3,x=44,y=14,text="Next \x1a",callback=submit_ct_tr,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=net_c_3,x=1,y=1,height=2,text="Optionally, set the facility authentication key below. Do NOT use one of your passwords."}
|
TextBox{parent=net_c_4,x=1,y=1,height=2,text="Optionally, set the facility authentication key below. Do NOT use one of your passwords."}
|
||||||
TextBox{parent=net_c_3,x=1,y=4,height=6,text="This enables verifying that messages are authentic, so it is intended for security on multiplayer servers. All devices on the same network MUST use the same key if any device has a key. This does result in some extra computation (can slow things down).",fg_bg=g_lg_fg_bg}
|
TextBox{parent=net_c_4,x=1,y=4,height=6,text="This enables verifying that messages are authentic, so it is intended for wireless security on multiplayer servers. All devices on the same wireless network MUST use the same key if any device has a key. This does result in some extra computation (can slow things down).",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=net_c_3,x=1,y=11,text="Facility Auth Key"}
|
TextBox{parent=net_c_4,x=1,y=11,text="Auth Key (Wireless Only, Not Used for Wired)"}
|
||||||
local key, _ = TextField{parent=net_c_3,x=1,y=12,max_len=64,value=ini_cfg.AuthKey,width=32,height=1,fg_bg=bw_fg_bg}
|
local key, _ = TextField{parent=net_c_4,x=1,y=12,max_len=64,value=ini_cfg.AuthKey,width=32,height=1,fg_bg=bw_fg_bg}
|
||||||
|
|
||||||
local function censor_key(enable) key.censor(tri(enable, "*", nil)) end
|
local function censor_key(enable) key.censor(tri(enable, "*", nil)) end
|
||||||
|
|
||||||
local hide_key = Checkbox{parent=net_c_3,x=34,y=12,label="Hide",box_fg_bg=cpair(colors.lightBlue,colors.black),callback=censor_key}
|
local hide_key = Checkbox{parent=net_c_4,x=34,y=12,label="Hide",box_fg_bg=cpair(colors.lightBlue,colors.black),callback=censor_key}
|
||||||
|
|
||||||
hide_key.set_value(true)
|
hide_key.set_value(true)
|
||||||
censor_key(true)
|
censor_key(true)
|
||||||
|
|
||||||
local key_err = TextBox{parent=net_c_3,x=8,y=14,width=35,text="Key must be at least 8 characters.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
local key_err = TextBox{parent=net_c_4,x=8,y=14,width=35,text="Key must be at least 8 characters.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||||
|
|
||||||
local function submit_auth()
|
local function submit_auth()
|
||||||
local v = key.get_value()
|
local v = key.get_value()
|
||||||
@@ -182,8 +260,8 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style)
|
|||||||
else key_err.show() end
|
else key_err.show() end
|
||||||
end
|
end
|
||||||
|
|
||||||
PushButton{parent=net_c_3,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=net_c_4,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(3)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
PushButton{parent=net_c_3,x=44,y=14,text="Next \x1a",callback=submit_auth,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=net_c_4,x=44,y=14,text="Next \x1a",callback=submit_auth,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
--#endregion
|
--#endregion
|
||||||
|
|
||||||
@@ -196,7 +274,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style)
|
|||||||
TextBox{parent=log_c_1,x=1,y=1,text="Please configure logging below."}
|
TextBox{parent=log_c_1,x=1,y=1,text="Please configure logging below."}
|
||||||
|
|
||||||
TextBox{parent=log_c_1,x=1,y=3,text="Log File Mode"}
|
TextBox{parent=log_c_1,x=1,y=3,text="Log File Mode"}
|
||||||
local mode = RadioButton{parent=log_c_1,x=1,y=4,default=ini_cfg.LogMode+1,options={"Append on Startup","Replace on Startup"},callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.pink}
|
local mode = RadioButton{parent=log_c_1,x=1,y=4,default=ini_cfg.LogMode+1,options={"Append on Startup","Replace on Startup"},radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.pink}
|
||||||
|
|
||||||
TextBox{parent=log_c_1,x=1,y=7,text="Log File Path"}
|
TextBox{parent=log_c_1,x=1,y=7,text="Log File Path"}
|
||||||
local path = TextField{parent=log_c_1,x=1,y=8,width=49,height=1,value=ini_cfg.LogPath,max_len=128,fg_bg=bw_fg_bg}
|
local path = TextField{parent=log_c_1,x=1,y=8,width=49,height=1,value=ini_cfg.LogPath,max_len=128,fg_bg=bw_fg_bg}
|
||||||
@@ -238,7 +316,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style)
|
|||||||
TextBox{parent=clr_c_1,x=1,y=4,height=2,text="Click 'Accessibility' below to access colorblind assistive options.",fg_bg=g_lg_fg_bg}
|
TextBox{parent=clr_c_1,x=1,y=4,height=2,text="Click 'Accessibility' below to access colorblind assistive options.",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=clr_c_1,x=1,y=7,text="Front Panel Theme"}
|
TextBox{parent=clr_c_1,x=1,y=7,text="Front Panel Theme"}
|
||||||
local fp_theme = RadioButton{parent=clr_c_1,x=1,y=8,default=ini_cfg.FrontPanelTheme,options=themes.FP_THEME_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.magenta}
|
local fp_theme = RadioButton{parent=clr_c_1,x=1,y=8,default=ini_cfg.FrontPanelTheme,options=themes.FP_THEME_NAMES,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=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."}
|
||||||
|
|
||||||
@@ -382,10 +460,13 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style)
|
|||||||
load_settings(ini_cfg)
|
load_settings(ini_cfg)
|
||||||
|
|
||||||
try_set(s_vol, ini_cfg.SpeakerVolume)
|
try_set(s_vol, ini_cfg.SpeakerVolume)
|
||||||
|
try_set(self.wireless, ini_cfg.WirelessModem)
|
||||||
|
try_set(self.wired, ini_cfg.WiredModem ~= false)
|
||||||
|
try_set(self.wl_pref, ini_cfg.PreferWireless)
|
||||||
try_set(svr_chan, ini_cfg.SVR_Channel)
|
try_set(svr_chan, ini_cfg.SVR_Channel)
|
||||||
try_set(rtu_chan, ini_cfg.RTU_Channel)
|
try_set(rtu_chan, ini_cfg.RTU_Channel)
|
||||||
try_set(timeout, ini_cfg.ConnTimeout)
|
try_set(timeout, ini_cfg.ConnTimeout)
|
||||||
try_set(range, ini_cfg.TrustedRange)
|
try_set(self.range, ini_cfg.TrustedRange)
|
||||||
try_set(key, ini_cfg.AuthKey)
|
try_set(key, ini_cfg.AuthKey)
|
||||||
try_set(mode, ini_cfg.LogMode)
|
try_set(mode, ini_cfg.LogMode)
|
||||||
try_set(path, ini_cfg.LogPath)
|
try_set(path, ini_cfg.LogPath)
|
||||||
@@ -506,7 +587,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style)
|
|||||||
local u, idx = def.unit, def.index
|
local u, idx = def.unit, def.index
|
||||||
|
|
||||||
if util.table_contains(NEEDS_UNIT, mount.type) then
|
if util.table_contains(NEEDS_UNIT, mount.type) then
|
||||||
if (mount.type == "dynamicValve" or mount.type == "environmentDetector") and for_facility then
|
if (mount.type == "dynamicValve" or mount.type == "environmentDetector" or mount.type == "environment_detector") and for_facility then
|
||||||
-- skip
|
-- skip
|
||||||
elseif not (util.is_int(u) and u > 0 and u < 5) then
|
elseif not (util.is_int(u) and u > 0 and u < 5) then
|
||||||
err = true
|
err = true
|
||||||
@@ -527,7 +608,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style)
|
|||||||
else index = idx end
|
else index = idx end
|
||||||
elseif mount.type == "dynamicValve" then
|
elseif mount.type == "dynamicValve" then
|
||||||
index = 1
|
index = 1
|
||||||
elseif mount.type == "environmentDetector" then
|
elseif mount.type == "environmentDetector" or mount.type == "environment_detector" then
|
||||||
if not (util.is_int(idx) and idx > 0) then
|
if not (util.is_int(idx) and idx > 0) then
|
||||||
err = true
|
err = true
|
||||||
else index = idx end
|
else index = idx end
|
||||||
@@ -665,6 +746,59 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- generate the list of available/assigned wired modems
|
||||||
|
function tool_ctl.gen_modem_list()
|
||||||
|
modem_list.remove_all()
|
||||||
|
|
||||||
|
local enable = self.wired.get_value()
|
||||||
|
|
||||||
|
local function select(iface)
|
||||||
|
tmp_cfg.WiredModem = iface
|
||||||
|
tool_ctl.gen_modem_list()
|
||||||
|
end
|
||||||
|
|
||||||
|
local modems = ppm.get_wired_modem_list()
|
||||||
|
local missing = { tmp = true, ini = true }
|
||||||
|
|
||||||
|
for iface, _ in pairs(modems) do
|
||||||
|
if ini_cfg.WiredModem == iface then missing.ini = false end
|
||||||
|
if tmp_cfg.WiredModem == iface then missing.tmp = false end
|
||||||
|
end
|
||||||
|
|
||||||
|
if missing.tmp and tmp_cfg.WiredModem then
|
||||||
|
local line = Div{parent=modem_list,x=1,y=1,height=1}
|
||||||
|
|
||||||
|
TextBox{parent=line,x=1,y=1,width=4,text="Used",fg_bg=cpair(tri(enable,colors.blue,colors.gray),colors.white)}
|
||||||
|
PushButton{parent=line,x=6,y=1,min_width=8,height=1,text="SELECT",callback=function()end,fg_bg=cpair(colors.black,colors.lightBlue),active_fg_bg=btn_act_fg_bg,dis_fg_bg=g_lg_fg_bg}.disable()
|
||||||
|
TextBox{parent=line,x=15,y=1,text="[missing]",fg_bg=cpair(colors.red,colors.white)}
|
||||||
|
TextBox{parent=line,x=25,y=1,text=tmp_cfg.WiredModem}
|
||||||
|
end
|
||||||
|
|
||||||
|
if missing.ini and ini_cfg.WiredModem and (tmp_cfg.WiredModem ~= ini_cfg.WiredModem) then
|
||||||
|
local line = Div{parent=modem_list,x=1,y=1,height=1}
|
||||||
|
local used = tmp_cfg.WiredModem == ini_cfg.WiredModem
|
||||||
|
|
||||||
|
TextBox{parent=line,x=1,y=1,width=4,text=tri(used,"Used","----"),fg_bg=cpair(tri(used and enable,colors.blue,colors.gray),colors.white)}
|
||||||
|
local select_btn = PushButton{parent=line,x=6,y=1,min_width=8,height=1,text="SELECT",callback=function()select(ini_cfg.WiredModem)end,fg_bg=cpair(colors.black,colors.lightBlue),active_fg_bg=btn_act_fg_bg,dis_fg_bg=g_lg_fg_bg}
|
||||||
|
TextBox{parent=line,x=15,y=1,text="[missing]",fg_bg=cpair(colors.red,colors.white)}
|
||||||
|
TextBox{parent=line,x=25,y=1,text=ini_cfg.WiredModem}
|
||||||
|
|
||||||
|
if used or not enable then select_btn.disable() end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- list wired modems
|
||||||
|
for iface, _ in pairs(modems) do
|
||||||
|
local line = Div{parent=modem_list,x=1,y=1,height=1}
|
||||||
|
local used = tmp_cfg.WiredModem == iface
|
||||||
|
|
||||||
|
TextBox{parent=line,x=1,y=1,width=4,text=tri(used,"Used","----"),fg_bg=cpair(tri(used and enable,colors.blue,colors.gray),colors.white)}
|
||||||
|
local select_btn = PushButton{parent=line,x=6,y=1,min_width=8,height=1,text="SELECT",callback=function()select(iface)end,fg_bg=cpair(colors.black,colors.lightBlue),active_fg_bg=btn_act_fg_bg,dis_fg_bg=g_lg_fg_bg}
|
||||||
|
TextBox{parent=line,x=15,y=1,text=iface}
|
||||||
|
|
||||||
|
if used or not enable then select_btn.disable() end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
--#endregion
|
--#endregion
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ local ppm = require("scada-common.ppm")
|
|||||||
local tcd = require("scada-common.tcd")
|
local tcd = require("scada-common.tcd")
|
||||||
local util = require("scada-common.util")
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
|
local check = require("rtu.config.check")
|
||||||
local peripherals = require("rtu.config.peripherals")
|
local peripherals = require("rtu.config.peripherals")
|
||||||
local redstone = require("rtu.config.redstone")
|
local redstone = require("rtu.config.redstone")
|
||||||
local system = require("rtu.config.system")
|
local system = require("rtu.config.system")
|
||||||
@@ -34,7 +35,10 @@ local changes = {
|
|||||||
{ "v1.7.9", { "ConnTimeout can now have a fractional part" } },
|
{ "v1.7.9", { "ConnTimeout can now have a fractional part" } },
|
||||||
{ "v1.7.15", { "Added front panel UI theme", "Added color accessibility modes" } },
|
{ "v1.7.15", { "Added front panel UI theme", "Added color accessibility modes" } },
|
||||||
{ "v1.9.2", { "Added standard with black off state color mode", "Added blue indicator color modes" } },
|
{ "v1.9.2", { "Added standard with black off state color mode", "Added blue indicator color modes" } },
|
||||||
{ "v1.10.2", { "Re-organized peripheral configuration UI, resulting in some input fields being re-ordered" } }
|
{ "v1.10.2", { "Re-organized peripheral configuration UI, resulting in some input fields being re-ordered" } },
|
||||||
|
{ "v1.11.8", { "Added advanced option to invert digital redstone signals" } },
|
||||||
|
{ "v1.12.0", { "Added support for redstone relays" } },
|
||||||
|
{ "v1.13.0", { "Added support for wired communications modems" } }
|
||||||
}
|
}
|
||||||
|
|
||||||
---@class rtu_configurator
|
---@class rtu_configurator
|
||||||
@@ -61,38 +65,44 @@ local tool_ctl = {
|
|||||||
viewing_config = false,
|
viewing_config = false,
|
||||||
jumped_to_color = false,
|
jumped_to_color = false,
|
||||||
|
|
||||||
view_gw_cfg = nil, ---@type PushButton
|
view_gw_cfg = nil, ---@type PushButton
|
||||||
dev_cfg = nil, ---@type PushButton
|
dev_cfg = nil, ---@type PushButton
|
||||||
rs_cfg = nil, ---@type PushButton
|
rs_cfg = nil, ---@type PushButton
|
||||||
color_cfg = nil, ---@type PushButton
|
color_cfg = nil, ---@type PushButton
|
||||||
color_next = nil, ---@type PushButton
|
color_next = nil, ---@type PushButton
|
||||||
color_apply = nil, ---@type PushButton
|
color_apply = nil, ---@type PushButton
|
||||||
settings_apply = nil, ---@type PushButton
|
settings_apply = nil, ---@type PushButton
|
||||||
settings_confirm = nil, ---@type PushButton
|
settings_confirm = nil, ---@type PushButton
|
||||||
|
|
||||||
go_home = nil, ---@type function
|
go_home = nil, ---@type function
|
||||||
gen_summary = nil, ---@type function
|
gen_summary = nil, ---@type function
|
||||||
load_legacy = nil, ---@type function
|
load_legacy = nil, ---@type function
|
||||||
update_peri_list = nil, ---@type function
|
update_peri_list = nil, ---@type function
|
||||||
gen_peri_summary = nil, ---@type function
|
update_relay_list = nil, ---@type function
|
||||||
gen_rs_summary = nil, ---@type function
|
gen_peri_summary = nil, ---@type function
|
||||||
|
gen_rs_summary = nil, ---@type function
|
||||||
|
|
||||||
|
gen_modem_list = function () end
|
||||||
}
|
}
|
||||||
|
|
||||||
---@class rtu_config
|
---@class rtu_config
|
||||||
local tmp_cfg = {
|
local tmp_cfg = {
|
||||||
SpeakerVolume = 1.0,
|
SpeakerVolume = 1.0,
|
||||||
Peripherals = {}, ---@type rtu_peri_definition[]
|
Peripherals = {}, ---@type rtu_peri_definition[]
|
||||||
Redstone = {}, ---@type rtu_rs_definition[]
|
Redstone = {}, ---@type rtu_rs_definition[]
|
||||||
SVR_Channel = nil, ---@type integer
|
WirelessModem = true,
|
||||||
RTU_Channel = nil, ---@type integer
|
WiredModem = false, ---@type string|false
|
||||||
ConnTimeout = nil, ---@type number
|
PreferWireless = true,
|
||||||
TrustedRange = nil, ---@type number
|
SVR_Channel = nil, ---@type integer
|
||||||
AuthKey = nil, ---@type string|nil
|
RTU_Channel = nil, ---@type integer
|
||||||
LogMode = 0, ---@type LOG_MODE
|
ConnTimeout = nil, ---@type number
|
||||||
|
TrustedRange = nil, ---@type number
|
||||||
|
AuthKey = nil, ---@type string
|
||||||
|
LogMode = 0, ---@type LOG_MODE
|
||||||
LogPath = "",
|
LogPath = "",
|
||||||
LogDebug = false,
|
LogDebug = false,
|
||||||
FrontPanelTheme = 1, ---@type FP_THEME
|
FrontPanelTheme = 1, ---@type FP_THEME
|
||||||
ColorMode = 1 ---@type COLOR_MODE
|
ColorMode = 1 ---@type COLOR_MODE
|
||||||
}
|
}
|
||||||
|
|
||||||
---@class rtu_config
|
---@class rtu_config
|
||||||
@@ -102,6 +112,9 @@ local settings_cfg = {}
|
|||||||
|
|
||||||
local fields = {
|
local fields = {
|
||||||
{ "SpeakerVolume", "Speaker Volume", 1.0 },
|
{ "SpeakerVolume", "Speaker Volume", 1.0 },
|
||||||
|
{ "WirelessModem", "Wireless/Ender Comms Modem", true },
|
||||||
|
{ "WiredModem", "Wired Comms Modem", false },
|
||||||
|
{ "PreferWireless", "Prefer Wireless Modem", true },
|
||||||
{ "SVR_Channel", "SVR Channel", 16240 },
|
{ "SVR_Channel", "SVR Channel", 16240 },
|
||||||
{ "RTU_Channel", "RTU Channel", 16242 },
|
{ "RTU_Channel", "RTU Channel", 16242 },
|
||||||
{ "ConnTimeout", "Connection Timeout", 5 },
|
{ "ConnTimeout", "Connection Timeout", 5 },
|
||||||
@@ -115,6 +128,7 @@ local fields = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
-- deep copy peripherals defs
|
-- deep copy peripherals defs
|
||||||
|
---@param data rtu_peri_definition[]
|
||||||
function tool_ctl.deep_copy_peri(data)
|
function tool_ctl.deep_copy_peri(data)
|
||||||
local array = {}
|
local array = {}
|
||||||
for _, d in ipairs(data) do table.insert(array, { unit = d.unit, index = d.index, name = d.name }) end
|
for _, d in ipairs(data) do table.insert(array, { unit = d.unit, index = d.index, name = d.name }) end
|
||||||
@@ -122,9 +136,10 @@ function tool_ctl.deep_copy_peri(data)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- deep copy redstone defs
|
-- deep copy redstone defs
|
||||||
|
---@param data rtu_rs_definition[]
|
||||||
function tool_ctl.deep_copy_rs(data)
|
function tool_ctl.deep_copy_rs(data)
|
||||||
local array = {}
|
local array = {}
|
||||||
for _, d in ipairs(data) do table.insert(array, { unit = d.unit, port = d.port, side = d.side, color = d.color }) end
|
for _, d in ipairs(data) do table.insert(array, { unit = d.unit, port = d.port, relay = d.relay, side = d.side, color = d.color, invert = d.invert }) end
|
||||||
return array
|
return array
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -169,8 +184,9 @@ local function config_view(display)
|
|||||||
local changelog = Div{parent=root_pane_div,x=1,y=1}
|
local changelog = Div{parent=root_pane_div,x=1,y=1}
|
||||||
local peri_cfg = Div{parent=root_pane_div,x=1,y=1}
|
local peri_cfg = Div{parent=root_pane_div,x=1,y=1}
|
||||||
local rs_cfg = Div{parent=root_pane_div,x=1,y=1}
|
local rs_cfg = Div{parent=root_pane_div,x=1,y=1}
|
||||||
|
local check_sys = Div{parent=root_pane_div,x=1,y=1}
|
||||||
|
|
||||||
local main_pane = MultiPane{parent=root_pane_div,x=1,y=1,panes={main_page,spkr_cfg,net_cfg,log_cfg,clr_cfg,summary,changelog,peri_cfg,rs_cfg}}
|
local main_pane = MultiPane{parent=root_pane_div,x=1,y=1,panes={main_page,spkr_cfg,net_cfg,log_cfg,clr_cfg,summary,changelog,peri_cfg,rs_cfg,check_sys}}
|
||||||
|
|
||||||
--#region Main Page
|
--#region Main Page
|
||||||
|
|
||||||
@@ -203,7 +219,6 @@ local function config_view(display)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function show_rs_conns()
|
local function show_rs_conns()
|
||||||
tool_ctl.gen_rs_summary()
|
|
||||||
main_pane.set_value(9)
|
main_pane.set_value(9)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -226,8 +241,9 @@ local function config_view(display)
|
|||||||
|
|
||||||
PushButton{parent=main_page,x=2,y=17,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=main_page,x=2,y=17,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=btn_act_fg_bg}
|
||||||
local start_btn = PushButton{parent=main_page,x=42,y=17,min_width=9,text="Startup",callback=startup,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
local start_btn = PushButton{parent=main_page,x=42,y=17,min_width=9,text="Startup",callback=startup,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
||||||
tool_ctl.color_cfg = PushButton{parent=main_page,x=36,y=y_start,min_width=15,text="Color Options",callback=jump_color,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
PushButton{parent=main_page,x=39,y=y_start,min_width=12,text="Self-Check",callback=function()main_pane.set_value(10)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
||||||
PushButton{parent=main_page,x=39,y=y_start+2,min_width=12,text="Change Log",callback=function()main_pane.set_value(7)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
tool_ctl.color_cfg = PushButton{parent=main_page,x=36,y=y_start+2,min_width=15,text="Color Options",callback=jump_color,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
||||||
|
PushButton{parent=main_page,x=39,y=y_start+4,min_width=12,text="Change Log",callback=function()main_pane.set_value(7)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
if tool_ctl.ask_config then start_btn.disable() end
|
if tool_ctl.ask_config then start_btn.disable() end
|
||||||
|
|
||||||
@@ -283,6 +299,12 @@ local function config_view(display)
|
|||||||
PushButton{parent=cl,x=1,y=14,text="\x1b Back",callback=function()main_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=cl,x=1,y=14,text="\x1b Back",callback=function()main_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
--#endregion
|
--#endregion
|
||||||
|
|
||||||
|
--#region Self-Check
|
||||||
|
|
||||||
|
check.create(main_pane, settings_cfg, check_sys, style)
|
||||||
|
|
||||||
|
--#endregion
|
||||||
end
|
end
|
||||||
|
|
||||||
-- reset terminal screen
|
-- reset terminal screen
|
||||||
@@ -300,6 +322,9 @@ function configurator.configure(ask_config)
|
|||||||
|
|
||||||
load_settings(settings_cfg, true)
|
load_settings(settings_cfg, true)
|
||||||
tool_ctl.has_config = load_settings(ini_cfg)
|
tool_ctl.has_config = load_settings(ini_cfg)
|
||||||
|
|
||||||
|
-- set tmp_cfg so interface lists are correct
|
||||||
|
tmp_cfg.WiredModem = ini_cfg.WiredModem
|
||||||
tmp_cfg.Peripherals = tool_ctl.deep_copy_peri(ini_cfg.Peripherals)
|
tmp_cfg.Peripherals = tool_ctl.deep_copy_peri(ini_cfg.Peripherals)
|
||||||
tmp_cfg.Redstone = tool_ctl.deep_copy_rs(ini_cfg.Redstone)
|
tmp_cfg.Redstone = tool_ctl.deep_copy_rs(ini_cfg.Redstone)
|
||||||
|
|
||||||
@@ -316,8 +341,10 @@ function configurator.configure(ask_config)
|
|||||||
local display = DisplayBox{window=term.current(),fg_bg=style.root}
|
local display = DisplayBox{window=term.current(),fg_bg=style.root}
|
||||||
config_view(display)
|
config_view(display)
|
||||||
|
|
||||||
|
tool_ctl.gen_modem_list()
|
||||||
|
|
||||||
while true do
|
while true do
|
||||||
local event, param1, param2, param3 = util.pull_event()
|
local event, param1, param2, param3, param4, param5 = util.pull_event()
|
||||||
|
|
||||||
-- handle event
|
-- handle event
|
||||||
if event == "timer" then
|
if event == "timer" then
|
||||||
@@ -330,14 +357,22 @@ function configurator.configure(ask_config)
|
|||||||
if k_e then display.handle_key(k_e) end
|
if k_e then display.handle_key(k_e) end
|
||||||
elseif event == "paste" then
|
elseif event == "paste" then
|
||||||
display.handle_paste(param1)
|
display.handle_paste(param1)
|
||||||
|
elseif event == "modem_message" then
|
||||||
|
check.receive_sv(param1, param2, param3, param4, param5)
|
||||||
|
elseif event == "conn_test_complete" then
|
||||||
|
check.conn_test_callback(param1)
|
||||||
elseif event == "peripheral_detach" then
|
elseif event == "peripheral_detach" then
|
||||||
---@diagnostic disable-next-line: discard-returns
|
---@diagnostic disable-next-line: discard-returns
|
||||||
ppm.handle_unmount(param1)
|
ppm.handle_unmount(param1)
|
||||||
tool_ctl.update_peri_list()
|
tool_ctl.update_peri_list()
|
||||||
|
tool_ctl.update_relay_list()
|
||||||
|
tool_ctl.gen_modem_list()
|
||||||
elseif event == "peripheral" then
|
elseif event == "peripheral" then
|
||||||
---@diagnostic disable-next-line: discard-returns
|
---@diagnostic disable-next-line: discard-returns
|
||||||
ppm.mount(param1)
|
ppm.mount(param1)
|
||||||
tool_ctl.update_peri_list()
|
tool_ctl.update_peri_list()
|
||||||
|
tool_ctl.update_relay_list()
|
||||||
|
tool_ctl.gen_modem_list()
|
||||||
end
|
end
|
||||||
|
|
||||||
if event == "terminate" then return end
|
if event == "terminate" then return end
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ databus.RTU_HW_STATE = RTU_HW_STATE
|
|||||||
-- call to toggle heartbeat signal
|
-- call to toggle heartbeat signal
|
||||||
function databus.heartbeat() databus.ps.toggle("heartbeat") end
|
function databus.heartbeat() databus.ps.toggle("heartbeat") end
|
||||||
|
|
||||||
-- transmit firmware versions across the bus
|
-- transmit firmware versions
|
||||||
---@param rtu_v string RTU version
|
---@param rtu_v string RTU version
|
||||||
---@param comms_v string comms version
|
---@param comms_v string comms version
|
||||||
function databus.tx_versions(rtu_v, comms_v)
|
function databus.tx_versions(rtu_v, comms_v)
|
||||||
@@ -31,10 +31,16 @@ function databus.tx_versions(rtu_v, comms_v)
|
|||||||
databus.ps.publish("comms_version", comms_v)
|
databus.ps.publish("comms_version", comms_v)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- transmit hardware status for modem connection state
|
-- transmit hardware status for the wired comms modem
|
||||||
---@param has_modem boolean
|
---@param has_modem boolean
|
||||||
function databus.tx_hw_modem(has_modem)
|
function databus.tx_hw_wd_modem(has_modem)
|
||||||
databus.ps.publish("has_modem", has_modem)
|
databus.ps.publish("has_wd_modem", has_modem)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- transmit hardware status for the wireless comms modem
|
||||||
|
---@param has_modem boolean
|
||||||
|
function databus.tx_hw_wl_modem(has_modem)
|
||||||
|
databus.ps.publish("has_wl_modem", has_modem)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- transmit the number of speakers connected
|
-- transmit the number of speakers connected
|
||||||
@@ -43,14 +49,14 @@ function databus.tx_hw_spkr_count(count)
|
|||||||
databus.ps.publish("speaker_count", count)
|
databus.ps.publish("speaker_count", count)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- transmit unit hardware type across the bus
|
-- transmit unit hardware type
|
||||||
---@param uid integer unit ID
|
---@param uid integer unit ID
|
||||||
---@param type RTU_UNIT_TYPE
|
---@param type RTU_UNIT_TYPE
|
||||||
function databus.tx_unit_hw_type(uid, type)
|
function databus.tx_unit_hw_type(uid, type)
|
||||||
databus.ps.publish("unit_type_" .. uid, type)
|
databus.ps.publish("unit_type_" .. uid, type)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- transmit unit hardware status across the bus
|
-- transmit unit hardware status
|
||||||
---@param uid integer unit ID
|
---@param uid integer unit ID
|
||||||
---@param status RTU_HW_STATE
|
---@param status RTU_HW_STATE
|
||||||
function databus.tx_unit_hw_status(uid, status)
|
function databus.tx_unit_hw_status(uid, status)
|
||||||
@@ -64,17 +70,10 @@ function databus.tx_rt_status(thread, ok)
|
|||||||
databus.ps.publish(util.c("routine__", thread), ok)
|
databus.ps.publish(util.c("routine__", thread), ok)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- transmit supervisor link state across the bus
|
-- transmit supervisor link state
|
||||||
---@param state integer
|
---@param state integer
|
||||||
function databus.tx_link_state(state)
|
function databus.tx_link_state(state)
|
||||||
databus.ps.publish("link_state", state)
|
databus.ps.publish("link_state", state)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- link a function to receive data from the bus
|
|
||||||
---@param field string field name
|
|
||||||
---@param func function function to link
|
|
||||||
function databus.rx_field(field, func)
|
|
||||||
databus.ps.subscribe(field, func)
|
|
||||||
end
|
|
||||||
|
|
||||||
return databus
|
return databus
|
||||||
|
|||||||
@@ -11,10 +11,14 @@ local digital_write = rsio.digital_write
|
|||||||
|
|
||||||
-- create new redstone device
|
-- create new redstone device
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
|
---@param relay? table optional redstone relay to use instead of the computer's redstone interface
|
||||||
---@return rtu_rs_device interface, boolean faulted
|
---@return rtu_rs_device interface, boolean faulted
|
||||||
function redstone_rtu.new()
|
function redstone_rtu.new(relay)
|
||||||
local unit = rtu.init_unit()
|
local unit = rtu.init_unit()
|
||||||
|
|
||||||
|
-- physical interface to use
|
||||||
|
local phy = relay or rs
|
||||||
|
|
||||||
-- get RTU interface
|
-- get RTU interface
|
||||||
local interface = unit.interface()
|
local interface = unit.interface()
|
||||||
|
|
||||||
@@ -30,85 +34,114 @@ function redstone_rtu.new()
|
|||||||
write_holding_reg = interface.write_holding_reg
|
write_holding_reg = interface.write_holding_reg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
-- change the phy in use (a relay or rs)
|
||||||
|
---@param new_phy table
|
||||||
|
function public.remount_phy(new_phy) phy = new_phy end
|
||||||
|
|
||||||
|
-- NOTE: for runtime speed, inversion logic results in extra code here but less code when functions are called
|
||||||
|
|
||||||
-- link digital input
|
-- link digital input
|
||||||
---@param side string
|
---@param side string
|
||||||
---@param color integer
|
---@param color integer
|
||||||
function public.link_di(side, color)
|
---@param invert boolean|nil
|
||||||
local f_read ---@type function
|
---@return integer count count of digital inputs
|
||||||
|
function public.link_di(side, color, invert)
|
||||||
|
local f_read ---@type function
|
||||||
|
|
||||||
if color then
|
if color then
|
||||||
f_read = function ()
|
if invert then
|
||||||
return digital_read(rs.testBundledInput(side, color))
|
f_read = function () return digital_read(not phy.testBundledInput(side, color)) end
|
||||||
|
else
|
||||||
|
f_read = function () return digital_read(phy.testBundledInput(side, color)) end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
f_read = function ()
|
if invert then
|
||||||
return digital_read(rs.getInput(side))
|
f_read = function () return digital_read(not phy.getInput(side)) end
|
||||||
|
else
|
||||||
|
f_read = function () return digital_read(phy.getInput(side)) end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
unit.connect_di(f_read)
|
return unit.connect_di(f_read)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- link digital output
|
-- link digital output
|
||||||
---@param side string
|
---@param side string
|
||||||
---@param color integer
|
---@param color integer
|
||||||
function public.link_do(side, color)
|
---@param invert boolean|nil
|
||||||
local f_read ---@type function
|
---@return integer count count of digital outputs
|
||||||
local f_write ---@type function
|
function public.link_do(side, color, invert)
|
||||||
|
local f_read ---@type function
|
||||||
|
local f_write ---@type function
|
||||||
|
|
||||||
if color then
|
if color then
|
||||||
f_read = function ()
|
if invert then
|
||||||
return digital_read(colors.test(rs.getBundledOutput(side), color))
|
f_read = function () return digital_read(not colors.test(phy.getBundledOutput(side), color)) end
|
||||||
end
|
|
||||||
|
|
||||||
f_write = function (level)
|
f_write = function (level)
|
||||||
if level ~= IO_LVL.FLOATING and level ~= IO_LVL.DISCONNECT then
|
if level ~= IO_LVL.FLOATING and level ~= IO_LVL.DISCONNECT then
|
||||||
local output = rs.getBundledOutput(side)
|
local output = phy.getBundledOutput(side)
|
||||||
|
|
||||||
if digital_write(level) then
|
-- inverted conditions
|
||||||
output = colors.combine(output, color)
|
if digital_write(level) then
|
||||||
else
|
output = colors.subtract(output, color)
|
||||||
output = colors.subtract(output, color)
|
else output = colors.combine(output, color) end
|
||||||
|
|
||||||
|
phy.setBundledOutput(side, output)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
f_read = function () return digital_read(colors.test(phy.getBundledOutput(side), color)) end
|
||||||
|
|
||||||
rs.setBundledOutput(side, output)
|
f_write = function (level)
|
||||||
|
if level ~= IO_LVL.FLOATING and level ~= IO_LVL.DISCONNECT then
|
||||||
|
local output = phy.getBundledOutput(side)
|
||||||
|
|
||||||
|
if digital_write(level) then
|
||||||
|
output = colors.combine(output, color)
|
||||||
|
else output = colors.subtract(output, color) end
|
||||||
|
|
||||||
|
phy.setBundledOutput(side, output)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
f_read = function ()
|
if invert then
|
||||||
return digital_read(rs.getOutput(side))
|
f_read = function () return digital_read(not phy.getOutput(side)) end
|
||||||
end
|
|
||||||
|
|
||||||
f_write = function (level)
|
f_write = function (level)
|
||||||
if level ~= IO_LVL.FLOATING and level ~= IO_LVL.DISCONNECT then
|
if level ~= IO_LVL.FLOATING and level ~= IO_LVL.DISCONNECT then
|
||||||
rs.setOutput(side, digital_write(level))
|
phy.setOutput(side, not digital_write(level))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
f_read = function () return digital_read(phy.getOutput(side)) end
|
||||||
|
|
||||||
|
f_write = function (level)
|
||||||
|
if level ~= IO_LVL.FLOATING and level ~= IO_LVL.DISCONNECT then
|
||||||
|
phy.setOutput(side, digital_write(level))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
unit.connect_coil(f_read, f_write)
|
return unit.connect_coil(f_read, f_write)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- link analog input
|
-- link analog input
|
||||||
---@param side string
|
---@param side string
|
||||||
|
---@return integer count count of analog inputs
|
||||||
function public.link_ai(side)
|
function public.link_ai(side)
|
||||||
unit.connect_input_reg(
|
return unit.connect_input_reg(function () return phy.getAnalogInput(side) end)
|
||||||
function ()
|
|
||||||
return rs.getAnalogInput(side)
|
|
||||||
end
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- link analog output
|
-- link analog output
|
||||||
---@param side string
|
---@param side string
|
||||||
|
---@return integer count count of analog outputs
|
||||||
function public.link_ao(side)
|
function public.link_ao(side)
|
||||||
unit.connect_holding_reg(
|
return unit.connect_holding_reg(
|
||||||
function ()
|
function () return phy.getAnalogOutput(side) end,
|
||||||
return rs.getAnalogOutput(side)
|
function (value) phy.setAnalogOutput(side, value) end
|
||||||
end,
|
|
||||||
function (value)
|
|
||||||
rs.setAnalogOutput(side, value)
|
|
||||||
end
|
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -399,43 +399,41 @@ function modbus.new(rtu_dev, use_parallel_read)
|
|||||||
return public
|
return public
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- create an error reply
|
||||||
|
---@nodiscard
|
||||||
|
---@param packet modbus_frame MODBUS packet frame
|
||||||
|
---@param code MODBUS_EXCODE exception code
|
||||||
|
---@return modbus_packet reply
|
||||||
|
local function excode_reply(packet, code)
|
||||||
|
-- reply back with error flag and exception code
|
||||||
|
local reply = comms.modbus_packet()
|
||||||
|
local fcode = bit.bor(packet.func_code, MODBUS_FCODE.ERROR_FLAG)
|
||||||
|
reply.make(packet.txn_id, packet.unit_id, fcode, { code })
|
||||||
|
return reply
|
||||||
|
end
|
||||||
|
|
||||||
|
-- return a SERVER_DEVICE_FAIL error reply
|
||||||
|
---@nodiscard
|
||||||
|
---@param packet modbus_frame MODBUS packet frame
|
||||||
|
---@return modbus_packet reply
|
||||||
|
function modbus.reply__srv_device_fail(packet) return excode_reply(packet, MODBUS_EXCODE.SERVER_DEVICE_FAIL) end
|
||||||
|
|
||||||
-- return a SERVER_DEVICE_BUSY error reply
|
-- return a SERVER_DEVICE_BUSY error reply
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
---@param packet modbus_frame MODBUS packet frame
|
---@param packet modbus_frame MODBUS packet frame
|
||||||
---@return modbus_packet reply
|
---@return modbus_packet reply
|
||||||
function modbus.reply__srv_device_busy(packet)
|
function modbus.reply__srv_device_busy(packet) return excode_reply(packet, MODBUS_EXCODE.SERVER_DEVICE_BUSY) end
|
||||||
-- reply back with error flag and exception code
|
|
||||||
local reply = comms.modbus_packet()
|
|
||||||
local fcode = bit.bor(packet.func_code, MODBUS_FCODE.ERROR_FLAG)
|
|
||||||
local data = { MODBUS_EXCODE.SERVER_DEVICE_BUSY }
|
|
||||||
reply.make(packet.txn_id, packet.unit_id, fcode, data)
|
|
||||||
return reply
|
|
||||||
end
|
|
||||||
|
|
||||||
-- return a NEG_ACKNOWLEDGE error reply
|
-- return a NEG_ACKNOWLEDGE error reply
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
---@param packet modbus_frame MODBUS packet frame
|
---@param packet modbus_frame MODBUS packet frame
|
||||||
---@return modbus_packet reply
|
---@return modbus_packet reply
|
||||||
function modbus.reply__neg_ack(packet)
|
function modbus.reply__neg_ack(packet) return excode_reply(packet, MODBUS_EXCODE.NEG_ACKNOWLEDGE) end
|
||||||
-- reply back with error flag and exception code
|
|
||||||
local reply = comms.modbus_packet()
|
|
||||||
local fcode = bit.bor(packet.func_code, MODBUS_FCODE.ERROR_FLAG)
|
|
||||||
local data = { MODBUS_EXCODE.NEG_ACKNOWLEDGE }
|
|
||||||
reply.make(packet.txn_id, packet.unit_id, fcode, data)
|
|
||||||
return reply
|
|
||||||
end
|
|
||||||
|
|
||||||
-- return a GATEWAY_PATH_UNAVAILABLE error reply
|
-- return a GATEWAY_PATH_UNAVAILABLE error reply
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
---@param packet modbus_frame MODBUS packet frame
|
---@param packet modbus_frame MODBUS packet frame
|
||||||
---@return modbus_packet reply
|
---@return modbus_packet reply
|
||||||
function modbus.reply__gw_unavailable(packet)
|
function modbus.reply__gw_unavailable(packet) return excode_reply(packet, MODBUS_EXCODE.GATEWAY_PATH_UNAVAILABLE) end
|
||||||
-- reply back with error flag and exception code
|
|
||||||
local reply = comms.modbus_packet()
|
|
||||||
local fcode = bit.bor(packet.func_code, MODBUS_FCODE.ERROR_FLAG)
|
|
||||||
local data = { MODBUS_EXCODE.GATEWAY_PATH_UNAVAILABLE }
|
|
||||||
reply.make(packet.txn_id, packet.unit_id, fcode, data)
|
|
||||||
return reply
|
|
||||||
end
|
|
||||||
|
|
||||||
return modbus
|
return modbus
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ local style = require("rtu.panel.style")
|
|||||||
local core = require("graphics.core")
|
local core = require("graphics.core")
|
||||||
|
|
||||||
local Div = require("graphics.elements.Div")
|
local Div = require("graphics.elements.Div")
|
||||||
|
local Rectangle = require("graphics.elements.Rectangle")
|
||||||
local TextBox = require("graphics.elements.TextBox")
|
local TextBox = require("graphics.elements.TextBox")
|
||||||
|
|
||||||
local DataIndicator = require("graphics.elements.indicators.DataIndicator")
|
local DataIndicator = require("graphics.elements.indicators.DataIndicator")
|
||||||
@@ -19,11 +20,13 @@ local LED = require("graphics.elements.indicators.LED")
|
|||||||
local LEDPair = require("graphics.elements.indicators.LEDPair")
|
local LEDPair = require("graphics.elements.indicators.LEDPair")
|
||||||
local RGBLED = require("graphics.elements.indicators.RGBLED")
|
local RGBLED = require("graphics.elements.indicators.RGBLED")
|
||||||
|
|
||||||
local LINK_STATE = types.PANEL_LINK_STATE
|
local LINK_STATE = types.PANEL_LINK_STATE
|
||||||
|
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
|
||||||
|
|
||||||
local ALIGN = core.ALIGN
|
local ALIGN = core.ALIGN
|
||||||
|
|
||||||
local cpair = core.cpair
|
local cpair = core.cpair
|
||||||
|
local border = core.border
|
||||||
|
|
||||||
local ind_grn = style.ind_grn
|
local ind_grn = style.ind_grn
|
||||||
|
|
||||||
@@ -31,8 +34,11 @@ local UNIT_TYPE_LABELS = { "UNKNOWN", "REDSTONE", "BOILER", "TURBINE", "DYNAMIC
|
|||||||
|
|
||||||
-- create new front panel view
|
-- create new front panel view
|
||||||
---@param panel DisplayBox main displaybox
|
---@param panel DisplayBox main displaybox
|
||||||
|
---@param config rtu_config configuraiton
|
||||||
---@param units rtu_registry_entry[] unit list
|
---@param units rtu_registry_entry[] unit list
|
||||||
local function init(panel, units)
|
local function init(panel, config, units)
|
||||||
|
local s_hi_box = style.theme.highlight_box
|
||||||
|
|
||||||
local disabled_fg = style.fp.disabled_fg
|
local disabled_fg = style.fp.disabled_fg
|
||||||
|
|
||||||
local term_w, term_h = term.getSize()
|
local term_w, term_h = term.getSize()
|
||||||
@@ -52,7 +58,15 @@ local function init(panel, units)
|
|||||||
|
|
||||||
heartbeat.register(databus.ps, "heartbeat", heartbeat.update)
|
heartbeat.register(databus.ps, "heartbeat", heartbeat.update)
|
||||||
|
|
||||||
local modem = LED{parent=system,label="MODEM",colors=ind_grn}
|
if config.WirelessModem and config.WiredModem then
|
||||||
|
local wd_modem = LED{parent=system,label="WD MODEM",colors=ind_grn}
|
||||||
|
local wl_modem = LED{parent=system,label="WL MODEM",colors=ind_grn}
|
||||||
|
wd_modem.register(databus.ps, "has_wd_modem", wd_modem.update)
|
||||||
|
wl_modem.register(databus.ps, "has_wl_modem", wl_modem.update)
|
||||||
|
else
|
||||||
|
local modem = LED{parent=system,label="MODEM",colors=ind_grn}
|
||||||
|
modem.register(databus.ps, util.trinary(config.WirelessModem, "has_wl_modem", "has_wd_modem"), modem.update)
|
||||||
|
end
|
||||||
|
|
||||||
if not style.colorblind then
|
if not style.colorblind then
|
||||||
local network = RGBLED{parent=system,label="NETWORK",colors={colors.green,colors.red,colors.yellow,colors.orange,style.ind_bkg}}
|
local network = RGBLED{parent=system,label="NETWORK",colors={colors.green,colors.red,colors.yellow,colors.orange,style.ind_bkg}}
|
||||||
@@ -89,8 +103,6 @@ local function init(panel, units)
|
|||||||
|
|
||||||
system.line_break()
|
system.line_break()
|
||||||
|
|
||||||
modem.register(databus.ps, "has_modem", modem.update)
|
|
||||||
|
|
||||||
local rt_main = LED{parent=system,label="RT MAIN",colors=ind_grn}
|
local rt_main = LED{parent=system,label="RT MAIN",colors=ind_grn}
|
||||||
local rt_comm = LED{parent=system,label="RT COMMS",colors=ind_grn}
|
local rt_comm = LED{parent=system,label="RT COMMS",colors=ind_grn}
|
||||||
system.line_break()
|
system.line_break()
|
||||||
@@ -98,25 +110,27 @@ local function init(panel, units)
|
|||||||
rt_main.register(databus.ps, "routine__main", rt_main.update)
|
rt_main.register(databus.ps, "routine__main", rt_main.update)
|
||||||
rt_comm.register(databus.ps, "routine__comms", rt_comm.update)
|
rt_comm.register(databus.ps, "routine__comms", rt_comm.update)
|
||||||
|
|
||||||
|
--
|
||||||
|
-- hardware labeling
|
||||||
|
--
|
||||||
|
|
||||||
|
local hw_labels = Rectangle{parent=panel,x=2,y=term_h-6,width=14,height=5,border=border(1,s_hi_box.bkg,true),even_inner=true}
|
||||||
|
|
||||||
---@diagnostic disable-next-line: undefined-field
|
---@diagnostic disable-next-line: undefined-field
|
||||||
local comp_id = util.sprintf("(%d)", os.getComputerID())
|
local comp_id = util.sprintf("%03d", os.getComputerID())
|
||||||
TextBox{parent=system,x=9,y=4,width=6,text=comp_id,fg_bg=disabled_fg}
|
|
||||||
|
|
||||||
TextBox{parent=system,y=term_h-5,text="SPEAKERS",width=8,fg_bg=style.fp.text_fg}
|
TextBox{parent=hw_labels,text="FW "..databus.ps.get("version"),fg_bg=s_hi_box}
|
||||||
local speaker_count = DataIndicator{parent=system,x=10,y=term_h-5,label="",format="%3d",value=0,width=3,fg_bg=style.theme.field_box}
|
TextBox{parent=hw_labels,text="NT v"..databus.ps.get("comms_version"),fg_bg=s_hi_box}
|
||||||
|
TextBox{parent=hw_labels,text="SN "..comp_id.."-RTU",fg_bg=s_hi_box}
|
||||||
|
|
||||||
|
--
|
||||||
|
-- speaker count
|
||||||
|
--
|
||||||
|
|
||||||
|
TextBox{parent=panel,x=2,y=term_h-1,text="SPEAKERS",width=8,fg_bg=style.fp.text_fg}
|
||||||
|
local speaker_count = DataIndicator{parent=panel,x=11,y=term_h-1,label="",format="%3d",value=0,width=3,fg_bg=style.theme.field_box}
|
||||||
speaker_count.register(databus.ps, "speaker_count", speaker_count.update)
|
speaker_count.register(databus.ps, "speaker_count", speaker_count.update)
|
||||||
|
|
||||||
--
|
|
||||||
-- about label
|
|
||||||
--
|
|
||||||
|
|
||||||
local about = Div{parent=panel,width=15,height=2,y=term_h-1,fg_bg=disabled_fg}
|
|
||||||
local fw_v = TextBox{parent=about,text="FW: v00.00.00"}
|
|
||||||
local comms_v = TextBox{parent=about,text="NT: v00.00.00"}
|
|
||||||
|
|
||||||
fw_v.register(databus.ps, "version", function (version) fw_v.set_value(util.c("FW: ", version)) end)
|
|
||||||
comms_v.register(databus.ps, "comms_version", function (version) comms_v.set_value(util.c("NT: v", version)) end)
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- unit status list
|
-- unit status list
|
||||||
--
|
--
|
||||||
@@ -129,30 +143,45 @@ local function init(panel, units)
|
|||||||
-- show routine statuses
|
-- show routine statuses
|
||||||
for i = 1, list_length do
|
for i = 1, list_length do
|
||||||
TextBox{parent=threads,x=1,y=i,text=util.sprintf("%02d",i)}
|
TextBox{parent=threads,x=1,y=i,text=util.sprintf("%02d",i)}
|
||||||
local rt_unit = LED{parent=threads,x=4,y=i,label="RT",colors=ind_grn}
|
local rt_unit = LED{parent=threads,x=4,y=i,label="RT",colors=util.trinary(units[i].type~=RTU_UNIT_TYPE.REDSTONE,ind_grn,cpair(style.ind_bkg,style.ind_bkg))}
|
||||||
rt_unit.register(databus.ps, "routine__unit_" .. i, rt_unit.update)
|
rt_unit.register(databus.ps, "routine__unit_" .. i, rt_unit.update)
|
||||||
end
|
end
|
||||||
|
|
||||||
local unit_hw_statuses = Div{parent=panel,height=term_h-3,x=25,y=3}
|
local unit_hw_statuses = Div{parent=panel,height=term_h-3,x=25,y=3}
|
||||||
|
|
||||||
|
local relay_counter = 0
|
||||||
|
|
||||||
-- show hardware statuses
|
-- show hardware statuses
|
||||||
for i = 1, list_length do
|
for i = 1, list_length do
|
||||||
local unit = units[i]
|
local unit = units[i]
|
||||||
|
|
||||||
|
local is_rs = unit.type == RTU_UNIT_TYPE.REDSTONE
|
||||||
|
|
||||||
-- hardware status
|
-- hardware status
|
||||||
local unit_hw = RGBLED{parent=unit_hw_statuses,y=i,label="",colors={colors.red,colors.orange,colors.yellow,colors.green}}
|
local unit_hw = RGBLED{parent=unit_hw_statuses,y=i,label="",colors={colors.red,colors.orange,colors.yellow,colors.green}}
|
||||||
|
|
||||||
unit_hw.register(databus.ps, "unit_hw_" .. i, unit_hw.update)
|
unit_hw.register(databus.ps, "unit_hw_" .. i, unit_hw.update)
|
||||||
|
|
||||||
-- unit name identifier (type + index)
|
-- unit name identifier (type + index)
|
||||||
local function get_name(t) return util.c(UNIT_TYPE_LABELS[t + 1], " ", util.trinary(util.is_int(unit.index), unit.index, "")) end
|
local function get_name()
|
||||||
local name_box = TextBox{parent=unit_hw_statuses,y=i,x=3,text=get_name(unit.type),width=15}
|
if is_rs then
|
||||||
|
local is_local = unit.name == "redstone_local"
|
||||||
|
relay_counter = relay_counter + util.trinary(is_local, 0, 1)
|
||||||
|
return util.c("REDSTONE", util.trinary(is_local, "", " RELAY " .. relay_counter))
|
||||||
|
else
|
||||||
|
return util.c(UNIT_TYPE_LABELS[unit.type + 1], " ", util.trinary(util.is_int(unit.index), unit.index, ""))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
name_box.register(databus.ps, "unit_type_" .. i, function (t) name_box.set_value(get_name(t)) end)
|
local name_box = TextBox{parent=unit_hw_statuses,y=i,x=3,text=get_name(),width=util.trinary(is_rs,24,15)}
|
||||||
|
|
||||||
|
name_box.register(databus.ps, "unit_type_" .. i, function () name_box.set_value(get_name()) end)
|
||||||
|
|
||||||
-- assignment (unit # or facility)
|
-- assignment (unit # or facility)
|
||||||
local for_unit = util.trinary(unit.reactor == 0, "\x1a FACIL ", "\x1a UNIT " .. unit.reactor)
|
if unit.reactor then
|
||||||
TextBox{parent=unit_hw_statuses,y=i,x=term_w-32,text=for_unit,fg_bg=disabled_fg}
|
local for_unit = util.trinary(unit.reactor == 0, "\x1a FACIL ", "\x1a UNIT " .. unit.reactor)
|
||||||
|
TextBox{parent=unit_hw_statuses,y=i,x=term_w-32,text=for_unit,fg_bg=disabled_fg}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -18,16 +18,15 @@ local ui = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
-- try to start the UI
|
-- try to start the UI
|
||||||
|
---@param config rtu_config configuration
|
||||||
---@param units rtu_registry_entry[] RTU entries
|
---@param units rtu_registry_entry[] RTU entries
|
||||||
---@param theme FP_THEME front panel theme
|
|
||||||
---@param color_mode COLOR_MODE color mode
|
|
||||||
---@return boolean success, any error_msg
|
---@return boolean success, any error_msg
|
||||||
function renderer.try_start_ui(units, theme, color_mode)
|
function renderer.try_start_ui(config, units)
|
||||||
local status, msg = true, nil
|
local status, msg = true, nil
|
||||||
|
|
||||||
if ui.display == nil then
|
if ui.display == nil then
|
||||||
-- set theme
|
-- set theme
|
||||||
style.set_theme(theme, color_mode)
|
style.set_theme(config.FrontPanelTheme, config.ColorMode)
|
||||||
|
|
||||||
-- reset terminal
|
-- reset terminal
|
||||||
term.setTextColor(colors.white)
|
term.setTextColor(colors.white)
|
||||||
@@ -41,7 +40,7 @@ function renderer.try_start_ui(units, theme, color_mode)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- apply color mode
|
-- apply color mode
|
||||||
local c_mode_overrides = style.theme.color_modes[color_mode]
|
local c_mode_overrides = style.theme.color_modes[config.ColorMode]
|
||||||
for i = 1, #c_mode_overrides do
|
for i = 1, #c_mode_overrides do
|
||||||
term.setPaletteColor(c_mode_overrides[i].c, c_mode_overrides[i].hex)
|
term.setPaletteColor(c_mode_overrides[i].c, c_mode_overrides[i].hex)
|
||||||
end
|
end
|
||||||
@@ -49,7 +48,7 @@ function renderer.try_start_ui(units, theme, color_mode)
|
|||||||
-- init front panel view
|
-- init front panel view
|
||||||
status, msg = pcall(function ()
|
status, msg = pcall(function ()
|
||||||
ui.display = DisplayBox{window=term.current(),fg_bg=style.fp.root}
|
ui.display = DisplayBox{window=term.current(),fg_bg=style.fp.root}
|
||||||
panel_view(ui.display, units)
|
panel_view(ui.display, config, units)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
if status then
|
if status then
|
||||||
|
|||||||
108
rtu/rtu.lua
108
rtu/rtu.lua
@@ -33,6 +33,9 @@ function rtu.load_config()
|
|||||||
|
|
||||||
config.SpeakerVolume = settings.get("SpeakerVolume")
|
config.SpeakerVolume = settings.get("SpeakerVolume")
|
||||||
|
|
||||||
|
config.WirelessModem = settings.get("WirelessModem")
|
||||||
|
config.WiredModem = settings.get("WiredModem")
|
||||||
|
config.PreferWireless = settings.get("PreferWireless")
|
||||||
config.SVR_Channel = settings.get("SVR_Channel")
|
config.SVR_Channel = settings.get("SVR_Channel")
|
||||||
config.RTU_Channel = settings.get("RTU_Channel")
|
config.RTU_Channel = settings.get("RTU_Channel")
|
||||||
config.ConnTimeout = settings.get("ConnTimeout")
|
config.ConnTimeout = settings.get("ConnTimeout")
|
||||||
@@ -46,36 +49,46 @@ function rtu.load_config()
|
|||||||
config.FrontPanelTheme = settings.get("FrontPanelTheme")
|
config.FrontPanelTheme = settings.get("FrontPanelTheme")
|
||||||
config.ColorMode = settings.get("ColorMode")
|
config.ColorMode = settings.get("ColorMode")
|
||||||
|
|
||||||
|
return rtu.validate_config(config)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- validate an RTU gateway configuration
|
||||||
|
---@param cfg rtu_config
|
||||||
|
function rtu.validate_config(cfg)
|
||||||
local cfv = util.new_validator()
|
local cfv = util.new_validator()
|
||||||
|
|
||||||
cfv.assert_type_num(config.SpeakerVolume)
|
cfv.assert_type_num(cfg.SpeakerVolume)
|
||||||
cfv.assert_range(config.SpeakerVolume, 0, 3)
|
cfv.assert_range(cfg.SpeakerVolume, 0, 3)
|
||||||
|
|
||||||
cfv.assert_channel(config.SVR_Channel)
|
cfv.assert_type_bool(cfg.WirelessModem)
|
||||||
cfv.assert_channel(config.RTU_Channel)
|
cfv.assert((cfg.WiredModem == false) or (type(cfg.WiredModem) == "string"))
|
||||||
cfv.assert_type_num(config.ConnTimeout)
|
cfv.assert(cfg.WirelessModem or (type(cfg.WiredModem) == "string"))
|
||||||
cfv.assert_min(config.ConnTimeout, 2)
|
cfv.assert_type_bool(cfg.PreferWireless)
|
||||||
cfv.assert_type_num(config.TrustedRange)
|
cfv.assert_channel(cfg.SVR_Channel)
|
||||||
cfv.assert_min(config.TrustedRange, 0)
|
cfv.assert_channel(cfg.RTU_Channel)
|
||||||
cfv.assert_type_str(config.AuthKey)
|
cfv.assert_type_num(cfg.ConnTimeout)
|
||||||
|
cfv.assert_min(cfg.ConnTimeout, 2)
|
||||||
|
cfv.assert_type_num(cfg.TrustedRange)
|
||||||
|
cfv.assert_min(cfg.TrustedRange, 0)
|
||||||
|
cfv.assert_type_str(cfg.AuthKey)
|
||||||
|
|
||||||
if type(config.AuthKey) == "string" then
|
if type(cfg.AuthKey) == "string" then
|
||||||
local len = string.len(config.AuthKey)
|
local len = string.len(cfg.AuthKey)
|
||||||
cfv.assert(len == 0 or len >= 8)
|
cfv.assert(len == 0 or len >= 8)
|
||||||
end
|
end
|
||||||
|
|
||||||
cfv.assert_type_int(config.LogMode)
|
cfv.assert_type_int(cfg.LogMode)
|
||||||
cfv.assert_range(config.LogMode, 0, 1)
|
cfv.assert_range(cfg.LogMode, 0, 1)
|
||||||
cfv.assert_type_str(config.LogPath)
|
cfv.assert_type_str(cfg.LogPath)
|
||||||
cfv.assert_type_bool(config.LogDebug)
|
cfv.assert_type_bool(cfg.LogDebug)
|
||||||
|
|
||||||
cfv.assert_type_int(config.FrontPanelTheme)
|
cfv.assert_type_int(cfg.FrontPanelTheme)
|
||||||
cfv.assert_range(config.FrontPanelTheme, 1, 2)
|
cfv.assert_range(cfg.FrontPanelTheme, 1, 2)
|
||||||
cfv.assert_type_int(config.ColorMode)
|
cfv.assert_type_int(cfg.ColorMode)
|
||||||
cfv.assert_range(config.ColorMode, 1, themes.COLOR_MODE.NUM_MODES)
|
cfv.assert_range(cfg.ColorMode, 1, themes.COLOR_MODE.NUM_MODES)
|
||||||
|
|
||||||
cfv.assert_type_table(config.Peripherals)
|
cfv.assert_type_table(cfg.Peripherals)
|
||||||
cfv.assert_type_table(config.Redstone)
|
cfv.assert_type_table(cfg.Redstone)
|
||||||
|
|
||||||
return cfv.valid()
|
return cfv.valid()
|
||||||
end
|
end
|
||||||
@@ -293,13 +306,11 @@ function rtu.comms(version, nic, conn_watchdog)
|
|||||||
|
|
||||||
local insert = table.insert
|
local insert = table.insert
|
||||||
|
|
||||||
comms.set_trusted_range(config.TrustedRange)
|
if config.WirelessModem then
|
||||||
|
comms.set_trusted_range(config.TrustedRange)
|
||||||
|
end
|
||||||
|
|
||||||
-- PRIVATE FUNCTIONS --
|
--#region PRIVATE FUNCTIONS --
|
||||||
|
|
||||||
-- configure modem channels
|
|
||||||
nic.closeAll()
|
|
||||||
nic.open(config.RTU_Channel)
|
|
||||||
|
|
||||||
-- send a scada management packet
|
-- send a scada management packet
|
||||||
---@param msg_type MGMT_TYPE
|
---@param msg_type MGMT_TYPE
|
||||||
@@ -332,31 +343,26 @@ function rtu.comms(version, nic, conn_watchdog)
|
|||||||
local unit = units[i]
|
local unit = units[i]
|
||||||
|
|
||||||
if unit.type ~= nil then
|
if unit.type ~= nil then
|
||||||
local advert = { unit.type, unit.index, unit.reactor }
|
insert(advertisement, { unit.type, unit.index, unit.reactor or -1, unit.rs_conns })
|
||||||
|
|
||||||
if unit.type == RTU_UNIT_TYPE.REDSTONE then
|
|
||||||
insert(advert, unit.device)
|
|
||||||
end
|
|
||||||
|
|
||||||
insert(advertisement, advert)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return advertisement
|
return advertisement
|
||||||
end
|
end
|
||||||
|
|
||||||
-- PUBLIC FUNCTIONS --
|
--#endregion
|
||||||
|
|
||||||
|
--#region PUBLIC FUNCTIONS --
|
||||||
|
|
||||||
---@class rtu_comms
|
---@class rtu_comms
|
||||||
local public = {}
|
local public = {}
|
||||||
|
|
||||||
-- send a MODBUS TCP packet
|
-- switch the current active NIC
|
||||||
---@param m_pkt modbus_packet
|
---@param act_nic nic
|
||||||
function public.send_modbus(m_pkt)
|
---@param rtu_state rtu_state
|
||||||
local s_pkt = comms.scada_packet()
|
function public.switch_nic(act_nic, rtu_state)
|
||||||
s_pkt.make(self.sv_addr, self.seq_num, PROTOCOL.MODBUS_TCP, m_pkt.raw_sendable())
|
public.close(rtu_state)
|
||||||
nic.transmit(config.SVR_Channel, config.RTU_Channel, s_pkt)
|
nic = act_nic
|
||||||
self.seq_num = self.seq_num + 1
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- unlink from the server
|
-- unlink from the server
|
||||||
@@ -376,6 +382,17 @@ function rtu.comms(version, nic, conn_watchdog)
|
|||||||
_send(MGMT_TYPE.CLOSE, {})
|
_send(MGMT_TYPE.CLOSE, {})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- send a MODBUS TCP packet
|
||||||
|
---@param m_pkt modbus_packet
|
||||||
|
function public.send_modbus(m_pkt)
|
||||||
|
local s_pkt = comms.scada_packet()
|
||||||
|
|
||||||
|
s_pkt.make(self.sv_addr, self.seq_num, PROTOCOL.MODBUS_TCP, m_pkt.raw_sendable())
|
||||||
|
|
||||||
|
nic.transmit(config.SVR_Channel, config.RTU_Channel, s_pkt)
|
||||||
|
self.seq_num = self.seq_num + 1
|
||||||
|
end
|
||||||
|
|
||||||
-- send establish request (includes advertisement)
|
-- send establish request (includes advertisement)
|
||||||
---@param units table
|
---@param units table
|
||||||
function public.send_establish(units)
|
function public.send_establish(units)
|
||||||
@@ -471,9 +488,10 @@ function rtu.comms(version, nic, conn_watchdog)
|
|||||||
local unit = units[packet.unit_id]
|
local unit = units[packet.unit_id]
|
||||||
local unit_dbg_tag = " (unit " .. packet.unit_id .. ")"
|
local unit_dbg_tag = " (unit " .. packet.unit_id .. ")"
|
||||||
|
|
||||||
if unit.name == "redstone_io" then
|
if unit.type == RTU_UNIT_TYPE.REDSTONE then
|
||||||
-- immediately execute redstone RTU requests
|
-- immediately execute redstone RTU requests
|
||||||
return_code, reply = unit.modbus_io.handle_packet(packet)
|
return_code, reply = unit.modbus_io.handle_packet(packet)
|
||||||
|
|
||||||
if not return_code then
|
if not return_code then
|
||||||
log.warning("requested MODBUS operation failed" .. unit_dbg_tag)
|
log.warning("requested MODBUS operation failed" .. unit_dbg_tag)
|
||||||
end
|
end
|
||||||
@@ -490,7 +508,7 @@ function rtu.comms(version, nic, conn_watchdog)
|
|||||||
unit.pkt_queue.push_packet(packet)
|
unit.pkt_queue.push_packet(packet)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
log.warning("cannot perform requested MODBUS operation" .. unit_dbg_tag)
|
log.warning("requested MODBUS operation failed" .. unit_dbg_tag)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
@@ -593,6 +611,8 @@ function rtu.comms(version, nic, conn_watchdog)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
return public
|
return public
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
454
rtu/startup.lua
454
rtu/startup.lua
@@ -1,40 +1,27 @@
|
|||||||
--
|
--
|
||||||
-- RTU: Remote Terminal Unit
|
-- RTU Gateway: Remote Terminal Unit Gateway
|
||||||
--
|
--
|
||||||
|
|
||||||
require("/initenv").init_env()
|
require("/initenv").init_env()
|
||||||
|
|
||||||
local audio = require("scada-common.audio")
|
local audio = require("scada-common.audio")
|
||||||
local comms = require("scada-common.comms")
|
local comms = require("scada-common.comms")
|
||||||
local crash = require("scada-common.crash")
|
local crash = require("scada-common.crash")
|
||||||
local log = require("scada-common.log")
|
local log = require("scada-common.log")
|
||||||
local mqueue = require("scada-common.mqueue")
|
local mqueue = require("scada-common.mqueue")
|
||||||
local network = require("scada-common.network")
|
local network = require("scada-common.network")
|
||||||
local ppm = require("scada-common.ppm")
|
local ppm = require("scada-common.ppm")
|
||||||
local rsio = require("scada-common.rsio")
|
local util = require("scada-common.util")
|
||||||
local types = require("scada-common.types")
|
|
||||||
local util = require("scada-common.util")
|
|
||||||
|
|
||||||
local configure = require("rtu.configure")
|
local backplane = require("rtu.backplane")
|
||||||
local databus = require("rtu.databus")
|
local configure = require("rtu.configure")
|
||||||
local modbus = require("rtu.modbus")
|
local databus = require("rtu.databus")
|
||||||
local renderer = require("rtu.renderer")
|
local renderer = require("rtu.renderer")
|
||||||
local rtu = require("rtu.rtu")
|
local rtu = require("rtu.rtu")
|
||||||
local threads = require("rtu.threads")
|
local threads = require("rtu.threads")
|
||||||
|
local uinit = require("rtu.uinit")
|
||||||
|
|
||||||
local boilerv_rtu = require("rtu.dev.boilerv_rtu")
|
local RTU_VERSION = "v1.13.0"
|
||||||
local dynamicv_rtu = require("rtu.dev.dynamicv_rtu")
|
|
||||||
local envd_rtu = require("rtu.dev.envd_rtu")
|
|
||||||
local imatrix_rtu = require("rtu.dev.imatrix_rtu")
|
|
||||||
local redstone_rtu = require("rtu.dev.redstone_rtu")
|
|
||||||
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.11.6"
|
|
||||||
|
|
||||||
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
|
|
||||||
local RTU_HW_STATE = databus.RTU_HW_STATE
|
|
||||||
|
|
||||||
local println = util.println
|
local println = util.println
|
||||||
local println_ts = util.println_ts
|
local println_ts = util.println_ts
|
||||||
@@ -82,7 +69,7 @@ local function main()
|
|||||||
-- startup
|
-- startup
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
|
|
||||||
-- record firmware versions and ID
|
-- report versions
|
||||||
databus.tx_versions(RTU_VERSION, comms.version)
|
databus.tx_versions(RTU_VERSION, comms.version)
|
||||||
|
|
||||||
-- mount connected devices
|
-- mount connected devices
|
||||||
@@ -106,15 +93,9 @@ local function main()
|
|||||||
shutdown = false
|
shutdown = false
|
||||||
},
|
},
|
||||||
|
|
||||||
-- RTU gateway devices (not RTU units)
|
|
||||||
rtu_dev = {
|
|
||||||
modem = ppm.get_wireless_modem(),
|
|
||||||
sounders = {} ---@type rtu_speaker_sounder[]
|
|
||||||
},
|
|
||||||
|
|
||||||
-- system objects
|
-- system objects
|
||||||
|
---@class rtu_sys
|
||||||
rtu_sys = {
|
rtu_sys = {
|
||||||
nic = nil, ---@type nic
|
|
||||||
rtu_comms = nil, ---@type rtu_comms
|
rtu_comms = nil, ---@type rtu_comms
|
||||||
conn_watchdog = nil, ---@type watchdog
|
conn_watchdog = nil, ---@type watchdog
|
||||||
units = {} ---@type rtu_registry_entry[]
|
units = {} ---@type rtu_registry_entry[]
|
||||||
@@ -126,378 +107,23 @@ local function main()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
local smem_sys = __shared_memory.rtu_sys
|
local smem_sys = __shared_memory.rtu_sys
|
||||||
local smem_dev = __shared_memory.rtu_dev
|
|
||||||
|
|
||||||
local rtu_state = __shared_memory.rtu_state
|
local rtu_state = __shared_memory.rtu_state
|
||||||
|
local units = __shared_memory.rtu_sys.units
|
||||||
|
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
-- interpret config and init units
|
-- init and start system
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
|
|
||||||
local units = __shared_memory.rtu_sys.units
|
-- modem and speaker initialization
|
||||||
|
if not backplane.init(config, __shared_memory) then return end
|
||||||
|
|
||||||
local rtu_redstone = config.Redstone
|
log.debug("startup> running uinit()")
|
||||||
local rtu_devices = config.Peripherals
|
|
||||||
|
|
||||||
-- configure RTU gateway based on settings file definitions
|
if uinit(config, __shared_memory) then
|
||||||
local function sys_config()
|
|
||||||
-- redstone interfaces
|
|
||||||
local rs_rtus = {} ---@type { rtu: rtu_rs_device, capabilities: IO_PORT[] }[]
|
|
||||||
|
|
||||||
-- go through redstone definitions list
|
|
||||||
for entry_idx = 1, #rtu_redstone do
|
|
||||||
local entry = rtu_redstone[entry_idx]
|
|
||||||
local assignment
|
|
||||||
local for_reactor = entry.unit
|
|
||||||
local iface_name = util.trinary(entry.color ~= nil, util.c(entry.side, "/", rsio.color_name(entry.color)), entry.side)
|
|
||||||
|
|
||||||
if util.is_int(entry.unit) and entry.unit > 0 and entry.unit < 5 then
|
|
||||||
---@cast for_reactor integer
|
|
||||||
assignment = "reactor unit " .. entry.unit
|
|
||||||
if rs_rtus[for_reactor] == nil then
|
|
||||||
log.debug(util.c("sys_config> allocated redstone RTU for reactor unit ", entry.unit))
|
|
||||||
rs_rtus[for_reactor] = { rtu = redstone_rtu.new(), capabilities = {} }
|
|
||||||
end
|
|
||||||
elseif entry.unit == nil then
|
|
||||||
assignment = "facility"
|
|
||||||
for_reactor = 0
|
|
||||||
if rs_rtus[for_reactor] == nil then
|
|
||||||
log.debug(util.c("sys_config> allocated redstone RTU for the facility"))
|
|
||||||
rs_rtus[for_reactor] = { rtu = redstone_rtu.new(), capabilities = {} }
|
|
||||||
end
|
|
||||||
else
|
|
||||||
local message = util.c("sys_config> invalid unit assignment at block index #", entry_idx)
|
|
||||||
println(message)
|
|
||||||
log.fatal(message)
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
-- verify configuration
|
|
||||||
local valid = false
|
|
||||||
if rsio.is_valid_port(entry.port) and rsio.is_valid_side(entry.side) then
|
|
||||||
valid = util.trinary(entry.color == nil, true, rsio.is_color(entry.color))
|
|
||||||
end
|
|
||||||
|
|
||||||
local rs_rtu = rs_rtus[for_reactor].rtu
|
|
||||||
local capabilities = rs_rtus[for_reactor].capabilities
|
|
||||||
|
|
||||||
if not valid then
|
|
||||||
local message = util.c("sys_config> invalid redstone definition at block index #", entry_idx)
|
|
||||||
println(message)
|
|
||||||
log.fatal(message)
|
|
||||||
return false
|
|
||||||
else
|
|
||||||
-- link redstone in RTU
|
|
||||||
local mode = rsio.get_io_mode(entry.port)
|
|
||||||
if mode == rsio.IO_MODE.DIGITAL_IN then
|
|
||||||
-- can't have duplicate inputs
|
|
||||||
if util.table_contains(capabilities, entry.port) then
|
|
||||||
local message = util.c("sys_config> skipping duplicate input for port ", rsio.to_string(entry.port), " on side ", iface_name)
|
|
||||||
println(message)
|
|
||||||
log.warning(message)
|
|
||||||
else
|
|
||||||
rs_rtu.link_di(entry.side, entry.color)
|
|
||||||
end
|
|
||||||
elseif mode == rsio.IO_MODE.DIGITAL_OUT then
|
|
||||||
rs_rtu.link_do(entry.side, entry.color)
|
|
||||||
elseif mode == rsio.IO_MODE.ANALOG_IN then
|
|
||||||
-- can't have duplicate inputs
|
|
||||||
if util.table_contains(capabilities, entry.port) then
|
|
||||||
local message = util.c("sys_config> skipping duplicate input for port ", rsio.to_string(entry.port), " on side ", iface_name)
|
|
||||||
println(message)
|
|
||||||
log.warning(message)
|
|
||||||
else
|
|
||||||
rs_rtu.link_ai(entry.side)
|
|
||||||
end
|
|
||||||
elseif mode == rsio.IO_MODE.ANALOG_OUT then
|
|
||||||
rs_rtu.link_ao(entry.side)
|
|
||||||
else
|
|
||||||
-- should be unreachable code, we already validated ports
|
|
||||||
log.error("sys_config> fell through if chain attempting to identify IO mode at block index #" .. entry_idx, true)
|
|
||||||
println("sys_config> encountered a software error, check logs")
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
table.insert(capabilities, entry.port)
|
|
||||||
|
|
||||||
log.debug(util.c("sys_config> linked redstone ", #capabilities, ": ", rsio.to_string(entry.port), " (", iface_name, ") for ", assignment))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- create unit entries for redstone RTUs
|
|
||||||
for for_reactor, def in pairs(rs_rtus) do
|
|
||||||
---@class rtu_registry_entry
|
|
||||||
local unit = {
|
|
||||||
uid = 0, ---@type integer
|
|
||||||
name = "redstone_io", ---@type string
|
|
||||||
type = RTU_UNIT_TYPE.REDSTONE, ---@type RTU_UNIT_TYPE
|
|
||||||
index = false, ---@type integer|false
|
|
||||||
reactor = for_reactor, ---@type integer
|
|
||||||
device = def.capabilities, ---@type IO_PORT[] use device field for redstone ports
|
|
||||||
is_multiblock = false, ---@type boolean
|
|
||||||
formed = nil, ---@type boolean|nil
|
|
||||||
hw_state = RTU_HW_STATE.OK, ---@type RTU_HW_STATE
|
|
||||||
rtu = def.rtu, ---@type rtu_device|rtu_rs_device
|
|
||||||
modbus_io = modbus.new(def.rtu, false),
|
|
||||||
pkt_queue = nil, ---@type mqueue|nil
|
|
||||||
thread = nil ---@type parallel_thread|nil
|
|
||||||
}
|
|
||||||
|
|
||||||
table.insert(units, unit)
|
|
||||||
|
|
||||||
local for_message = "facility"
|
|
||||||
if util.is_int(for_reactor) then
|
|
||||||
for_message = util.c("reactor unit ", for_reactor)
|
|
||||||
end
|
|
||||||
|
|
||||||
log.info(util.c("sys_config> initialized RTU unit #", #units, ": redstone_io (redstone) [1] for ", for_message))
|
|
||||||
|
|
||||||
unit.uid = #units
|
|
||||||
|
|
||||||
databus.tx_unit_hw_status(unit.uid, unit.hw_state)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- mounted peripherals
|
|
||||||
for i = 1, #rtu_devices do
|
|
||||||
local entry = rtu_devices[i] ---@type rtu_peri_definition
|
|
||||||
local name = entry.name
|
|
||||||
local index = entry.index
|
|
||||||
local for_reactor = util.trinary(entry.unit == nil, 0, entry.unit)
|
|
||||||
|
|
||||||
-- CHECK: name is a string
|
|
||||||
if type(name) ~= "string" then
|
|
||||||
local message = util.c("sys_config> device entry #", i, ": device ", name, " isn't a string")
|
|
||||||
println(message)
|
|
||||||
log.fatal(message)
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
-- CHECK: index type
|
|
||||||
if (index ~= nil) and (not util.is_int(index)) then
|
|
||||||
local message = util.c("sys_config> device entry #", i, ": index ", index, " isn't valid")
|
|
||||||
println(message)
|
|
||||||
log.fatal(message)
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
-- CHECK: index range
|
|
||||||
local function validate_index(min, max)
|
|
||||||
if (not util.is_int(index)) or ((index < min) and (max ~= nil and index > max)) then
|
|
||||||
local message = util.c("sys_config> device entry #", i, ": index ", index, " isn't >= ", min)
|
|
||||||
if max ~= nil then message = util.c(message, " and <= ", max) end
|
|
||||||
println(message)
|
|
||||||
log.fatal(message)
|
|
||||||
return false
|
|
||||||
else return true end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- CHECK: reactor is an integer >= 0
|
|
||||||
local function validate_assign(for_facility)
|
|
||||||
if for_facility and for_reactor ~= 0 then
|
|
||||||
local message = util.c("sys_config> device entry #", i, ": must only be for the facility")
|
|
||||||
println(message)
|
|
||||||
log.fatal(message)
|
|
||||||
return false
|
|
||||||
elseif (not for_facility) and ((not util.is_int(for_reactor)) or (for_reactor < 1) or (for_reactor > 4)) then
|
|
||||||
local message = util.c("sys_config> device entry #", i, ": unit assignment ", for_reactor, " isn't vaild")
|
|
||||||
println(message)
|
|
||||||
log.fatal(message)
|
|
||||||
return false
|
|
||||||
else return true end
|
|
||||||
end
|
|
||||||
|
|
||||||
local device = ppm.get_periph(name)
|
|
||||||
|
|
||||||
local type ---@type string|nil
|
|
||||||
local rtu_iface ---@type rtu_device
|
|
||||||
local rtu_type ---@type RTU_UNIT_TYPE
|
|
||||||
local is_multiblock = false ---@type boolean
|
|
||||||
local formed = nil ---@type boolean|nil
|
|
||||||
local faulted = nil ---@type boolean|nil
|
|
||||||
|
|
||||||
if device == nil then
|
|
||||||
local message = util.c("sys_config> '", name, "' not found, using placeholder")
|
|
||||||
println(message)
|
|
||||||
log.warning(message)
|
|
||||||
|
|
||||||
-- mount a virtual (placeholder) device
|
|
||||||
type, device = ppm.mount_virtual()
|
|
||||||
else
|
|
||||||
type = ppm.get_type(name)
|
|
||||||
end
|
|
||||||
|
|
||||||
if type == "boilerValve" then
|
|
||||||
-- boiler multiblock
|
|
||||||
if not validate_index(1, 2) then return false end
|
|
||||||
if not validate_assign() then return false end
|
|
||||||
|
|
||||||
rtu_type = RTU_UNIT_TYPE.BOILER_VALVE
|
|
||||||
rtu_iface, faulted = boilerv_rtu.new(device)
|
|
||||||
is_multiblock = true
|
|
||||||
formed = device.isFormed()
|
|
||||||
|
|
||||||
if formed == ppm.ACCESS_FAULT then
|
|
||||||
println_ts(util.c("sys_config> failed to check if '", name, "' is formed"))
|
|
||||||
log.warning(util.c("sys_config> failed to check if '", name, "' is a formed boiler multiblock"))
|
|
||||||
end
|
|
||||||
elseif type == "turbineValve" then
|
|
||||||
-- turbine multiblock
|
|
||||||
if not validate_index(1, 3) then return false end
|
|
||||||
if not validate_assign() then return false end
|
|
||||||
|
|
||||||
rtu_type = RTU_UNIT_TYPE.TURBINE_VALVE
|
|
||||||
rtu_iface, faulted = turbinev_rtu.new(device)
|
|
||||||
is_multiblock = true
|
|
||||||
formed = device.isFormed()
|
|
||||||
|
|
||||||
if formed == ppm.ACCESS_FAULT then
|
|
||||||
println_ts(util.c("sys_config> failed to check if '", name, "' is formed"))
|
|
||||||
log.warning(util.c("sys_config> failed to check if '", name, "' is a formed turbine multiblock"))
|
|
||||||
end
|
|
||||||
elseif type == "dynamicValve" then
|
|
||||||
-- dynamic tank multiblock
|
|
||||||
if entry.unit == nil then
|
|
||||||
if not validate_index(1, 4) then return false end
|
|
||||||
if not validate_assign(true) then return false end
|
|
||||||
else
|
|
||||||
if not validate_index(1, 1) then return false end
|
|
||||||
if not validate_assign() then return false end
|
|
||||||
end
|
|
||||||
|
|
||||||
rtu_type = RTU_UNIT_TYPE.DYNAMIC_VALVE
|
|
||||||
rtu_iface, faulted = dynamicv_rtu.new(device)
|
|
||||||
is_multiblock = true
|
|
||||||
formed = device.isFormed()
|
|
||||||
|
|
||||||
if formed == ppm.ACCESS_FAULT then
|
|
||||||
println_ts(util.c("sys_config> failed to check if '", name, "' is formed"))
|
|
||||||
log.warning(util.c("sys_config> failed to check if '", name, "' is a formed dynamic tank multiblock"))
|
|
||||||
end
|
|
||||||
elseif type == "inductionPort" then
|
|
||||||
-- induction matrix multiblock
|
|
||||||
if not validate_assign(true) then return false end
|
|
||||||
|
|
||||||
rtu_type = RTU_UNIT_TYPE.IMATRIX
|
|
||||||
rtu_iface, faulted = imatrix_rtu.new(device)
|
|
||||||
is_multiblock = true
|
|
||||||
formed = device.isFormed()
|
|
||||||
|
|
||||||
if formed == ppm.ACCESS_FAULT then
|
|
||||||
println_ts(util.c("sys_config> failed to check if '", name, "' is formed"))
|
|
||||||
log.warning(util.c("sys_config> failed to check if '", name, "' is a formed induction matrix multiblock"))
|
|
||||||
end
|
|
||||||
elseif type == "spsPort" then
|
|
||||||
-- SPS multiblock
|
|
||||||
if not validate_assign(true) then return false end
|
|
||||||
|
|
||||||
rtu_type = RTU_UNIT_TYPE.SPS
|
|
||||||
rtu_iface, faulted = sps_rtu.new(device)
|
|
||||||
is_multiblock = true
|
|
||||||
formed = device.isFormed()
|
|
||||||
|
|
||||||
if formed == ppm.ACCESS_FAULT then
|
|
||||||
println_ts(util.c("sys_config> failed to check if '", name, "' is formed"))
|
|
||||||
log.warning(util.c("sys_config> failed to check if '", name, "' is a formed SPS multiblock"))
|
|
||||||
end
|
|
||||||
elseif type == "solarNeutronActivator" then
|
|
||||||
-- SNA
|
|
||||||
if not validate_assign() then return false end
|
|
||||||
|
|
||||||
rtu_type = RTU_UNIT_TYPE.SNA
|
|
||||||
rtu_iface, faulted = sna_rtu.new(device)
|
|
||||||
elseif type == "environmentDetector" then
|
|
||||||
-- advanced peripherals environment detector
|
|
||||||
if not validate_index(1) then return false end
|
|
||||||
if not validate_assign(entry.unit == nil) then return false end
|
|
||||||
|
|
||||||
rtu_type = RTU_UNIT_TYPE.ENV_DETECTOR
|
|
||||||
rtu_iface, faulted = envd_rtu.new(device)
|
|
||||||
elseif type == ppm.VIRTUAL_DEVICE_TYPE then
|
|
||||||
-- placeholder device
|
|
||||||
rtu_type = RTU_UNIT_TYPE.VIRTUAL
|
|
||||||
rtu_iface = rtu.init_unit().interface()
|
|
||||||
else
|
|
||||||
local message = util.c("sys_config> device '", name, "' is not a known type (", type, ")")
|
|
||||||
println_ts(message)
|
|
||||||
log.fatal(message)
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
if is_multiblock then
|
|
||||||
if not formed then
|
|
||||||
if formed == false then
|
|
||||||
log.info(util.c("sys_config> device '", name, "' is not formed"))
|
|
||||||
else formed = false end
|
|
||||||
elseif faulted then
|
|
||||||
-- sometimes there is a race condition on server boot where it reports formed, but
|
|
||||||
-- the other functions are not yet defined (that's the theory at least). mark as unformed to attempt connection later
|
|
||||||
formed = false
|
|
||||||
log.warning(util.c("sys_config> device '", name, "' is formed, but initialization had one or more faults: marked as unformed"))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
---@class rtu_registry_entry
|
|
||||||
local rtu_unit = {
|
|
||||||
uid = 0, ---@type integer
|
|
||||||
name = name, ---@type string
|
|
||||||
type = rtu_type, ---@type RTU_UNIT_TYPE
|
|
||||||
index = index or false, ---@type integer|false
|
|
||||||
reactor = for_reactor, ---@type integer
|
|
||||||
device = device, ---@type table peripheral reference
|
|
||||||
is_multiblock = is_multiblock, ---@type boolean
|
|
||||||
formed = formed, ---@type boolean|nil
|
|
||||||
hw_state = RTU_HW_STATE.OFFLINE, ---@type RTU_HW_STATE
|
|
||||||
rtu = rtu_iface, ---@type rtu_device|rtu_rs_device
|
|
||||||
modbus_io = modbus.new(rtu_iface, true),
|
|
||||||
pkt_queue = mqueue.new(), ---@type mqueue|nil
|
|
||||||
thread = nil ---@type parallel_thread|nil
|
|
||||||
}
|
|
||||||
|
|
||||||
rtu_unit.thread = threads.thread__unit_comms(__shared_memory, rtu_unit)
|
|
||||||
|
|
||||||
table.insert(units, rtu_unit)
|
|
||||||
|
|
||||||
local for_message = "the facility"
|
|
||||||
if for_reactor > 0 then
|
|
||||||
for_message = util.c("reactor ", for_reactor)
|
|
||||||
end
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
-- determine hardware status
|
|
||||||
if rtu_unit.type == RTU_UNIT_TYPE.VIRTUAL then
|
|
||||||
rtu_unit.hw_state = RTU_HW_STATE.OFFLINE
|
|
||||||
else
|
|
||||||
if rtu_unit.is_multiblock then
|
|
||||||
rtu_unit.hw_state = util.trinary(rtu_unit.formed == true, RTU_HW_STATE.OK, RTU_HW_STATE.UNFORMED)
|
|
||||||
elseif faulted then
|
|
||||||
rtu_unit.hw_state = RTU_HW_STATE.FAULTED
|
|
||||||
else
|
|
||||||
rtu_unit.hw_state = RTU_HW_STATE.OK
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- report hardware status
|
|
||||||
databus.tx_unit_hw_status(rtu_unit.uid, rtu_unit.hw_state)
|
|
||||||
end
|
|
||||||
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
----------------------------------------
|
|
||||||
-- start system
|
|
||||||
----------------------------------------
|
|
||||||
|
|
||||||
log.debug("boot> running sys_config()")
|
|
||||||
|
|
||||||
if sys_config() then
|
|
||||||
-- start UI
|
-- start UI
|
||||||
local message
|
local message
|
||||||
rtu_state.fp_ok, message = renderer.try_start_ui(units, config.FrontPanelTheme, config.ColorMode)
|
rtu_state.fp_ok, message = renderer.try_start_ui(config, units)
|
||||||
|
|
||||||
if not rtu_state.fp_ok then
|
if not rtu_state.fp_ok then
|
||||||
println_ts(util.c("UI error: ", message))
|
println_ts(util.c("UI error: ", message))
|
||||||
@@ -506,34 +132,12 @@ local function main()
|
|||||||
log.info("startup> running in headless mode without front panel")
|
log.info("startup> running in headless mode without front panel")
|
||||||
end
|
end
|
||||||
|
|
||||||
-- check modem
|
|
||||||
if smem_dev.modem == nil then
|
|
||||||
println("startup> wireless modem not found")
|
|
||||||
log.fatal("no wireless modem on startup")
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
databus.tx_hw_modem(true)
|
|
||||||
|
|
||||||
-- find and setup all speakers
|
|
||||||
local speakers = ppm.get_all_devices("speaker")
|
|
||||||
for _, s in pairs(speakers) do
|
|
||||||
local sounder = rtu.init_sounder(s)
|
|
||||||
|
|
||||||
table.insert(smem_dev.sounders, sounder)
|
|
||||||
|
|
||||||
log.debug(util.c("startup> added speaker, attached as ", sounder.name))
|
|
||||||
end
|
|
||||||
|
|
||||||
databus.tx_hw_spkr_count(#smem_dev.sounders)
|
|
||||||
|
|
||||||
-- start connection watchdog
|
-- start connection watchdog
|
||||||
smem_sys.conn_watchdog = util.new_watchdog(config.ConnTimeout)
|
smem_sys.conn_watchdog = util.new_watchdog(config.ConnTimeout)
|
||||||
log.debug("startup> conn watchdog started")
|
log.debug("startup> conn watchdog started")
|
||||||
|
|
||||||
-- setup comms
|
-- setup comms
|
||||||
smem_sys.nic = network.nic(smem_dev.modem)
|
smem_sys.rtu_comms = rtu.comms(RTU_VERSION, backplane.active_nic(), smem_sys.conn_watchdog)
|
||||||
smem_sys.rtu_comms = rtu.comms(RTU_VERSION, smem_sys.nic, smem_sys.conn_watchdog)
|
|
||||||
log.debug("startup> comms init")
|
log.debug("startup> comms init")
|
||||||
|
|
||||||
-- init threads
|
-- init threads
|
||||||
@@ -553,7 +157,7 @@ local function main()
|
|||||||
-- run threads
|
-- run threads
|
||||||
parallel.waitForAll(table.unpack(_threads))
|
parallel.waitForAll(table.unpack(_threads))
|
||||||
else
|
else
|
||||||
println("configuration failed, exiting...")
|
println("system initialization failed, exiting...")
|
||||||
end
|
end
|
||||||
|
|
||||||
renderer.close_ui()
|
renderer.close_ui()
|
||||||
|
|||||||
101
rtu/threads.lua
101
rtu/threads.lua
@@ -5,10 +5,10 @@ local tcd = require("scada-common.tcd")
|
|||||||
local types = require("scada-common.types")
|
local types = require("scada-common.types")
|
||||||
local util = require("scada-common.util")
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
|
local backplane = require("rtu.backplane")
|
||||||
local databus = require("rtu.databus")
|
local databus = require("rtu.databus")
|
||||||
local modbus = require("rtu.modbus")
|
local modbus = require("rtu.modbus")
|
||||||
local renderer = require("rtu.renderer")
|
local renderer = require("rtu.renderer")
|
||||||
local rtu = require("rtu.rtu")
|
|
||||||
|
|
||||||
local boilerv_rtu = require("rtu.dev.boilerv_rtu")
|
local boilerv_rtu = require("rtu.dev.boilerv_rtu")
|
||||||
local dynamicv_rtu = require("rtu.dev.dynamicv_rtu")
|
local dynamicv_rtu = require("rtu.dev.dynamicv_rtu")
|
||||||
@@ -25,8 +25,8 @@ local threads = {}
|
|||||||
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
|
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
|
||||||
local RTU_HW_STATE = databus.RTU_HW_STATE
|
local RTU_HW_STATE = databus.RTU_HW_STATE
|
||||||
|
|
||||||
local MAIN_CLOCK = 0.5 -- (2Hz, 10 ticks)
|
local MAIN_CLOCK = 0.5 -- 2Hz, 10 ticks
|
||||||
local COMMS_SLEEP = 100 -- (100ms, 2 ticks)
|
local COMMS_SLEEP = 100 -- 100ms, 2 ticks
|
||||||
|
|
||||||
---@param smem rtu_shared_memory
|
---@param smem rtu_shared_memory
|
||||||
---@param println_ts function
|
---@param println_ts function
|
||||||
@@ -74,7 +74,7 @@ local function handle_unit_mount(smem, println_ts, iface, type, device, unit)
|
|||||||
end
|
end
|
||||||
|
|
||||||
unit.type = RTU_UNIT_TYPE.DYNAMIC_VALVE
|
unit.type = RTU_UNIT_TYPE.DYNAMIC_VALVE
|
||||||
elseif type == "inductionPort" then
|
elseif type == "inductionPort" or type == "reinforcedInductionPort" then
|
||||||
-- induction matrix multiblock
|
-- induction matrix multiblock
|
||||||
if unit.reactor ~= 0 then fail(util.c("induction matrix '", unit.name, "' cannot init, not assigned to facility")) end
|
if unit.reactor ~= 0 then fail(util.c("induction matrix '", unit.name, "' cannot init, not assigned to facility")) end
|
||||||
|
|
||||||
@@ -89,7 +89,7 @@ local function handle_unit_mount(smem, println_ts, iface, type, device, unit)
|
|||||||
if unit.reactor < 1 or unit.reactor > 4 then fail(util.c("SNA '", unit.name, "' cannot init, not assigned to a valid unit")) end
|
if unit.reactor < 1 or unit.reactor > 4 then fail(util.c("SNA '", unit.name, "' cannot init, not assigned to a valid unit")) end
|
||||||
|
|
||||||
unit.type = RTU_UNIT_TYPE.SNA
|
unit.type = RTU_UNIT_TYPE.SNA
|
||||||
elseif type == "environmentDetector" then
|
elseif type == "environmentDetector" or type == "environment_detector" then
|
||||||
-- advanced peripherals environment detector
|
-- advanced peripherals environment detector
|
||||||
if unit.reactor < 0 or unit.reactor > 4 then fail(util.c("environment detector '", unit.name, "' cannot init, no valid assignment provided")) end
|
if unit.reactor < 0 or unit.reactor > 4 then fail(util.c("environment detector '", unit.name, "' cannot init, no valid assignment provided")) end
|
||||||
if (unit.index == false) or unit.index < 1 then fail(util.c("environment detector '", unit.name, "' cannot init, invalid index provided")) end
|
if (unit.index == false) or unit.index < 1 then fail(util.c("environment detector '", unit.name, "' cannot init, invalid index provided")) end
|
||||||
@@ -132,6 +132,8 @@ local function handle_unit_mount(smem, println_ts, iface, type, device, unit)
|
|||||||
unit.rtu, faulted = sna_rtu.new(device)
|
unit.rtu, faulted = sna_rtu.new(device)
|
||||||
elseif unit.type == RTU_UNIT_TYPE.ENV_DETECTOR then
|
elseif unit.type == RTU_UNIT_TYPE.ENV_DETECTOR then
|
||||||
unit.rtu, faulted = envd_rtu.new(device)
|
unit.rtu, faulted = envd_rtu.new(device)
|
||||||
|
elseif unit.type == RTU_UNIT_TYPE.REDSTONE then
|
||||||
|
unit.rtu.remount_phy(device)
|
||||||
else
|
else
|
||||||
unknown = true
|
unknown = true
|
||||||
log.error(util.c("failed to identify reconnected RTU unit type (", unit.name, ")"), true)
|
log.error(util.c("failed to identify reconnected RTU unit type (", unit.name, ")"), true)
|
||||||
@@ -182,19 +184,19 @@ function threads.thread__main(smem)
|
|||||||
-- execute thread
|
-- execute thread
|
||||||
function public.exec()
|
function public.exec()
|
||||||
databus.tx_rt_status("main", true)
|
databus.tx_rt_status("main", true)
|
||||||
log.debug("main thread start")
|
log.debug("OS: main thread start")
|
||||||
|
|
||||||
-- main loop clock
|
-- main loop clock
|
||||||
local loop_clock = util.new_clock(MAIN_CLOCK)
|
local loop_clock = util.new_clock(MAIN_CLOCK)
|
||||||
|
|
||||||
-- load in from shared memory
|
-- load in from shared memory
|
||||||
local rtu_state = smem.rtu_state
|
local rtu_state = smem.rtu_state
|
||||||
local sounders = smem.rtu_dev.sounders
|
|
||||||
local nic = smem.rtu_sys.nic
|
|
||||||
local rtu_comms = smem.rtu_sys.rtu_comms
|
local rtu_comms = smem.rtu_sys.rtu_comms
|
||||||
local conn_watchdog = smem.rtu_sys.conn_watchdog
|
local conn_watchdog = smem.rtu_sys.conn_watchdog
|
||||||
local units = smem.rtu_sys.units
|
local units = smem.rtu_sys.units
|
||||||
|
|
||||||
|
local sounders = backplane.sounders()
|
||||||
|
|
||||||
-- start unlinked (in case of restart)
|
-- start unlinked (in case of restart)
|
||||||
rtu_comms.unlink(rtu_state)
|
rtu_comms.unlink(rtu_state)
|
||||||
|
|
||||||
@@ -244,38 +246,8 @@ function threads.thread__main(smem)
|
|||||||
local type, device = ppm.handle_unmount(param1)
|
local type, device = ppm.handle_unmount(param1)
|
||||||
|
|
||||||
if type ~= nil and device ~= nil then
|
if type ~= nil and device ~= nil then
|
||||||
if type == "modem" then
|
if type == "modem" or type == "speaker" then
|
||||||
---@cast device Modem
|
backplane.detach(type, device, param1, println_ts)
|
||||||
-- we only care if this is our wireless modem
|
|
||||||
if nic.is_modem(device) then
|
|
||||||
nic.disconnect()
|
|
||||||
|
|
||||||
println_ts("wireless modem disconnected!")
|
|
||||||
log.warning("comms modem disconnected")
|
|
||||||
|
|
||||||
local other_modem = ppm.get_wireless_modem()
|
|
||||||
if other_modem then
|
|
||||||
log.info("found another wireless modem, using it for comms")
|
|
||||||
nic.connect(other_modem)
|
|
||||||
else
|
|
||||||
databus.tx_hw_modem(false)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
log.warning("non-comms modem disconnected")
|
|
||||||
end
|
|
||||||
elseif type == "speaker" then
|
|
||||||
---@cast device Speaker
|
|
||||||
for i = 1, #sounders do
|
|
||||||
if sounders[i].speaker == device then
|
|
||||||
table.remove(sounders, i)
|
|
||||||
|
|
||||||
log.warning(util.c("speaker ", param1, " disconnected"))
|
|
||||||
println_ts("speaker disconnected")
|
|
||||||
|
|
||||||
databus.tx_hw_spkr_count(#sounders)
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
for i = 1, #units do
|
for i = 1, #units do
|
||||||
-- find disconnected device
|
-- find disconnected device
|
||||||
@@ -299,29 +271,8 @@ function threads.thread__main(smem)
|
|||||||
local type, device = ppm.mount(param1)
|
local type, device = ppm.mount(param1)
|
||||||
|
|
||||||
if type ~= nil and device ~= nil then
|
if type ~= nil and device ~= nil then
|
||||||
if type == "modem" then
|
if type == "modem" or type == "speaker" then
|
||||||
---@cast device Modem
|
backplane.attach(type, device, param1, println_ts)
|
||||||
if device.isWireless() and not nic.is_connected() then
|
|
||||||
-- reconnected modem
|
|
||||||
nic.connect(device)
|
|
||||||
|
|
||||||
println_ts("wireless modem reconnected.")
|
|
||||||
log.info("comms modem reconnected")
|
|
||||||
|
|
||||||
databus.tx_hw_modem(true)
|
|
||||||
elseif device.isWireless() then
|
|
||||||
log.info("unused wireless modem reconnected")
|
|
||||||
else
|
|
||||||
log.info("wired modem reconnected")
|
|
||||||
end
|
|
||||||
elseif type == "speaker" then
|
|
||||||
---@cast device Speaker
|
|
||||||
table.insert(sounders, rtu.init_sounder(device))
|
|
||||||
|
|
||||||
println_ts("speaker connected")
|
|
||||||
log.info(util.c("connected speaker ", param1))
|
|
||||||
|
|
||||||
databus.tx_hw_spkr_count(#sounders)
|
|
||||||
else
|
else
|
||||||
-- relink lost peripheral to correct unit entry
|
-- relink lost peripheral to correct unit entry
|
||||||
for i = 1, #units do
|
for i = 1, #units do
|
||||||
@@ -347,7 +298,7 @@ function threads.thread__main(smem)
|
|||||||
-- check for termination request
|
-- check for termination request
|
||||||
if event == "terminate" or ppm.should_terminate() then
|
if event == "terminate" or ppm.should_terminate() then
|
||||||
rtu_state.shutdown = true
|
rtu_state.shutdown = true
|
||||||
log.info("terminate requested, main thread exiting")
|
log.info("OS: terminate requested, main thread exiting")
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -366,7 +317,7 @@ function threads.thread__main(smem)
|
|||||||
databus.tx_rt_status("main", false)
|
databus.tx_rt_status("main", false)
|
||||||
|
|
||||||
if not rtu_state.shutdown then
|
if not rtu_state.shutdown then
|
||||||
log.info("main thread restarting in 5 seconds...")
|
log.info("OS: main thread restarting in 5 seconds...")
|
||||||
util.psleep(5)
|
util.psleep(5)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -385,16 +336,16 @@ function threads.thread__comms(smem)
|
|||||||
-- execute thread
|
-- execute thread
|
||||||
function public.exec()
|
function public.exec()
|
||||||
databus.tx_rt_status("comms", true)
|
databus.tx_rt_status("comms", true)
|
||||||
log.debug("comms thread start")
|
log.debug("OS: comms thread start")
|
||||||
|
|
||||||
-- load in from shared memory
|
-- load in from shared memory
|
||||||
local rtu_state = smem.rtu_state
|
local rtu_state = smem.rtu_state
|
||||||
local sounders = smem.rtu_dev.sounders
|
|
||||||
local rtu_comms = smem.rtu_sys.rtu_comms
|
local rtu_comms = smem.rtu_sys.rtu_comms
|
||||||
local units = smem.rtu_sys.units
|
local units = smem.rtu_sys.units
|
||||||
|
|
||||||
local comms_queue = smem.q.mq_comms
|
local comms_queue = smem.q.mq_comms
|
||||||
|
|
||||||
|
local sounders = backplane.sounders()
|
||||||
|
|
||||||
local last_update = util.time()
|
local last_update = util.time()
|
||||||
|
|
||||||
-- thread loop
|
-- thread loop
|
||||||
@@ -419,7 +370,7 @@ function threads.thread__comms(smem)
|
|||||||
|
|
||||||
-- max 100ms spent processing queue
|
-- max 100ms spent processing queue
|
||||||
if util.time() - handle_start > 100 then
|
if util.time() - handle_start > 100 then
|
||||||
log.warning("comms thread exceeded 100ms queue process limit")
|
log.warning("OS: comms thread exceeded 100ms queue process limit")
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -430,7 +381,7 @@ function threads.thread__comms(smem)
|
|||||||
-- check for termination request
|
-- check for termination request
|
||||||
if rtu_state.shutdown then
|
if rtu_state.shutdown then
|
||||||
rtu_comms.close(rtu_state)
|
rtu_comms.close(rtu_state)
|
||||||
log.info("comms thread exiting")
|
log.info("OS: comms thread exiting")
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -452,7 +403,7 @@ function threads.thread__comms(smem)
|
|||||||
databus.tx_rt_status("comms", false)
|
databus.tx_rt_status("comms", false)
|
||||||
|
|
||||||
if not rtu_state.shutdown then
|
if not rtu_state.shutdown then
|
||||||
log.info("comms thread restarting in 5 seconds...")
|
log.info("OS: comms thread restarting in 5 seconds...")
|
||||||
util.psleep(5)
|
util.psleep(5)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -475,7 +426,7 @@ function threads.thread__unit_comms(smem, unit)
|
|||||||
-- execute thread
|
-- execute thread
|
||||||
function public.exec()
|
function public.exec()
|
||||||
databus.tx_rt_status("unit_" .. unit.uid, true)
|
databus.tx_rt_status("unit_" .. unit.uid, true)
|
||||||
log.debug(util.c("rtu unit thread start -> ", types.rtu_type_to_string(unit.type), " (", unit.name, ")"))
|
log.debug(util.c("OS: rtu unit thread start -> ", types.rtu_type_to_string(unit.type), " (", unit.name, ")"))
|
||||||
|
|
||||||
-- load in from shared memory
|
-- load in from shared memory
|
||||||
local rtu_state = smem.rtu_state
|
local rtu_state = smem.rtu_state
|
||||||
@@ -492,7 +443,7 @@ function threads.thread__unit_comms(smem, unit)
|
|||||||
local short_name = util.c(types.rtu_type_to_string(unit.type), " (", unit.name, ")")
|
local short_name = util.c(types.rtu_type_to_string(unit.type), " (", unit.name, ")")
|
||||||
|
|
||||||
if packet_queue == nil then
|
if packet_queue == nil then
|
||||||
log.error("rtu unit thread created without a message queue, exiting...", true)
|
log.error("OS: rtu unit thread created without a message queue, exiting...", true)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -520,7 +471,7 @@ function threads.thread__unit_comms(smem, unit)
|
|||||||
|
|
||||||
-- check for termination request
|
-- check for termination request
|
||||||
if rtu_state.shutdown then
|
if rtu_state.shutdown then
|
||||||
log.info("rtu unit thread exiting -> " .. short_name)
|
log.info("OS: rtu unit thread exiting -> " .. short_name)
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -585,7 +536,7 @@ function threads.thread__unit_comms(smem, unit)
|
|||||||
databus.tx_rt_status("unit_" .. unit.uid, false)
|
databus.tx_rt_status("unit_" .. unit.uid, false)
|
||||||
|
|
||||||
if not rtu_state.shutdown then
|
if not rtu_state.shutdown then
|
||||||
log.info(util.c("rtu unit thread ", types.rtu_type_to_string(unit.type), " (", unit.name, ") restarting in 5 seconds..."))
|
log.info(util.c("OS: rtu unit thread ", types.rtu_type_to_string(unit.type), " (", unit.name, ") restarting in 5 seconds..."))
|
||||||
util.psleep(5)
|
util.psleep(5)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
441
rtu/uinit.lua
Normal file
441
rtu/uinit.lua
Normal file
@@ -0,0 +1,441 @@
|
|||||||
|
local log = require("scada-common.log")
|
||||||
|
local mqueue = require("scada-common.mqueue")
|
||||||
|
local ppm = require("scada-common.ppm")
|
||||||
|
local rsio = require("scada-common.rsio")
|
||||||
|
local types = require("scada-common.types")
|
||||||
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
|
local databus = require("rtu.databus")
|
||||||
|
local modbus = require("rtu.modbus")
|
||||||
|
local rtu = require("rtu.rtu")
|
||||||
|
local threads = require("rtu.threads")
|
||||||
|
|
||||||
|
local boilerv_rtu = require("rtu.dev.boilerv_rtu")
|
||||||
|
local dynamicv_rtu = require("rtu.dev.dynamicv_rtu")
|
||||||
|
local envd_rtu = require("rtu.dev.envd_rtu")
|
||||||
|
local imatrix_rtu = require("rtu.dev.imatrix_rtu")
|
||||||
|
local redstone_rtu = require("rtu.dev.redstone_rtu")
|
||||||
|
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 println = util.println
|
||||||
|
local println_ts = util.println_ts
|
||||||
|
|
||||||
|
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
|
||||||
|
local RTU_HW_STATE = databus.RTU_HW_STATE
|
||||||
|
|
||||||
|
-- print and log a fatal error during startup
|
||||||
|
---@param msg string
|
||||||
|
local function log_fail(msg)
|
||||||
|
println(msg)
|
||||||
|
log.fatal(msg)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- get a string representation of a port interface
|
||||||
|
---@param entry rtu_rs_definition
|
||||||
|
---@return string
|
||||||
|
local function entry_iface_name(entry)
|
||||||
|
return util.trinary(entry.color ~= nil, util.c(entry.side, "/", rsio.color_name(entry.color)), entry.side)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- configure RTU gateway based on settings file definitions
|
||||||
|
---@param config rtu_config
|
||||||
|
---@param __shared_memory rtu_shared_memory
|
||||||
|
---@return boolean success
|
||||||
|
return function(config, __shared_memory)
|
||||||
|
local units = __shared_memory.rtu_sys.units
|
||||||
|
|
||||||
|
local rtu_redstone = config.Redstone
|
||||||
|
local rtu_devices = config.Peripherals
|
||||||
|
|
||||||
|
--#region Redstone Interfaces
|
||||||
|
|
||||||
|
local rs_rtus = {} ---@type { name: string, hw_state: RTU_HW_STATE, rtu: rtu_rs_device, phy: table, banks: rtu_rs_definition[][] }[]
|
||||||
|
local all_conns = { [0] = {}, {}, {}, {}, {} }
|
||||||
|
|
||||||
|
-- go through redstone definitions list
|
||||||
|
for entry_idx = 1, #rtu_redstone do
|
||||||
|
local entry = rtu_redstone[entry_idx]
|
||||||
|
|
||||||
|
local assignment
|
||||||
|
local for_reactor = entry.unit
|
||||||
|
local phy = entry.relay or 0
|
||||||
|
local phy_name = entry.relay or "local"
|
||||||
|
local iface_name = entry_iface_name(entry)
|
||||||
|
|
||||||
|
if util.is_int(entry.unit) and entry.unit > 0 and entry.unit < 5 then
|
||||||
|
---@cast for_reactor integer
|
||||||
|
assignment = "reactor unit " .. entry.unit
|
||||||
|
elseif entry.unit == nil then
|
||||||
|
assignment = "facility"
|
||||||
|
for_reactor = 0
|
||||||
|
else
|
||||||
|
log_fail(util.c("uinit> invalid unit assignment at block index #", entry_idx))
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- create the appropriate RTU if it doesn't exist and check relay name validity
|
||||||
|
if entry.relay then
|
||||||
|
if type(entry.relay) ~= "string" then
|
||||||
|
log_fail(util.c("uinit> invalid redstone relay '", entry.relay, '"'))
|
||||||
|
return false
|
||||||
|
elseif not rs_rtus[entry.relay] then
|
||||||
|
log.debug(util.c("uinit> allocated relay redstone RTU on interface ", entry.relay))
|
||||||
|
|
||||||
|
local hw_state = RTU_HW_STATE.OK
|
||||||
|
local relay = ppm.get_periph(entry.relay)
|
||||||
|
|
||||||
|
if not relay then
|
||||||
|
hw_state = RTU_HW_STATE.OFFLINE
|
||||||
|
log.warning(util.c("uinit> redstone relay ", entry.relay, " is not connected"))
|
||||||
|
local _, v_device = ppm.mount_virtual()
|
||||||
|
relay = v_device
|
||||||
|
elseif ppm.get_type(entry.relay) ~= "redstone_relay" then
|
||||||
|
hw_state = RTU_HW_STATE.FAULTED
|
||||||
|
log.warning(util.c("uinit> redstone relay ", entry.relay, " is not a redstone relay"))
|
||||||
|
end
|
||||||
|
|
||||||
|
rs_rtus[entry.relay] = { name = entry.relay, hw_state = hw_state, rtu = redstone_rtu.new(relay), phy = relay, banks = { [0] = {}, {}, {}, {}, {} } }
|
||||||
|
end
|
||||||
|
elseif rs_rtus[0] == nil then
|
||||||
|
log.debug(util.c("uinit> allocated local redstone RTU"))
|
||||||
|
rs_rtus[0] = { name = "redstone_local", hw_state = RTU_HW_STATE.OK, rtu = redstone_rtu.new(), phy = rs, banks = { [0] = {}, {}, {}, {}, {} } }
|
||||||
|
end
|
||||||
|
|
||||||
|
-- verify configuration
|
||||||
|
local valid = false
|
||||||
|
if rsio.is_valid_port(entry.port) and rsio.is_valid_side(entry.side) then
|
||||||
|
valid = util.trinary(entry.color == nil, true, rsio.is_color(entry.color))
|
||||||
|
end
|
||||||
|
|
||||||
|
local bank = rs_rtus[phy].banks[for_reactor]
|
||||||
|
local conns = all_conns[for_reactor]
|
||||||
|
|
||||||
|
if not valid then
|
||||||
|
log_fail(util.c("uinit> invalid redstone definition at block index #", entry_idx))
|
||||||
|
return false
|
||||||
|
else
|
||||||
|
-- link redstone in RTU
|
||||||
|
local mode = rsio.get_io_mode(entry.port)
|
||||||
|
if mode == rsio.IO_MODE.DIGITAL_IN then
|
||||||
|
-- can't have duplicate inputs
|
||||||
|
if util.table_contains(conns, entry.port) then
|
||||||
|
local message = util.c("uinit> skipping duplicate input for port ", rsio.to_string(entry.port), " on side ", iface_name, " @ ", phy_name)
|
||||||
|
println(message)
|
||||||
|
log.warning(message)
|
||||||
|
else
|
||||||
|
table.insert(bank, entry)
|
||||||
|
end
|
||||||
|
elseif mode == rsio.IO_MODE.ANALOG_IN then
|
||||||
|
-- can't have duplicate inputs
|
||||||
|
if util.table_contains(conns, entry.port) then
|
||||||
|
local message = util.c("uinit> skipping duplicate input for port ", rsio.to_string(entry.port), " on side ", iface_name, " @ ", phy_name)
|
||||||
|
println(message)
|
||||||
|
log.warning(message)
|
||||||
|
else
|
||||||
|
table.insert(bank, entry)
|
||||||
|
end
|
||||||
|
elseif (mode == rsio.IO_MODE.DIGITAL_OUT) or (mode == rsio.IO_MODE.ANALOG_OUT) then
|
||||||
|
table.insert(bank, entry)
|
||||||
|
else
|
||||||
|
-- should be unreachable code, we already validated ports
|
||||||
|
log.fatal("uinit> failed to identify IO mode at block index #" .. entry_idx)
|
||||||
|
println("uinit> encountered a software error, check logs")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(conns, entry.port)
|
||||||
|
|
||||||
|
log.debug(util.c("uinit> banked redstone ", #conns, ": ", rsio.to_string(entry.port), " (", iface_name, " @ ", phy_name, ") for ", assignment))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- create unit entries for redstone RTUs
|
||||||
|
for _, def in pairs(rs_rtus) do
|
||||||
|
local rtu_conns = { [0] = {}, {}, {}, {}, {} }
|
||||||
|
|
||||||
|
-- connect the IO banks
|
||||||
|
for for_reactor = 0, #def.banks do
|
||||||
|
local bank = def.banks[for_reactor]
|
||||||
|
local conns = rtu_conns[for_reactor]
|
||||||
|
local assign = util.trinary(for_reactor > 0, "reactor unit " .. for_reactor, "the facility")
|
||||||
|
|
||||||
|
-- link redstone to the RTU
|
||||||
|
for i = 1, #bank do
|
||||||
|
local conn = bank[i]
|
||||||
|
local phy_name = conn.relay or "local"
|
||||||
|
|
||||||
|
local mode = rsio.get_io_mode(conn.port)
|
||||||
|
if mode == rsio.IO_MODE.DIGITAL_IN then
|
||||||
|
def.rtu.link_di(conn.side, conn.color, conn.invert)
|
||||||
|
elseif mode == rsio.IO_MODE.DIGITAL_OUT then
|
||||||
|
def.rtu.link_do(conn.side, conn.color, conn.invert)
|
||||||
|
elseif mode == rsio.IO_MODE.ANALOG_IN then
|
||||||
|
def.rtu.link_ai(conn.side)
|
||||||
|
elseif mode == rsio.IO_MODE.ANALOG_OUT then
|
||||||
|
def.rtu.link_ao(conn.side)
|
||||||
|
else
|
||||||
|
log.fatal(util.c("uinit> failed to identify IO mode of ", rsio.to_string(conn.port), " (", entry_iface_name(conn), " @ ", phy_name, ") for ", assign))
|
||||||
|
println("uinit> encountered a software error, check logs")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(conns, conn.port)
|
||||||
|
|
||||||
|
log.debug(util.c("uinit> linked redstone ", for_reactor, ".", #conns, ": ", rsio.to_string(conn.port), " (", entry_iface_name(conn), ")", " @ ", phy_name, ") for ", assign))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@type rtu_registry_entry
|
||||||
|
local unit = {
|
||||||
|
uid = 0,
|
||||||
|
name = def.name,
|
||||||
|
type = RTU_UNIT_TYPE.REDSTONE,
|
||||||
|
index = false,
|
||||||
|
reactor = nil,
|
||||||
|
device = def.phy,
|
||||||
|
rs_conns = rtu_conns,
|
||||||
|
is_multiblock = false,
|
||||||
|
formed = nil,
|
||||||
|
hw_state = def.hw_state,
|
||||||
|
rtu = def.rtu,
|
||||||
|
modbus_io = modbus.new(def.rtu, false),
|
||||||
|
pkt_queue = nil,
|
||||||
|
thread = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
table.insert(units, unit)
|
||||||
|
|
||||||
|
local type = util.trinary(def.phy == rs, "redstone", "redstone_relay")
|
||||||
|
|
||||||
|
log.info(util.c("uinit> initialized RTU unit #", #units, ": ", unit.name, " (", type, ")"))
|
||||||
|
|
||||||
|
unit.uid = #units
|
||||||
|
|
||||||
|
databus.tx_unit_hw_status(unit.uid, unit.hw_state)
|
||||||
|
end
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
--#region Mounted Peripherals
|
||||||
|
|
||||||
|
for i = 1, #rtu_devices do
|
||||||
|
local entry = rtu_devices[i] ---@type rtu_peri_definition
|
||||||
|
local name = entry.name
|
||||||
|
local index = entry.index
|
||||||
|
local for_reactor = util.trinary(entry.unit == nil, 0, entry.unit)
|
||||||
|
|
||||||
|
-- CHECK: name is a string
|
||||||
|
if type(name) ~= "string" then
|
||||||
|
log_fail(util.c("uinit> device entry #", i, ": device ", name, " isn't a string"))
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- CHECK: index type
|
||||||
|
if (index ~= nil) and (not util.is_int(index)) then
|
||||||
|
log_fail(util.c("uinit> device entry #", i, ": index ", index, " isn't valid"))
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- CHECK: index range
|
||||||
|
local function validate_index(min, max)
|
||||||
|
if (not util.is_int(index)) or ((index < min) and (max ~= nil and index > max)) then
|
||||||
|
local message = util.c("uinit> device entry #", i, ": index ", index, " isn't >= ", min)
|
||||||
|
if max ~= nil then message = util.c(message, " and <= ", max) end
|
||||||
|
log_fail(message)
|
||||||
|
return false
|
||||||
|
else return true end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- CHECK: reactor is an integer >= 0
|
||||||
|
local function validate_assign(for_facility)
|
||||||
|
if for_facility and for_reactor ~= 0 then
|
||||||
|
log_fail(util.c("uinit> device entry #", i, ": must only be for the facility"))
|
||||||
|
return false
|
||||||
|
elseif (not for_facility) and ((not util.is_int(for_reactor)) or (for_reactor < 1) or (for_reactor > 4)) then
|
||||||
|
log_fail(util.c("uinit> device entry #", i, ": unit assignment ", for_reactor, " isn't vaild"))
|
||||||
|
return false
|
||||||
|
else return true end
|
||||||
|
end
|
||||||
|
|
||||||
|
local device = ppm.get_periph(name)
|
||||||
|
|
||||||
|
local type ---@type string|nil
|
||||||
|
local rtu_iface ---@type rtu_device
|
||||||
|
local rtu_type ---@type RTU_UNIT_TYPE
|
||||||
|
local is_multiblock = false ---@type boolean
|
||||||
|
local formed = nil ---@type boolean|nil
|
||||||
|
local faulted = nil ---@type boolean|nil
|
||||||
|
|
||||||
|
if device == nil then
|
||||||
|
local message = util.c("uinit> '", name, "' not found, using placeholder")
|
||||||
|
println(message)
|
||||||
|
log.warning(message)
|
||||||
|
|
||||||
|
-- mount a virtual (placeholder) device
|
||||||
|
type, device = ppm.mount_virtual()
|
||||||
|
else
|
||||||
|
type = ppm.get_type(name)
|
||||||
|
end
|
||||||
|
|
||||||
|
if type == "boilerValve" then
|
||||||
|
-- boiler multiblock
|
||||||
|
if not validate_index(1, 2) then return false end
|
||||||
|
if not validate_assign() then return false end
|
||||||
|
|
||||||
|
rtu_type = RTU_UNIT_TYPE.BOILER_VALVE
|
||||||
|
rtu_iface, faulted = boilerv_rtu.new(device)
|
||||||
|
is_multiblock = true
|
||||||
|
formed = device.isFormed()
|
||||||
|
|
||||||
|
if formed == ppm.ACCESS_FAULT then
|
||||||
|
println_ts(util.c("uinit> failed to check if '", name, "' is formed"))
|
||||||
|
log.warning(util.c("uinit> failed to check if '", name, "' is a formed boiler multiblock"))
|
||||||
|
end
|
||||||
|
elseif type == "turbineValve" then
|
||||||
|
-- turbine multiblock
|
||||||
|
if not validate_index(1, 3) then return false end
|
||||||
|
if not validate_assign() then return false end
|
||||||
|
|
||||||
|
rtu_type = RTU_UNIT_TYPE.TURBINE_VALVE
|
||||||
|
rtu_iface, faulted = turbinev_rtu.new(device)
|
||||||
|
is_multiblock = true
|
||||||
|
formed = device.isFormed()
|
||||||
|
|
||||||
|
if formed == ppm.ACCESS_FAULT then
|
||||||
|
println_ts(util.c("uinit> failed to check if '", name, "' is formed"))
|
||||||
|
log.warning(util.c("uinit> failed to check if '", name, "' is a formed turbine multiblock"))
|
||||||
|
end
|
||||||
|
elseif type == "dynamicValve" then
|
||||||
|
-- dynamic tank multiblock
|
||||||
|
if entry.unit == nil then
|
||||||
|
if not validate_index(1, 4) then return false end
|
||||||
|
if not validate_assign(true) then return false end
|
||||||
|
else
|
||||||
|
if not validate_index(1, 1) then return false end
|
||||||
|
if not validate_assign() then return false end
|
||||||
|
end
|
||||||
|
|
||||||
|
rtu_type = RTU_UNIT_TYPE.DYNAMIC_VALVE
|
||||||
|
rtu_iface, faulted = dynamicv_rtu.new(device)
|
||||||
|
is_multiblock = true
|
||||||
|
formed = device.isFormed()
|
||||||
|
|
||||||
|
if formed == ppm.ACCESS_FAULT then
|
||||||
|
println_ts(util.c("uinit> failed to check if '", name, "' is formed"))
|
||||||
|
log.warning(util.c("uinit> failed to check if '", name, "' is a formed dynamic tank multiblock"))
|
||||||
|
end
|
||||||
|
elseif type == "inductionPort" or type == "reinforcedInductionPort" then
|
||||||
|
-- induction matrix multiblock (normal or reinforced)
|
||||||
|
if not validate_assign(true) then return false end
|
||||||
|
|
||||||
|
rtu_type = RTU_UNIT_TYPE.IMATRIX
|
||||||
|
rtu_iface, faulted = imatrix_rtu.new(device)
|
||||||
|
is_multiblock = true
|
||||||
|
formed = device.isFormed()
|
||||||
|
|
||||||
|
if formed == ppm.ACCESS_FAULT then
|
||||||
|
println_ts(util.c("uinit> failed to check if '", name, "' is formed"))
|
||||||
|
log.warning(util.c("uinit> failed to check if '", name, "' is a formed induction matrix multiblock"))
|
||||||
|
end
|
||||||
|
elseif type == "spsPort" then
|
||||||
|
-- SPS multiblock
|
||||||
|
if not validate_assign(true) then return false end
|
||||||
|
|
||||||
|
rtu_type = RTU_UNIT_TYPE.SPS
|
||||||
|
rtu_iface, faulted = sps_rtu.new(device)
|
||||||
|
is_multiblock = true
|
||||||
|
formed = device.isFormed()
|
||||||
|
|
||||||
|
if formed == ppm.ACCESS_FAULT then
|
||||||
|
println_ts(util.c("uinit> failed to check if '", name, "' is formed"))
|
||||||
|
log.warning(util.c("uinit> failed to check if '", name, "' is a formed SPS multiblock"))
|
||||||
|
end
|
||||||
|
elseif type == "solarNeutronActivator" then
|
||||||
|
-- SNA
|
||||||
|
if not validate_assign() then return false end
|
||||||
|
|
||||||
|
rtu_type = RTU_UNIT_TYPE.SNA
|
||||||
|
rtu_iface, faulted = sna_rtu.new(device)
|
||||||
|
elseif type == "environmentDetector" or type == "environment_detector" then
|
||||||
|
-- advanced peripherals environment detector
|
||||||
|
if not validate_index(1) then return false end
|
||||||
|
if not validate_assign(entry.unit == nil) then return false end
|
||||||
|
|
||||||
|
rtu_type = RTU_UNIT_TYPE.ENV_DETECTOR
|
||||||
|
rtu_iface, faulted = envd_rtu.new(device)
|
||||||
|
elseif type == ppm.VIRTUAL_DEVICE_TYPE then
|
||||||
|
-- placeholder device
|
||||||
|
rtu_type = RTU_UNIT_TYPE.VIRTUAL
|
||||||
|
rtu_iface = rtu.init_unit().interface()
|
||||||
|
else
|
||||||
|
log_fail(util.c("uinit> device '", name, "' is not a known type (", type, ")"))
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
if is_multiblock then
|
||||||
|
if not formed then
|
||||||
|
if formed == false then
|
||||||
|
log.info(util.c("uinit> device '", name, "' is not formed"))
|
||||||
|
else formed = false end
|
||||||
|
elseif faulted then
|
||||||
|
-- sometimes there is a race condition on server boot where it reports formed, but
|
||||||
|
-- the other functions are not yet defined (that's the theory at least). mark as unformed to attempt connection later
|
||||||
|
formed = false
|
||||||
|
log.warning(util.c("uinit> device '", name, "' is formed, but initialization had one or more faults: marked as unformed"))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@class rtu_registry_entry
|
||||||
|
local rtu_unit = {
|
||||||
|
uid = 0, ---@type integer RTU unit ID
|
||||||
|
name = name, ---@type string unit name
|
||||||
|
type = rtu_type, ---@type RTU_UNIT_TYPE unit type
|
||||||
|
index = index or false, ---@type integer|false device index
|
||||||
|
reactor = for_reactor, ---@type integer|nil unit/facility assignment
|
||||||
|
device = device, ---@type table peripheral reference
|
||||||
|
rs_conns = nil, ---@type IO_PORT[][]|nil available redstone connections
|
||||||
|
is_multiblock = is_multiblock, ---@type boolean if this is for a multiblock peripheral
|
||||||
|
formed = formed, ---@type boolean|nil if this peripheral is currently formed
|
||||||
|
hw_state = RTU_HW_STATE.OFFLINE, ---@type RTU_HW_STATE hardware device status
|
||||||
|
rtu = rtu_iface, ---@type rtu_device|rtu_rs_device RTU hardware interface
|
||||||
|
modbus_io = modbus.new(rtu_iface, true), ---@type modbus MODBUS interface
|
||||||
|
pkt_queue = mqueue.new(), ---@type mqueue|nil packet queue
|
||||||
|
thread = nil ---@type parallel_thread|nil associated RTU thread
|
||||||
|
}
|
||||||
|
|
||||||
|
rtu_unit.thread = threads.thread__unit_comms(__shared_memory, rtu_unit)
|
||||||
|
|
||||||
|
table.insert(units, rtu_unit)
|
||||||
|
|
||||||
|
local for_message = "the facility"
|
||||||
|
if for_reactor > 0 then
|
||||||
|
for_message = util.c("reactor ", for_reactor)
|
||||||
|
end
|
||||||
|
|
||||||
|
local index_str = util.trinary(index ~= nil, util.c(" [", index, "]"), "")
|
||||||
|
log.info(util.c("uinit> initialized RTU unit #", #units, ": ", name, " (", types.rtu_type_to_string(rtu_type), ")", index_str, " for ", for_message))
|
||||||
|
|
||||||
|
rtu_unit.uid = #units
|
||||||
|
|
||||||
|
-- determine hardware status
|
||||||
|
if rtu_unit.type == RTU_UNIT_TYPE.VIRTUAL then
|
||||||
|
rtu_unit.hw_state = RTU_HW_STATE.OFFLINE
|
||||||
|
else
|
||||||
|
if rtu_unit.is_multiblock then
|
||||||
|
rtu_unit.hw_state = util.trinary(rtu_unit.formed == true, RTU_HW_STATE.OK, RTU_HW_STATE.UNFORMED)
|
||||||
|
elseif faulted then
|
||||||
|
rtu_unit.hw_state = RTU_HW_STATE.FAULTED
|
||||||
|
else
|
||||||
|
rtu_unit.hw_state = RTU_HW_STATE.OK
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- report hardware status
|
||||||
|
databus.tx_unit_hw_status(rtu_unit.uid, rtu_unit.hw_state)
|
||||||
|
end
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
@@ -17,8 +17,8 @@ local max_distance = nil
|
|||||||
local comms = {}
|
local comms = {}
|
||||||
|
|
||||||
-- protocol/data versions (protocol/data independent changes tracked by util.lua version)
|
-- protocol/data versions (protocol/data independent changes tracked by util.lua version)
|
||||||
comms.version = "3.0.5"
|
comms.version = "3.1.0"
|
||||||
comms.api_version = "0.0.9"
|
comms.api_version = "0.0.10"
|
||||||
|
|
||||||
---@enum PROTOCOL
|
---@enum PROTOCOL
|
||||||
local PROTOCOL = {
|
local PROTOCOL = {
|
||||||
@@ -49,12 +49,14 @@ local MGMT_TYPE = {
|
|||||||
ESTABLISH = 0, -- establish new connection
|
ESTABLISH = 0, -- establish new connection
|
||||||
KEEP_ALIVE = 1, -- keep alive packet w/ RTT
|
KEEP_ALIVE = 1, -- keep alive packet w/ RTT
|
||||||
CLOSE = 2, -- close a connection
|
CLOSE = 2, -- close a connection
|
||||||
RTU_ADVERT = 3, -- RTU capability advertisement
|
PROBE = 3,
|
||||||
RTU_DEV_REMOUNT = 4, -- RTU multiblock possbily changed (formed, unformed) due to PPM remount
|
RTU_ADVERT = 4, -- RTU capability advertisement
|
||||||
RTU_TONE_ALARM = 5, -- instruct RTUs to play specified alarm tones
|
RTU_DEV_REMOUNT = 5, -- RTU multiblock possbily changed (formed, unformed) due to PPM remount
|
||||||
DIAG_TONE_GET = 6, -- diagnostic: get alarm tones
|
RTU_TONE_ALARM = 6, -- instruct RTUs to play specified alarm tones
|
||||||
DIAG_TONE_SET = 7, -- diagnostic: set alarm tones
|
DIAG_TONE_GET = 7, -- (API) diagnostic: get alarm tones
|
||||||
DIAG_ALARM_SET = 8 -- diagnostic: set alarm to simulate audio for
|
DIAG_TONE_SET = 8, -- (API) diagnostic: set alarm tones
|
||||||
|
DIAG_ALARM_SET = 9, -- (API) diagnostic: set alarm to simulate audio for
|
||||||
|
INFO_LIST_CMP = 10 -- (API) info: list all computers on the network
|
||||||
}
|
}
|
||||||
|
|
||||||
---@enum CRDN_TYPE
|
---@enum CRDN_TYPE
|
||||||
@@ -72,7 +74,8 @@ local CRDN_TYPE = {
|
|||||||
API_GET_UNIT = 10, -- API: get reactor unit data
|
API_GET_UNIT = 10, -- API: get reactor unit data
|
||||||
API_GET_CTRL = 11, -- API: get data for the control app
|
API_GET_CTRL = 11, -- API: get data for the control app
|
||||||
API_GET_PROC = 12, -- API: get data for the process app
|
API_GET_PROC = 12, -- API: get data for the process app
|
||||||
API_GET_WASTE = 13 -- API: get data for the waste app
|
API_GET_WASTE = 13, -- API: get data for the waste app
|
||||||
|
API_GET_RAD = 14 -- API: get data for the radiation monitor app
|
||||||
}
|
}
|
||||||
|
|
||||||
---@enum ESTABLISH_ACK
|
---@enum ESTABLISH_ACK
|
||||||
@@ -87,6 +90,12 @@ local ESTABLISH_ACK = {
|
|||||||
---@enum DEVICE_TYPE device types for establish messages
|
---@enum DEVICE_TYPE device types for establish messages
|
||||||
local DEVICE_TYPE = { PLC = 0, RTU = 1, SVR = 2, CRD = 3, PKT = 4 }
|
local DEVICE_TYPE = { PLC = 0, RTU = 1, SVR = 2, CRD = 3, PKT = 4 }
|
||||||
|
|
||||||
|
---@enum PROBE_ACK
|
||||||
|
local PROBE_ACK = {
|
||||||
|
OPEN = 0,
|
||||||
|
CONFLICT = 1
|
||||||
|
}
|
||||||
|
|
||||||
---@enum PLC_AUTO_ACK
|
---@enum PLC_AUTO_ACK
|
||||||
local PLC_AUTO_ACK = {
|
local PLC_AUTO_ACK = {
|
||||||
FAIL = 0, -- failed to set burn rate/burn rate invalid
|
FAIL = 0, -- failed to set burn rate/burn rate invalid
|
||||||
@@ -128,6 +137,8 @@ comms.CRDN_TYPE = CRDN_TYPE
|
|||||||
comms.ESTABLISH_ACK = ESTABLISH_ACK
|
comms.ESTABLISH_ACK = ESTABLISH_ACK
|
||||||
comms.DEVICE_TYPE = DEVICE_TYPE
|
comms.DEVICE_TYPE = DEVICE_TYPE
|
||||||
|
|
||||||
|
comms.PROBE_ACK = PROBE_ACK
|
||||||
|
|
||||||
comms.PLC_AUTO_ACK = PLC_AUTO_ACK
|
comms.PLC_AUTO_ACK = PLC_AUTO_ACK
|
||||||
|
|
||||||
comms.UNIT_COMMAND = UNIT_COMMAND
|
comms.UNIT_COMMAND = UNIT_COMMAND
|
||||||
@@ -136,6 +147,9 @@ comms.FAC_COMMAND = FAC_COMMAND
|
|||||||
-- destination broadcast address (to all devices)
|
-- destination broadcast address (to all devices)
|
||||||
comms.BROADCAST = -1
|
comms.BROADCAST = -1
|
||||||
|
|
||||||
|
-- firmware version used to indicate an establish packet is a connection test
|
||||||
|
comms.CONN_TEST_FWV = "CONN_TEST"
|
||||||
|
|
||||||
---@alias packet scada_packet|modbus_packet|rplc_packet|mgmt_packet|crdn_packet
|
---@alias packet scada_packet|modbus_packet|rplc_packet|mgmt_packet|crdn_packet
|
||||||
---@alias frame modbus_frame|rplc_frame|mgmt_frame|crdn_frame
|
---@alias frame modbus_frame|rplc_frame|mgmt_frame|crdn_frame
|
||||||
|
|
||||||
@@ -203,7 +217,7 @@ function comms.scada_packet()
|
|||||||
|
|
||||||
if (type(max_distance) == "number") and (type(distance) == "number") and (distance > max_distance) then
|
if (type(max_distance) == "number") and (type(distance) == "number") and (distance > max_distance) then
|
||||||
-- outside of maximum allowable transmission distance
|
-- outside of maximum allowable transmission distance
|
||||||
-- log.debug("comms.scada_packet.receive(): discarding packet with distance " .. distance .. " (outside trusted range)")
|
-- log.debug("COMMS: scada_packet.receive(): discarding packet with distance " .. distance .. " (outside trusted range)")
|
||||||
else
|
else
|
||||||
if type(self.raw) == "table" then
|
if type(self.raw) == "table" then
|
||||||
if #self.raw == 5 then
|
if #self.raw == 5 then
|
||||||
@@ -249,6 +263,8 @@ function comms.scada_packet()
|
|||||||
---@nodiscard
|
---@nodiscard
|
||||||
function public.raw_sendable() return self.raw end
|
function public.raw_sendable() return self.raw end
|
||||||
|
|
||||||
|
---@nodiscard
|
||||||
|
function public.interface() return self.modem_msg_in.iface end
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
function public.local_channel() return self.modem_msg_in.s_channel end
|
function public.local_channel() return self.modem_msg_in.s_channel end
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
@@ -324,7 +340,7 @@ function comms.authd_packet()
|
|||||||
|
|
||||||
if (type(max_distance) == "number") and ((type(distance) ~= "number") or (distance > max_distance)) then
|
if (type(max_distance) == "number") and ((type(distance) ~= "number") or (distance > max_distance)) then
|
||||||
-- outside of maximum allowable transmission distance
|
-- outside of maximum allowable transmission distance
|
||||||
-- log.debug("comms.authd_packet.receive(): discarding packet with distance " .. distance .. " (outside trusted range)")
|
-- log.debug("COMMS: authd_packet.receive(): discarding packet with distance " .. distance .. " (outside trusted range)")
|
||||||
else
|
else
|
||||||
if type(self.raw) == "table" then
|
if type(self.raw) == "table" then
|
||||||
if #self.raw == 4 then
|
if #self.raw == 4 then
|
||||||
@@ -410,7 +426,7 @@ function comms.modbus_packet()
|
|||||||
self.raw = { self.txn_id, self.unit_id, self.func_code }
|
self.raw = { self.txn_id, self.unit_id, self.func_code }
|
||||||
for i = 1, self.length do insert(self.raw, data[i]) end
|
for i = 1, self.length do insert(self.raw, data[i]) end
|
||||||
else
|
else
|
||||||
log.error("comms.modbus_packet.make(): data not a table")
|
log.error("COMMS: modbus_packet.make(): data not a table")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -433,11 +449,11 @@ function comms.modbus_packet()
|
|||||||
|
|
||||||
return size_ok and valid
|
return size_ok and valid
|
||||||
else
|
else
|
||||||
log.debug("attempted MODBUS_TCP parse of incorrect protocol " .. frame.protocol(), true)
|
log.debug("COMMS: attempted MODBUS_TCP parse of incorrect protocol " .. frame.protocol(), true)
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
log.debug("nil frame encountered", true)
|
log.debug("COMMS: nil frame encountered", true)
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -496,7 +512,7 @@ function comms.rplc_packet()
|
|||||||
self.raw = { self.id, self.type }
|
self.raw = { self.id, self.type }
|
||||||
for i = 1, #data do insert(self.raw, data[i]) end
|
for i = 1, #data do insert(self.raw, data[i]) end
|
||||||
else
|
else
|
||||||
log.error("comms.rplc_packet.make(): data not a table")
|
log.error("COMMS: rplc_packet.make(): data not a table")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -519,11 +535,11 @@ function comms.rplc_packet()
|
|||||||
|
|
||||||
return ok
|
return ok
|
||||||
else
|
else
|
||||||
log.debug("attempted RPLC parse of incorrect protocol " .. frame.protocol(), true)
|
log.debug("COMMS: attempted RPLC parse of incorrect protocol " .. frame.protocol(), true)
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
log.debug("nil frame encountered", true)
|
log.debug("COMMS: nil frame encountered", true)
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -578,7 +594,7 @@ function comms.mgmt_packet()
|
|||||||
self.raw = { self.type }
|
self.raw = { self.type }
|
||||||
for i = 1, #data do insert(self.raw, data[i]) end
|
for i = 1, #data do insert(self.raw, data[i]) end
|
||||||
else
|
else
|
||||||
log.error("comms.mgmt_packet.make(): data not a table")
|
log.error("COMMS: mgmt_packet.make(): data not a table")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -599,11 +615,11 @@ function comms.mgmt_packet()
|
|||||||
|
|
||||||
return ok
|
return ok
|
||||||
else
|
else
|
||||||
log.debug("attempted SCADA_MGMT parse of incorrect protocol " .. frame.protocol(), true)
|
log.debug("COMMS: attempted SCADA_MGMT parse of incorrect protocol " .. frame.protocol(), true)
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
log.debug("nil frame encountered", true)
|
log.debug("COMMS: nil frame encountered", true)
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -657,7 +673,7 @@ function comms.crdn_packet()
|
|||||||
self.raw = { self.type }
|
self.raw = { self.type }
|
||||||
for i = 1, #data do insert(self.raw, data[i]) end
|
for i = 1, #data do insert(self.raw, data[i]) end
|
||||||
else
|
else
|
||||||
log.error("comms.crdn_packet.make(): data not a table")
|
log.error("COMMS: crdn_packet.make(): data not a table")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -678,11 +694,11 @@ function comms.crdn_packet()
|
|||||||
|
|
||||||
return ok
|
return ok
|
||||||
else
|
else
|
||||||
log.debug("attempted SCADA_CRDN parse of incorrect protocol " .. frame.protocol(), true)
|
log.debug("COMMS: attempted SCADA_CRDN parse of incorrect protocol " .. frame.protocol(), true)
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
log.debug("nil frame encountered", true)
|
log.debug("COMMS: nil frame encountered", true)
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ constants.FLOW_STABILITY_DELAY_MS = 10000
|
|||||||
-- - background radiation 0.0000001 Sv/h (99.99 nSv/h)
|
-- - background radiation 0.0000001 Sv/h (99.99 nSv/h)
|
||||||
-- - "green tint" radiation 0.00001 Sv/h (10 uSv/h)
|
-- - "green tint" radiation 0.00001 Sv/h (10 uSv/h)
|
||||||
-- - damaging radiation 0.00006 Sv/h (60 uSv/h)
|
-- - damaging radiation 0.00006 Sv/h (60 uSv/h)
|
||||||
|
|
||||||
constants.LOW_RADIATION = 0.00001
|
constants.LOW_RADIATION = 0.00001
|
||||||
constants.HAZARD_RADIATION = 0.00006
|
constants.HAZARD_RADIATION = 0.00006
|
||||||
constants.HIGH_RADIATION = 0.001
|
constants.HIGH_RADIATION = 0.001
|
||||||
@@ -95,6 +96,11 @@ constants.VERY_HIGH_RADIATION = 0.1
|
|||||||
constants.SEVERE_RADIATION = 8.0
|
constants.SEVERE_RADIATION = 8.0
|
||||||
constants.EXTREME_RADIATION = 100.0
|
constants.EXTREME_RADIATION = 100.0
|
||||||
|
|
||||||
|
-- nominal RTT is ping (0ms to 10ms usually) + 150ms for SV main loop tick
|
||||||
|
|
||||||
|
constants.WARN_RTT = 300 -- 2x as long as expected w/ 0 ping
|
||||||
|
constants.HIGH_RTT = 500 -- 3.33x as long as expected w/ 0 ping
|
||||||
|
|
||||||
--#endregion
|
--#endregion
|
||||||
|
|
||||||
--#region Mekanism Configuration Constants
|
--#region Mekanism Configuration Constants
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
-- Crash Handler
|
-- Crash Handler
|
||||||
--
|
--
|
||||||
|
|
||||||
|
---@diagnostic disable-next-line: undefined-global
|
||||||
|
local _is_pocket_env = pocket -- luacheck: ignore pocket
|
||||||
|
|
||||||
local comms = require("scada-common.comms")
|
local comms = require("scada-common.comms")
|
||||||
local log = require("scada-common.log")
|
local log = require("scada-common.log")
|
||||||
local util = require("scada-common.util")
|
local util = require("scada-common.util")
|
||||||
@@ -36,6 +39,74 @@ local function log_versions(log_msg)
|
|||||||
if has_lockbox then log_msg(util.c("LOCKBOX VERSION: ", lockbox.version)) end
|
if has_lockbox then log_msg(util.c("LOCKBOX VERSION: ", lockbox.version)) end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- render the standard computer crash screen
|
||||||
|
---@param exit function callback on exit button press
|
||||||
|
---@return DisplayBox display
|
||||||
|
local function draw_computer_crash(exit)
|
||||||
|
local DisplayBox = require("graphics.elements.DisplayBox")
|
||||||
|
local Div = require("graphics.elements.Div")
|
||||||
|
local Rectangle = require("graphics.elements.Rectangle")
|
||||||
|
local TextBox = require("graphics.elements.TextBox")
|
||||||
|
local PushButton = require("graphics.elements.controls.PushButton")
|
||||||
|
|
||||||
|
local display = DisplayBox{window=term.current(),fg_bg=core.cpair(colors.white,colors.lightGray)}
|
||||||
|
|
||||||
|
local warning = Div{parent=display,x=2,y=2}
|
||||||
|
TextBox{parent=warning,x=7,text="\x90\n \x90\n \x90\n \x90\n \x90",fg_bg=core.cpair(colors.yellow,colors.lightGray)}
|
||||||
|
TextBox{parent=warning,x=5,y=1,text="\x9f ",width=2,fg_bg=core.cpair(colors.lightGray,colors.yellow)}
|
||||||
|
TextBox{parent=warning,x=4,text="\x9f ",width=4,fg_bg=core.cpair(colors.lightGray,colors.yellow)}
|
||||||
|
TextBox{parent=warning,x=3,text="\x9f ",width=6,fg_bg=core.cpair(colors.lightGray,colors.yellow)}
|
||||||
|
TextBox{parent=warning,x=2,text="\x9f ",width=8,fg_bg=core.cpair(colors.lightGray,colors.yellow)}
|
||||||
|
TextBox{parent=warning,text="\x9f ",width=10,fg_bg=core.cpair(colors.lightGray,colors.yellow)}
|
||||||
|
TextBox{parent=warning,text="\x8f\x8f\x8f\x8f\x8f\x8f\x8f\x8f\x8f\x8f\x8f",width=11,fg_bg=core.cpair(colors.yellow,colors.lightGray)}
|
||||||
|
TextBox{parent=warning,x=6,y=3,text=" \n \x83",width=1,fg_bg=core.cpair(colors.yellow,colors.white)}
|
||||||
|
|
||||||
|
TextBox{parent=display,x=13,y=2,text="Critical Software Fault Encountered",alignment=core.ALIGN.CENTER,fg_bg=core.cpair(colors.yellow,colors._INHERIT)}
|
||||||
|
TextBox{parent=display,x=15,y=4,text="Please consider reporting this on the cc-mek-scada Discord or GitHub.",width=36,alignment=core.ALIGN.CENTER}
|
||||||
|
TextBox{parent=display,x=14,y=7,text="refer to the log file for more info",alignment=core.ALIGN.CENTER,fg_bg=core.cpair(colors.gray,colors._INHERIT)}
|
||||||
|
|
||||||
|
local box = Rectangle{parent=display,x=2,y=9,width=display.get_width()-2,height=8,border=core.border(1,colors.gray,true),thin=true,fg_bg=core.cpair(colors.black,colors.white)}
|
||||||
|
TextBox{parent=box,text=err}
|
||||||
|
|
||||||
|
PushButton{parent=display,x=23,y=18,text=" Exit ",callback=exit,active_fg_bg=core.cpair(colors.white,colors.gray),fg_bg=core.cpair(colors.black,colors.red)}
|
||||||
|
|
||||||
|
return display
|
||||||
|
end
|
||||||
|
|
||||||
|
-- render the pocket crash screen
|
||||||
|
---@param exit function callback on exit button press
|
||||||
|
---@return DisplayBox display
|
||||||
|
local function draw_pocket_crash(exit)
|
||||||
|
local DisplayBox = require("graphics.elements.DisplayBox")
|
||||||
|
local Div = require("graphics.elements.Div")
|
||||||
|
local Rectangle = require("graphics.elements.Rectangle")
|
||||||
|
local TextBox = require("graphics.elements.TextBox")
|
||||||
|
local PushButton = require("graphics.elements.controls.PushButton")
|
||||||
|
|
||||||
|
local display = DisplayBox{window=term.current(),fg_bg=core.cpair(colors.white,colors.lightGray)}
|
||||||
|
|
||||||
|
local warning = Div{parent=display,x=2,y=1}
|
||||||
|
TextBox{parent=warning,x=4,y=1,text="\x90",width=1,fg_bg=core.cpair(colors.yellow,colors.lightGray)}
|
||||||
|
TextBox{parent=warning,x=3,text="\x81 ",width=2,fg_bg=core.cpair(colors.lightGray,colors.yellow)}
|
||||||
|
TextBox{parent=warning,x=5,y=2,text="\x94",width=1,fg_bg=core.cpair(colors.yellow,colors.lightGray)}
|
||||||
|
TextBox{parent=warning,x=2,text="\x81 ",width=4,fg_bg=core.cpair(colors.lightGray,colors.yellow)}
|
||||||
|
TextBox{parent=warning,x=6,y=3,text="\x94",width=1,fg_bg=core.cpair(colors.yellow,colors.lightGray)}
|
||||||
|
TextBox{parent=warning,text="\x8e\x8f\x8f\x8e\x8f\x8f\x84",width=7,fg_bg=core.cpair(colors.yellow,colors.lightGray)}
|
||||||
|
TextBox{parent=warning,x=4,y=2,text="\x90",width=1,fg_bg=core.cpair(colors.lightGray,colors.yellow)}
|
||||||
|
TextBox{parent=warning,x=4,y=3,text="\x85",width=1,fg_bg=core.cpair(colors.lightGray,colors.yellow)}
|
||||||
|
|
||||||
|
TextBox{parent=display,x=10,y=2,text=" Critical Software Fault",width=16,alignment=core.ALIGN.CENTER,fg_bg=core.cpair(colors.yellow,colors._INHERIT)}
|
||||||
|
TextBox{parent=display,x=2,y=5,text="Consider reporting this on the cc-mek-scada Discord or GitHub.",width=36,alignment=core.ALIGN.CENTER}
|
||||||
|
|
||||||
|
local box = Rectangle{parent=display,y=9,width=display.get_width(),height=8,fg_bg=core.cpair(colors.black,colors.white)}
|
||||||
|
TextBox{parent=box,text=err}
|
||||||
|
|
||||||
|
PushButton{parent=display,x=11,y=18,text=" Exit ",callback=exit,active_fg_bg=core.cpair(colors.white,colors.gray),fg_bg=core.cpair(colors.black,colors.red)}
|
||||||
|
TextBox{parent=display,x=2,y=20,text="see logs for details",width=24,alignment=core.ALIGN.CENTER,fg_bg=core.cpair(colors.gray,colors._INHERIT)}
|
||||||
|
|
||||||
|
return display
|
||||||
|
end
|
||||||
|
|
||||||
-- when running with debug logs, log the useful information that the crash handler knows
|
-- when running with debug logs, log the useful information that the crash handler knows
|
||||||
function crash.dbg_log_env() log_versions(log.debug) end
|
function crash.dbg_log_env() log_versions(log.debug) end
|
||||||
|
|
||||||
@@ -54,9 +125,41 @@ end
|
|||||||
|
|
||||||
-- final error print on failed xpcall, app exits here
|
-- final error print on failed xpcall, app exits here
|
||||||
function crash.exit()
|
function crash.exit()
|
||||||
|
local handled, run = false, true
|
||||||
|
local display ---@type DisplayBox
|
||||||
|
|
||||||
|
-- special graphical crash screen
|
||||||
|
if has_graphics then
|
||||||
|
handled, display = pcall(util.trinary(_is_pocket_env, draw_pocket_crash, draw_computer_crash), function () run = false end)
|
||||||
|
|
||||||
|
-- event loop
|
||||||
|
while display and run do
|
||||||
|
local event, param1, param2, param3 = util.pull_event()
|
||||||
|
|
||||||
|
-- handle event
|
||||||
|
if event == "mouse_click" or event == "mouse_up" or event == "double_click" then
|
||||||
|
local mouse = core.events.new_mouse_event(event, param1, param2, param3)
|
||||||
|
if mouse then display.handle_mouse(mouse) end
|
||||||
|
elseif event == "terminate" then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
display.delete()
|
||||||
|
|
||||||
|
term.setCursorPos(1, 1)
|
||||||
|
term.setTextColor(colors.white)
|
||||||
|
term.setBackgroundColor(colors.black)
|
||||||
|
term.clear()
|
||||||
|
end
|
||||||
|
|
||||||
log.close()
|
log.close()
|
||||||
util.println("fatal error occured in main application:")
|
|
||||||
error(err, 0)
|
-- default text failure message
|
||||||
|
if not handled then
|
||||||
|
util.println("fatal error occured in main application:")
|
||||||
|
error(err, 0)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return crash
|
return crash
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ local MODE = { APPEND = 0, NEW = 1 }
|
|||||||
|
|
||||||
log.MODE = MODE
|
log.MODE = MODE
|
||||||
|
|
||||||
local logger = {
|
local _log = {
|
||||||
not_ready = true,
|
not_ready = true,
|
||||||
path = "/log.txt",
|
path = "/log.txt",
|
||||||
mode = MODE.APPEND,
|
mode = MODE.APPEND,
|
||||||
@@ -42,36 +42,36 @@ local free_space = fs.getFreeSpace
|
|||||||
---@param err_msg string|nil error message
|
---@param err_msg string|nil error message
|
||||||
---@return boolean out_of_space
|
---@return boolean out_of_space
|
||||||
local function check_out_of_space(err_msg)
|
local function check_out_of_space(err_msg)
|
||||||
return (free_space(logger.path) < MIN_SPACE) or ((err_msg ~= nil) and (string.find(err_msg, OUT_OF_SPACE) ~= nil))
|
return (free_space(_log.path) < MIN_SPACE) or ((err_msg ~= nil) and (string.find(err_msg, OUT_OF_SPACE) ~= nil))
|
||||||
end
|
end
|
||||||
|
|
||||||
-- private log write function
|
-- private log write function
|
||||||
---@param msg_bits any[]
|
---@param msg_bits any[]
|
||||||
local function _log(msg_bits)
|
local function write_log(msg_bits)
|
||||||
if logger.not_ready then return end
|
if _log.not_ready then return end
|
||||||
|
|
||||||
local time_stamp = os.date(TIME_FMT)
|
local time_stamp = os.date(TIME_FMT)
|
||||||
local stamped = util.c(time_stamp, table.unpack(msg_bits))
|
local stamped = util.c(time_stamp, table.unpack(msg_bits))
|
||||||
|
|
||||||
-- attempt to write log
|
-- attempt to write log
|
||||||
local status, result = pcall(function ()
|
local status, result = pcall(function ()
|
||||||
logger.file.writeLine(stamped)
|
_log.file.writeLine(stamped)
|
||||||
logger.file.flush()
|
_log.file.flush()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
-- if we don't have space, we need to create a new log file
|
-- if we don't have space, we need to create a new log file
|
||||||
if check_out_of_space() then
|
if check_out_of_space() then
|
||||||
-- delete the old log file before opening a new one
|
-- delete the old log file before opening a new one
|
||||||
logger.file.close()
|
_log.file.close()
|
||||||
fs.delete(logger.path)
|
fs.delete(_log.path)
|
||||||
|
|
||||||
-- re-init logger and pass dmesg_out so that it doesn't change
|
-- re-init logger and pass dmesg_out so that it doesn't change
|
||||||
log.init(logger.path, logger.mode, logger.debug, logger.dmesg_out)
|
log.init(_log.path, _log.mode, _log.debug, _log.dmesg_out)
|
||||||
|
|
||||||
-- log the message and recycle warning
|
-- log the message and recycle warning
|
||||||
logger.file.writeLine(time_stamp .. WRN_TAG .. "recycled log file")
|
_log.file.writeLine(time_stamp .. WRN_TAG .. "recycled log file")
|
||||||
logger.file.writeLine(stamped)
|
_log.file.writeLine(stamped)
|
||||||
logger.file.flush()
|
_log.file.flush()
|
||||||
elseif (not status) and (result ~= nil) then
|
elseif (not status) and (result ~= nil) then
|
||||||
util.println("unexpected error writing to the log file: " .. result)
|
util.println("unexpected error writing to the log file: " .. result)
|
||||||
end
|
end
|
||||||
@@ -89,45 +89,45 @@ end
|
|||||||
function log.init(path, write_mode, include_debug, dmesg_redirect)
|
function log.init(path, write_mode, include_debug, dmesg_redirect)
|
||||||
local err_msg
|
local err_msg
|
||||||
|
|
||||||
logger.path = path
|
_log.path = path
|
||||||
logger.mode = write_mode
|
_log.mode = write_mode
|
||||||
logger.debug = include_debug
|
_log.debug = include_debug
|
||||||
logger.file, err_msg = fs.open(path, util.trinary(logger.mode == MODE.APPEND, "a", "w"))
|
_log.file, err_msg = fs.open(path, util.trinary(_log.mode == MODE.APPEND, "a", "w"))
|
||||||
|
|
||||||
if dmesg_redirect then
|
if dmesg_redirect then
|
||||||
logger.dmesg_out = dmesg_redirect
|
_log.dmesg_out = dmesg_redirect
|
||||||
else
|
else
|
||||||
logger.dmesg_out = term.current()
|
_log.dmesg_out = term.current()
|
||||||
end
|
end
|
||||||
|
|
||||||
-- check for space issues
|
-- check for space issues
|
||||||
local out_of_space = check_out_of_space(err_msg)
|
local out_of_space = check_out_of_space(err_msg)
|
||||||
|
|
||||||
-- try to handle problems
|
-- try to handle problems
|
||||||
if logger.file == nil or out_of_space then
|
if _log.file == nil or out_of_space then
|
||||||
if out_of_space then
|
if out_of_space then
|
||||||
if fs.exists(logger.path) then
|
if fs.exists(_log.path) then
|
||||||
fs.delete(logger.path)
|
fs.delete(_log.path)
|
||||||
|
|
||||||
logger.file, err_msg = fs.open(path, util.trinary(logger.mode == MODE.APPEND, "a", "w"))
|
_log.file, err_msg = fs.open(path, util.trinary(_log.mode == MODE.APPEND, "a", "w"))
|
||||||
|
|
||||||
if logger.file then
|
if _log.file then
|
||||||
logger.file.writeLine(os.date(TIME_FMT) .. WRN_TAG .. "init recycled log file")
|
_log.file.writeLine(os.date(TIME_FMT) .. WRN_TAG .. "init recycled log file")
|
||||||
logger.file.flush()
|
_log.file.flush()
|
||||||
else error("failed to setup the log file: " .. err_msg) end
|
else error("failed to setup the log file: " .. err_msg) end
|
||||||
else error("failed to make space for the log file, please delete unused files") end
|
else error("failed to make space for the log file, please delete unused files") end
|
||||||
else error("unexpected error setting up the log file: " .. err_msg) end
|
else error("unexpected error setting up the log file: " .. err_msg) end
|
||||||
end
|
end
|
||||||
|
|
||||||
logger.not_ready = false
|
_log.not_ready = false
|
||||||
end
|
end
|
||||||
|
|
||||||
-- close the log file handle
|
-- close the log file handle
|
||||||
function log.close() logger.file.close() end
|
function log.close() _log.file.close() end
|
||||||
|
|
||||||
-- direct dmesg output to a monitor/window
|
-- direct dmesg output to a monitor/window
|
||||||
---@param window Window window or terminal reference
|
---@param window Window window or terminal reference
|
||||||
function log.direct_dmesg(window) logger.dmesg_out = window end
|
function log.direct_dmesg(window) _log.dmesg_out = window end
|
||||||
|
|
||||||
-- dmesg style logging for boot because I like linux-y things
|
-- dmesg style logging for boot because I like linux-y things
|
||||||
---@param msg any message
|
---@param msg any message
|
||||||
@@ -142,7 +142,7 @@ function log.dmesg(msg, tag, tag_color)
|
|||||||
tag = util.strval(tag or "")
|
tag = util.strval(tag or "")
|
||||||
|
|
||||||
local t_stamp = string.format("%12.2f", os.clock())
|
local t_stamp = string.format("%12.2f", os.clock())
|
||||||
local out = logger.dmesg_out
|
local out = _log.dmesg_out
|
||||||
|
|
||||||
if out ~= nil then
|
if out ~= nil then
|
||||||
local out_w, out_h = out.getSize()
|
local out_w, out_h = out.getSize()
|
||||||
@@ -180,7 +180,7 @@ function log.dmesg(msg, tag, tag_color)
|
|||||||
if cur_y == out_h then
|
if cur_y == out_h then
|
||||||
out.scroll(1)
|
out.scroll(1)
|
||||||
out.setCursorPos(1, cur_y)
|
out.setCursorPos(1, cur_y)
|
||||||
logger.dmesg_scroll_count = logger.dmesg_scroll_count + 1
|
_log.dmesg_scroll_count = _log.dmesg_scroll_count + 1
|
||||||
else
|
else
|
||||||
out.setCursorPos(1, cur_y + 1)
|
out.setCursorPos(1, cur_y + 1)
|
||||||
end
|
end
|
||||||
@@ -216,7 +216,7 @@ function log.dmesg(msg, tag, tag_color)
|
|||||||
if cur_y == out_h then
|
if cur_y == out_h then
|
||||||
out.scroll(1)
|
out.scroll(1)
|
||||||
out.setCursorPos(1, cur_y)
|
out.setCursorPos(1, cur_y)
|
||||||
logger.dmesg_scroll_count = logger.dmesg_scroll_count + 1
|
_log.dmesg_scroll_count = _log.dmesg_scroll_count + 1
|
||||||
else
|
else
|
||||||
out.setCursorPos(1, cur_y + 1)
|
out.setCursorPos(1, cur_y + 1)
|
||||||
end
|
end
|
||||||
@@ -225,9 +225,9 @@ function log.dmesg(msg, tag, tag_color)
|
|||||||
out.write(lines[i])
|
out.write(lines[i])
|
||||||
end
|
end
|
||||||
|
|
||||||
logger.dmesg_restore_coord = { out.getCursorPos() }
|
_log.dmesg_restore_coord = { out.getCursorPos() }
|
||||||
|
|
||||||
_log{"[", t_stamp, "] [", tag, "] ", msg}
|
write_log{"[", t_stamp, "] [", tag, "] ", msg}
|
||||||
end
|
end
|
||||||
|
|
||||||
return ts_coord
|
return ts_coord
|
||||||
@@ -241,9 +241,9 @@ end
|
|||||||
---@return function update, function done
|
---@return function update, function done
|
||||||
function log.dmesg_working(msg, tag, tag_color)
|
function log.dmesg_working(msg, tag, tag_color)
|
||||||
local ts_coord = log.dmesg(msg, tag, tag_color)
|
local ts_coord = log.dmesg(msg, tag, tag_color)
|
||||||
local initial_scroll = logger.dmesg_scroll_count
|
local initial_scroll = _log.dmesg_scroll_count
|
||||||
|
|
||||||
local out = logger.dmesg_out
|
local out = _log.dmesg_out
|
||||||
local width = (ts_coord.x2 - ts_coord.x1) + 1
|
local width = (ts_coord.x2 - ts_coord.x1) + 1
|
||||||
|
|
||||||
if out ~= nil then
|
if out ~= nil then
|
||||||
@@ -252,7 +252,7 @@ function log.dmesg_working(msg, tag, tag_color)
|
|||||||
local counter = 0
|
local counter = 0
|
||||||
|
|
||||||
local function update(sec_remaining)
|
local function update(sec_remaining)
|
||||||
local new_y = ts_coord.y - (logger.dmesg_scroll_count - initial_scroll)
|
local new_y = ts_coord.y - (_log.dmesg_scroll_count - initial_scroll)
|
||||||
if new_y < 1 then return end
|
if new_y < 1 then return end
|
||||||
|
|
||||||
local time = util.sprintf("%ds", sec_remaining)
|
local time = util.sprintf("%ds", sec_remaining)
|
||||||
@@ -280,11 +280,11 @@ function log.dmesg_working(msg, tag, tag_color)
|
|||||||
|
|
||||||
counter = counter + 1
|
counter = counter + 1
|
||||||
|
|
||||||
out.setCursorPos(table.unpack(logger.dmesg_restore_coord))
|
out.setCursorPos(table.unpack(_log.dmesg_restore_coord))
|
||||||
end
|
end
|
||||||
|
|
||||||
local function done(ok)
|
local function done(ok)
|
||||||
local new_y = ts_coord.y - (logger.dmesg_scroll_count - initial_scroll)
|
local new_y = ts_coord.y - (_log.dmesg_scroll_count - initial_scroll)
|
||||||
if new_y < 1 then return end
|
if new_y < 1 then return end
|
||||||
|
|
||||||
out.setCursorPos(ts_coord.x1, new_y)
|
out.setCursorPos(ts_coord.x1, new_y)
|
||||||
@@ -299,7 +299,7 @@ function log.dmesg_working(msg, tag, tag_color)
|
|||||||
|
|
||||||
out.setTextColor(initial_color)
|
out.setTextColor(initial_color)
|
||||||
|
|
||||||
out.setCursorPos(table.unpack(logger.dmesg_restore_coord))
|
out.setCursorPos(table.unpack(_log.dmesg_restore_coord))
|
||||||
end
|
end
|
||||||
|
|
||||||
return update, done
|
return update, done
|
||||||
@@ -312,28 +312,28 @@ end
|
|||||||
---@param msg any message
|
---@param msg any message
|
||||||
---@param trace? boolean include file trace
|
---@param trace? boolean include file trace
|
||||||
function log.debug(msg, trace)
|
function log.debug(msg, trace)
|
||||||
if logger.debug then
|
if _log.debug then
|
||||||
if trace then
|
if trace then
|
||||||
local info = debug.getinfo(2)
|
local info = debug.getinfo(2)
|
||||||
|
|
||||||
if info.name ~= nil then
|
if info.name ~= nil then
|
||||||
_log{DBG_TAG, info.short_src, COLON, info.name, FUNC, info.currentline, ARROW, msg}
|
write_log{DBG_TAG, info.short_src, COLON, info.name, FUNC, info.currentline, ARROW, msg}
|
||||||
else
|
else
|
||||||
_log{DBG_TAG, info.short_src, COLON, info.currentline, ARROW, msg}
|
write_log{DBG_TAG, info.short_src, COLON, info.currentline, ARROW, msg}
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
_log{DBG_TAG, msg}
|
write_log{DBG_TAG, msg}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- log info messages
|
-- log info messages
|
||||||
---@param msg any message
|
---@param msg any message
|
||||||
function log.info(msg) _log{INF_TAG, msg} end
|
function log.info(msg) write_log{INF_TAG, msg} end
|
||||||
|
|
||||||
-- log warning messages
|
-- log warning messages
|
||||||
---@param msg any message
|
---@param msg any message
|
||||||
function log.warning(msg) _log{WRN_TAG, msg} end
|
function log.warning(msg) write_log{WRN_TAG, msg} end
|
||||||
|
|
||||||
-- log error messages
|
-- log error messages
|
||||||
---@param msg any message
|
---@param msg any message
|
||||||
@@ -343,17 +343,17 @@ function log.error(msg, trace)
|
|||||||
local info = debug.getinfo(2)
|
local info = debug.getinfo(2)
|
||||||
|
|
||||||
if info.name ~= nil then
|
if info.name ~= nil then
|
||||||
_log{ERR_TAG, info.short_src, COLON, info.name, FUNC, info.currentline, ARROW, msg}
|
write_log{ERR_TAG, info.short_src, COLON, info.name, FUNC, info.currentline, ARROW, msg}
|
||||||
else
|
else
|
||||||
_log{ERR_TAG, info.short_src, COLON, info.currentline, ARROW, msg}
|
write_log{ERR_TAG, info.short_src, COLON, info.currentline, ARROW, msg}
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
_log{ERR_TAG, msg}
|
write_log{ERR_TAG, msg}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- log fatal errors
|
-- log fatal errors
|
||||||
---@param msg any message
|
---@param msg any message
|
||||||
function log.fatal(msg) _log{FTL_TAG, msg} end
|
function log.fatal(msg) write_log{FTL_TAG, msg} end
|
||||||
|
|
||||||
return log
|
return log
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
--
|
--
|
||||||
-- Network Communications
|
-- Network Communications and Message Authentication
|
||||||
--
|
--
|
||||||
|
|
||||||
local comms = require("scada-common.comms")
|
local comms = require("scada-common.comms")
|
||||||
local log = require("scada-common.log")
|
local log = require("scada-common.log")
|
||||||
|
local ppm = require("scada-common.ppm")
|
||||||
local util = require("scada-common.util")
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
local md5 = require("lockbox.digest.md5")
|
local md5 = require("lockbox.digest.md5")
|
||||||
@@ -17,7 +18,7 @@ local array = require("lockbox.util.array")
|
|||||||
local network = {}
|
local network = {}
|
||||||
|
|
||||||
-- cryptography engine
|
-- cryptography engine
|
||||||
local c_eng = {
|
local _crypt = {
|
||||||
key = nil,
|
key = nil,
|
||||||
hmac = nil
|
hmac = nil
|
||||||
}
|
}
|
||||||
@@ -39,23 +40,23 @@ function network.init_mac(passkey)
|
|||||||
key_deriv.setPassword(passkey)
|
key_deriv.setPassword(passkey)
|
||||||
key_deriv.finish()
|
key_deriv.finish()
|
||||||
|
|
||||||
c_eng.key = array.fromHex(key_deriv.asHex())
|
_crypt.key = array.fromHex(key_deriv.asHex())
|
||||||
|
|
||||||
-- initialize HMAC
|
-- initialize HMAC
|
||||||
c_eng.hmac = hmac()
|
_crypt.hmac = hmac()
|
||||||
c_eng.hmac.setBlockSize(64)
|
_crypt.hmac.setBlockSize(64)
|
||||||
c_eng.hmac.setDigest(md5)
|
_crypt.hmac.setDigest(md5)
|
||||||
c_eng.hmac.setKey(c_eng.key)
|
_crypt.hmac.setKey(_crypt.key)
|
||||||
|
|
||||||
local init_time = util.time_ms() - start
|
local init_time = util.time_ms() - start
|
||||||
log.info("network.init_mac completed in " .. init_time .. "ms")
|
log.info("NET: network.init_mac() completed in " .. init_time .. "ms")
|
||||||
|
|
||||||
return init_time
|
return init_time
|
||||||
end
|
end
|
||||||
|
|
||||||
-- de-initialize message authentication system
|
-- de-initialize message authentication system
|
||||||
function network.deinit_mac()
|
function network.deinit_mac()
|
||||||
c_eng.key, c_eng.hmac = nil, nil
|
_crypt.key, _crypt.hmac = nil, nil
|
||||||
end
|
end
|
||||||
|
|
||||||
-- generate HMAC of message
|
-- generate HMAC of message
|
||||||
@@ -64,29 +65,41 @@ end
|
|||||||
local function compute_hmac(message)
|
local function compute_hmac(message)
|
||||||
-- local start = util.time_ms()
|
-- local start = util.time_ms()
|
||||||
|
|
||||||
c_eng.hmac.init()
|
_crypt.hmac.init()
|
||||||
c_eng.hmac.update(stream.fromString(message))
|
_crypt.hmac.update(stream.fromString(message))
|
||||||
c_eng.hmac.finish()
|
_crypt.hmac.finish()
|
||||||
|
|
||||||
local hash = c_eng.hmac.asHex()
|
local hash = _crypt.hmac.asHex()
|
||||||
|
|
||||||
-- log.debug("compute_hmac(): hmac-md5 = " .. util.strval(hash) .. " (took " .. (util.time_ms() - start) .. "ms)")
|
-- log.debug("NET: compute_hmac(): hmac-md5 = " .. util.strval(hash) .. " (took " .. (util.time_ms() - start) .. "ms)")
|
||||||
|
|
||||||
return hash
|
return hash
|
||||||
end
|
end
|
||||||
|
|
||||||
-- NIC: Network Interface Controller<br>
|
-- NIC: Network Interface Controller<br>
|
||||||
-- utilizes HMAC-MD5 for message authentication, if enabled
|
-- utilizes HMAC-MD5 for message authentication, if enabled and this is wireless
|
||||||
---@param modem Modem modem to use
|
---@param modem Modem|nil modem to use
|
||||||
function network.nic(modem)
|
function network.nic(modem)
|
||||||
local self = {
|
local self = {
|
||||||
connected = true, -- used to avoid costly MAC calculations if modem isn't even present
|
-- modem interface name
|
||||||
|
iface = "?",
|
||||||
|
-- phy name
|
||||||
|
name = "?",
|
||||||
|
-- used to quickly return out of tx/rx functions if there is nothing to do
|
||||||
|
connected = false,
|
||||||
|
-- used to avoid costly MAC calculations if not required
|
||||||
|
use_hash = false,
|
||||||
|
-- open channels
|
||||||
channels = {}
|
channels = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
---@class nic:Modem
|
---@class nic:Modem
|
||||||
local public = {}
|
local public = {}
|
||||||
|
|
||||||
|
-- get the phy name
|
||||||
|
---@nodiscard
|
||||||
|
function public.phy_name() return self.name end
|
||||||
|
|
||||||
-- check if this NIC has a connected modem
|
-- check if this NIC has a connected modem
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
function public.is_connected() return self.connected end
|
function public.is_connected() return self.connected end
|
||||||
@@ -95,9 +108,14 @@ function network.nic(modem)
|
|||||||
---@param reconnected_modem Modem
|
---@param reconnected_modem Modem
|
||||||
function public.connect(reconnected_modem)
|
function public.connect(reconnected_modem)
|
||||||
modem = reconnected_modem
|
modem = reconnected_modem
|
||||||
self.connected = true
|
|
||||||
|
|
||||||
-- open previously opened channels
|
self.iface = ppm.get_iface(modem)
|
||||||
|
self.name = util.c(util.trinary(modem.isWireless(), "WLAN_PHY", "ETH_PHY"), "{", self.iface, "}")
|
||||||
|
self.connected = true
|
||||||
|
self.use_hash = _crypt.hmac and modem.isWireless()
|
||||||
|
|
||||||
|
-- open only previously opened channels
|
||||||
|
modem.closeAll()
|
||||||
for _, channel in ipairs(self.channels) do
|
for _, channel in ipairs(self.channels) do
|
||||||
modem.open(channel)
|
modem.open(channel)
|
||||||
end
|
end
|
||||||
@@ -117,13 +135,13 @@ function network.nic(modem)
|
|||||||
function public.is_modem(device) return device == modem end
|
function public.is_modem(device) return device == modem end
|
||||||
|
|
||||||
-- wrap modem functions, then create custom functions
|
-- wrap modem functions, then create custom functions
|
||||||
public.connect(modem)
|
if modem then public.connect(modem) end
|
||||||
|
|
||||||
-- open a channel on the modem<br>
|
-- open a channel on the modem<br>
|
||||||
-- if disconnected *after* opening, previousy opened channels will be re-opened on reconnection
|
-- if disconnected *after* opening, previousy opened channels will be re-opened on reconnection
|
||||||
---@param channel integer
|
---@param channel integer
|
||||||
function public.open(channel)
|
function public.open(channel)
|
||||||
modem.open(channel)
|
if modem then modem.open(channel) end
|
||||||
|
|
||||||
local already_open = false
|
local already_open = false
|
||||||
for i = 1, #self.channels do
|
for i = 1, #self.channels do
|
||||||
@@ -141,7 +159,7 @@ function network.nic(modem)
|
|||||||
-- close a channel on the modem
|
-- close a channel on the modem
|
||||||
---@param channel integer
|
---@param channel integer
|
||||||
function public.close(channel)
|
function public.close(channel)
|
||||||
modem.close(channel)
|
if modem then modem.close(channel) end
|
||||||
|
|
||||||
for i = 1, #self.channels do
|
for i = 1, #self.channels do
|
||||||
if self.channels[i] == channel then
|
if self.channels[i] == channel then
|
||||||
@@ -153,7 +171,7 @@ function network.nic(modem)
|
|||||||
|
|
||||||
-- close all channels on the modem
|
-- close all channels on the modem
|
||||||
function public.closeAll()
|
function public.closeAll()
|
||||||
modem.closeAll()
|
if modem then modem.closeAll() end
|
||||||
self.channels = {}
|
self.channels = {}
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -165,17 +183,20 @@ function network.nic(modem)
|
|||||||
if self.connected then
|
if self.connected then
|
||||||
local tx_packet = packet ---@type authd_packet|scada_packet
|
local tx_packet = packet ---@type authd_packet|scada_packet
|
||||||
|
|
||||||
if c_eng.hmac ~= nil then
|
if self.use_hash then
|
||||||
-- local start = util.time_ms()
|
-- local start = util.time_ms()
|
||||||
tx_packet = comms.authd_packet()
|
tx_packet = comms.authd_packet()
|
||||||
|
|
||||||
---@cast tx_packet authd_packet
|
---@cast tx_packet authd_packet
|
||||||
tx_packet.make(packet, compute_hmac)
|
tx_packet.make(packet, compute_hmac)
|
||||||
|
|
||||||
-- log.debug("network.modem.transmit: data processing took " .. (util.time_ms() - start) .. "ms")
|
-- log.debug("NET: network.modem.transmit(): data processing took " .. (util.time_ms() - start) .. "ms")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@diagnostic disable-next-line: need-check-nil
|
||||||
modem.transmit(dest_channel, local_channel, tx_packet.raw_sendable())
|
modem.transmit(dest_channel, local_channel, tx_packet.raw_sendable())
|
||||||
|
else
|
||||||
|
log.debug("NET: network.transmit() tx dropped, link is down")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -190,10 +211,10 @@ function network.nic(modem)
|
|||||||
function public.receive(side, sender, reply_to, message, distance)
|
function public.receive(side, sender, reply_to, message, distance)
|
||||||
local packet = nil
|
local packet = nil
|
||||||
|
|
||||||
if self.connected then
|
if self.connected and side == self.iface then
|
||||||
local s_packet = comms.scada_packet()
|
local s_packet = comms.scada_packet()
|
||||||
|
|
||||||
if c_eng.hmac ~= nil then
|
if self.use_hash then
|
||||||
-- parse packet as an authenticated SCADA packet
|
-- parse packet as an authenticated SCADA packet
|
||||||
local a_packet = comms.authd_packet()
|
local a_packet = comms.authd_packet()
|
||||||
a_packet.receive(side, sender, reply_to, message, distance)
|
a_packet.receive(side, sender, reply_to, message, distance)
|
||||||
@@ -206,10 +227,10 @@ function network.nic(modem)
|
|||||||
local computed_hmac = compute_hmac(textutils.serialize(s_packet.raw_header(), { allow_repetitions = true, compact = true }))
|
local computed_hmac = compute_hmac(textutils.serialize(s_packet.raw_header(), { allow_repetitions = true, compact = true }))
|
||||||
|
|
||||||
if a_packet.mac() == computed_hmac then
|
if a_packet.mac() == computed_hmac then
|
||||||
-- log.debug("network.modem.receive: HMAC verified in " .. (util.time_ms() - start) .. "ms")
|
-- log.debug("NET: network.modem.receive(): HMAC verified in " .. (util.time_ms() - start) .. "ms")
|
||||||
s_packet.stamp_authenticated()
|
s_packet.stamp_authenticated()
|
||||||
else
|
else
|
||||||
-- log.debug("network.modem.receive: HMAC failed verification in " .. (util.time_ms() - start) .. "ms")
|
-- log.debug("NET: network.modem.receive(): HMAC failed verification in " .. (util.time_ms() - start) .. "ms")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ ppm.VIRTUAL_DEVICE_TYPE = VIRTUAL_DEVICE_TYPE
|
|||||||
|
|
||||||
local REPORT_FREQUENCY = 20 -- log every 20 faults per function
|
local REPORT_FREQUENCY = 20 -- log every 20 faults per function
|
||||||
|
|
||||||
local ppm_sys = {
|
local _ppm = {
|
||||||
mounts = {}, ---@type { [string]: ppm_entry }
|
mounts = {}, ---@type { [string]: ppm_entry }
|
||||||
next_vid = 0,
|
next_vid = 0,
|
||||||
auto_cf = false,
|
auto_cf = false,
|
||||||
@@ -66,7 +66,7 @@ local function peri_init(iface)
|
|||||||
if status then
|
if status then
|
||||||
-- auto fault clear
|
-- auto fault clear
|
||||||
if self.auto_cf then self.faulted = false end
|
if self.auto_cf then self.faulted = false end
|
||||||
if ppm_sys.auto_cf then ppm_sys.faulted = false end
|
if _ppm.auto_cf then _ppm.faulted = false end
|
||||||
|
|
||||||
self.fault_counts[key] = 0
|
self.fault_counts[key] = 0
|
||||||
|
|
||||||
@@ -78,10 +78,10 @@ local function peri_init(iface)
|
|||||||
self.faulted = true
|
self.faulted = true
|
||||||
self.last_fault = result
|
self.last_fault = result
|
||||||
|
|
||||||
ppm_sys.faulted = true
|
_ppm.faulted = true
|
||||||
ppm_sys.last_fault = result
|
_ppm.last_fault = result
|
||||||
|
|
||||||
if not ppm_sys.mute and (self.fault_counts[key] % REPORT_FREQUENCY == 0) then
|
if not _ppm.mute and (self.fault_counts[key] % REPORT_FREQUENCY == 0) then
|
||||||
local count_str = ""
|
local count_str = ""
|
||||||
if self.fault_counts[key] > 0 then
|
if self.fault_counts[key] > 0 then
|
||||||
count_str = " [" .. self.fault_counts[key] .. " total faults]"
|
count_str = " [" .. self.fault_counts[key] .. " total faults]"
|
||||||
@@ -92,7 +92,7 @@ local function peri_init(iface)
|
|||||||
|
|
||||||
self.fault_counts[key] = self.fault_counts[key] + 1
|
self.fault_counts[key] = self.fault_counts[key] + 1
|
||||||
|
|
||||||
if result == "Terminated" then ppm_sys.terminate = true end
|
if result == "Terminated" then _ppm.terminate = true end
|
||||||
|
|
||||||
return ACCESS_FAULT, result
|
return ACCESS_FAULT, result
|
||||||
end
|
end
|
||||||
@@ -159,10 +159,10 @@ local function peri_init(iface)
|
|||||||
self.faulted = true
|
self.faulted = true
|
||||||
self.last_fault = UNDEFINED_FIELD
|
self.last_fault = UNDEFINED_FIELD
|
||||||
|
|
||||||
ppm_sys.faulted = true
|
_ppm.faulted = true
|
||||||
ppm_sys.last_fault = UNDEFINED_FIELD
|
_ppm.last_fault = UNDEFINED_FIELD
|
||||||
|
|
||||||
if not ppm_sys.mute and (self.fault_counts[key] % REPORT_FREQUENCY == 0) then
|
if not _ppm.mute and (self.fault_counts[key] % REPORT_FREQUENCY == 0) then
|
||||||
local count_str = ""
|
local count_str = ""
|
||||||
if self.fault_counts[key] > 0 then
|
if self.fault_counts[key] > 0 then
|
||||||
count_str = " [" .. self.fault_counts[key] .. " total calls]"
|
count_str = " [" .. self.fault_counts[key] .. " total calls]"
|
||||||
@@ -193,35 +193,35 @@ end
|
|||||||
-- REPORTING --
|
-- REPORTING --
|
||||||
|
|
||||||
-- silence error prints
|
-- silence error prints
|
||||||
function ppm.disable_reporting() ppm_sys.mute = true end
|
function ppm.disable_reporting() _ppm.mute = true end
|
||||||
|
|
||||||
-- allow error prints
|
-- allow error prints
|
||||||
function ppm.enable_reporting() ppm_sys.mute = false end
|
function ppm.enable_reporting() _ppm.mute = false end
|
||||||
|
|
||||||
-- FAULT MEMORY --
|
-- FAULT MEMORY --
|
||||||
|
|
||||||
-- enable automatically clearing fault flag
|
-- enable automatically clearing fault flag
|
||||||
function ppm.enable_afc() ppm_sys.auto_cf = true end
|
function ppm.enable_afc() _ppm.auto_cf = true end
|
||||||
|
|
||||||
-- disable automatically clearing fault flag
|
-- disable automatically clearing fault flag
|
||||||
function ppm.disable_afc() ppm_sys.auto_cf = false end
|
function ppm.disable_afc() _ppm.auto_cf = false end
|
||||||
|
|
||||||
-- clear fault flag
|
-- clear fault flag
|
||||||
function ppm.clear_fault() ppm_sys.faulted = false end
|
function ppm.clear_fault() _ppm.faulted = false end
|
||||||
|
|
||||||
-- check fault flag
|
-- check fault flag
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
function ppm.is_faulted() return ppm_sys.faulted end
|
function ppm.is_faulted() return _ppm.faulted end
|
||||||
|
|
||||||
-- get the last fault message
|
-- get the last fault message
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
function ppm.get_last_fault() return ppm_sys.last_fault end
|
function ppm.get_last_fault() return _ppm.last_fault end
|
||||||
|
|
||||||
-- TERMINATION --
|
-- TERMINATION --
|
||||||
|
|
||||||
-- if a caught error was a termination request
|
-- if a caught error was a termination request
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
function ppm.should_terminate() return ppm_sys.terminate end
|
function ppm.should_terminate() return _ppm.terminate end
|
||||||
|
|
||||||
-- MOUNTING --
|
-- MOUNTING --
|
||||||
|
|
||||||
@@ -229,12 +229,12 @@ function ppm.should_terminate() return ppm_sys.terminate end
|
|||||||
function ppm.mount_all()
|
function ppm.mount_all()
|
||||||
local ifaces = peripheral.getNames()
|
local ifaces = peripheral.getNames()
|
||||||
|
|
||||||
ppm_sys.mounts = {}
|
_ppm.mounts = {}
|
||||||
|
|
||||||
for i = 1, #ifaces do
|
for i = 1, #ifaces do
|
||||||
ppm_sys.mounts[ifaces[i]] = peri_init(ifaces[i])
|
_ppm.mounts[ifaces[i]] = peri_init(ifaces[i])
|
||||||
|
|
||||||
log.info(util.c("PPM: found a ", ppm_sys.mounts[ifaces[i]].type, " (", ifaces[i], ")"))
|
log.info(util.c("PPM: found a ", _ppm.mounts[ifaces[i]].type, " (", ifaces[i], ")"))
|
||||||
end
|
end
|
||||||
|
|
||||||
if #ifaces == 0 then
|
if #ifaces == 0 then
|
||||||
@@ -253,10 +253,10 @@ function ppm.mount(iface)
|
|||||||
|
|
||||||
for i = 1, #ifaces do
|
for i = 1, #ifaces do
|
||||||
if iface == ifaces[i] then
|
if iface == ifaces[i] then
|
||||||
ppm_sys.mounts[iface] = peri_init(iface)
|
_ppm.mounts[iface] = peri_init(iface)
|
||||||
|
|
||||||
pm_type = ppm_sys.mounts[iface].type
|
pm_type = _ppm.mounts[iface].type
|
||||||
pm_dev = ppm_sys.mounts[iface].dev
|
pm_dev = _ppm.mounts[iface].dev
|
||||||
|
|
||||||
log.info(util.c("PPM: mount(", iface, ") -> found a ", pm_type))
|
log.info(util.c("PPM: mount(", iface, ") -> found a ", pm_type))
|
||||||
break
|
break
|
||||||
@@ -278,12 +278,12 @@ function ppm.remount(iface)
|
|||||||
for i = 1, #ifaces do
|
for i = 1, #ifaces do
|
||||||
if iface == ifaces[i] then
|
if iface == ifaces[i] then
|
||||||
log.info(util.c("PPM: remount(", iface, ") -> is a ", pm_type))
|
log.info(util.c("PPM: remount(", iface, ") -> is a ", pm_type))
|
||||||
ppm.unmount(ppm_sys.mounts[iface].dev)
|
ppm.unmount(_ppm.mounts[iface].dev)
|
||||||
|
|
||||||
ppm_sys.mounts[iface] = peri_init(iface)
|
_ppm.mounts[iface] = peri_init(iface)
|
||||||
|
|
||||||
pm_type = ppm_sys.mounts[iface].type
|
pm_type = _ppm.mounts[iface].type
|
||||||
pm_dev = ppm_sys.mounts[iface].dev
|
pm_dev = _ppm.mounts[iface].dev
|
||||||
|
|
||||||
log.info(util.c("PPM: remount(", iface, ") -> remounted a ", pm_type))
|
log.info(util.c("PPM: remount(", iface, ") -> remounted a ", pm_type))
|
||||||
break
|
break
|
||||||
@@ -293,28 +293,28 @@ function ppm.remount(iface)
|
|||||||
return pm_type, pm_dev
|
return pm_type, pm_dev
|
||||||
end
|
end
|
||||||
|
|
||||||
-- mount a virtual, placeholder device (specifically designed for RTU startup with missing devices)
|
-- mount a virtual placeholder device
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
---@return string type, table device
|
---@return string type, table device
|
||||||
function ppm.mount_virtual()
|
function ppm.mount_virtual()
|
||||||
local iface = "ppm_vdev_" .. ppm_sys.next_vid
|
local iface = "ppm_vdev_" .. _ppm.next_vid
|
||||||
|
|
||||||
ppm_sys.mounts[iface] = peri_init("__virtual__")
|
_ppm.mounts[iface] = peri_init("__virtual__")
|
||||||
ppm_sys.next_vid = ppm_sys.next_vid + 1
|
_ppm.next_vid = _ppm.next_vid + 1
|
||||||
|
|
||||||
log.info(util.c("PPM: mount_virtual() -> allocated new virtual device ", iface))
|
log.info(util.c("PPM: mount_virtual() -> allocated new virtual device ", iface))
|
||||||
|
|
||||||
return ppm_sys.mounts[iface].type, ppm_sys.mounts[iface].dev
|
return _ppm.mounts[iface].type, _ppm.mounts[iface].dev
|
||||||
end
|
end
|
||||||
|
|
||||||
-- manually unmount a peripheral from the PPM
|
-- manually unmount a peripheral from the PPM
|
||||||
---@param device table device table
|
---@param device table device table
|
||||||
function ppm.unmount(device)
|
function ppm.unmount(device)
|
||||||
if device then
|
if device then
|
||||||
for iface, data in pairs(ppm_sys.mounts) do
|
for iface, data in pairs(_ppm.mounts) do
|
||||||
if data.dev == device then
|
if data.dev == device then
|
||||||
log.warning(util.c("PPM: manually unmounted ", data.type, " mounted to ", iface))
|
log.warning(util.c("PPM: manually unmounted ", data.type, " mounted to ", iface))
|
||||||
ppm_sys.mounts[iface] = nil
|
_ppm.mounts[iface] = nil
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -330,7 +330,7 @@ function ppm.handle_unmount(iface)
|
|||||||
local pm_type = nil
|
local pm_type = nil
|
||||||
|
|
||||||
-- what got disconnected?
|
-- what got disconnected?
|
||||||
local lost_dev = ppm_sys.mounts[iface]
|
local lost_dev = _ppm.mounts[iface]
|
||||||
|
|
||||||
if lost_dev then
|
if lost_dev then
|
||||||
pm_type = lost_dev.type
|
pm_type = lost_dev.type
|
||||||
@@ -341,18 +341,18 @@ function ppm.handle_unmount(iface)
|
|||||||
log.error(util.c("PPM: lost device unknown to the PPM mounted to ", iface))
|
log.error(util.c("PPM: lost device unknown to the PPM mounted to ", iface))
|
||||||
end
|
end
|
||||||
|
|
||||||
ppm_sys.mounts[iface] = nil
|
_ppm.mounts[iface] = nil
|
||||||
|
|
||||||
return pm_type, pm_dev
|
return pm_type, pm_dev
|
||||||
end
|
end
|
||||||
|
|
||||||
-- log all mounts, to be used if `ppm.mount_all` is called before logging is ready
|
-- log all mounts, to be used if `ppm.mount_all` is called before logging is ready
|
||||||
function ppm.log_mounts()
|
function ppm.log_mounts()
|
||||||
for iface, mount in pairs(ppm_sys.mounts) do
|
for iface, mount in pairs(_ppm.mounts) do
|
||||||
log.info(util.c("PPM: had found a ", mount.type, " (", iface, ")"))
|
log.info(util.c("PPM: had found a ", mount.type, " (", iface, ")"))
|
||||||
end
|
end
|
||||||
|
|
||||||
if util.table_len(ppm_sys.mounts) == 0 then
|
if util.table_len(_ppm.mounts) == 0 then
|
||||||
log.warning("PPM: no devices had been found")
|
log.warning("PPM: no devices had been found")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -369,7 +369,7 @@ function ppm.list_avail() return peripheral.getNames() end
|
|||||||
---@return { [string]: ppm_entry } mounts
|
---@return { [string]: ppm_entry } mounts
|
||||||
function ppm.list_mounts()
|
function ppm.list_mounts()
|
||||||
local list = {}
|
local list = {}
|
||||||
for k, v in pairs(ppm_sys.mounts) do list[k] = v end
|
for k, v in pairs(_ppm.mounts) do list[k] = v end
|
||||||
return list
|
return list
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -379,7 +379,7 @@ end
|
|||||||
---@return string|nil iface CC peripheral interface
|
---@return string|nil iface CC peripheral interface
|
||||||
function ppm.get_iface(device)
|
function ppm.get_iface(device)
|
||||||
if device then
|
if device then
|
||||||
for iface, data in pairs(ppm_sys.mounts) do
|
for iface, data in pairs(_ppm.mounts) do
|
||||||
if data.dev == device then return iface end
|
if data.dev == device then return iface end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -392,8 +392,8 @@ end
|
|||||||
---@param iface string CC peripheral interface
|
---@param iface string CC peripheral interface
|
||||||
---@return { [string]: function }|nil device function table
|
---@return { [string]: function }|nil device function table
|
||||||
function ppm.get_periph(iface)
|
function ppm.get_periph(iface)
|
||||||
if ppm_sys.mounts[iface] then
|
if _ppm.mounts[iface] then
|
||||||
return ppm_sys.mounts[iface].dev
|
return _ppm.mounts[iface].dev
|
||||||
else return nil end
|
else return nil end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -402,20 +402,20 @@ end
|
|||||||
---@param iface string CC peripheral interface
|
---@param iface string CC peripheral interface
|
||||||
---@return string|nil type
|
---@return string|nil type
|
||||||
function ppm.get_type(iface)
|
function ppm.get_type(iface)
|
||||||
if ppm_sys.mounts[iface] then
|
if _ppm.mounts[iface] then
|
||||||
return ppm_sys.mounts[iface].type
|
return _ppm.mounts[iface].type
|
||||||
else return nil end
|
else return nil end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- get all mounted peripherals by type
|
-- get all mounted peripherals by type
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
---@param name string type name
|
---@param type string type name
|
||||||
---@return table devices device function tables
|
---@return table devices device function tables
|
||||||
function ppm.get_all_devices(name)
|
function ppm.get_all_devices(type)
|
||||||
local devices = {}
|
local devices = {}
|
||||||
|
|
||||||
for _, data in pairs(ppm_sys.mounts) do
|
for _, data in pairs(_ppm.mounts) do
|
||||||
if data.type == name then
|
if data.type == type then
|
||||||
table.insert(devices, data.dev)
|
table.insert(devices, data.dev)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -430,7 +430,7 @@ end
|
|||||||
function ppm.get_device(name)
|
function ppm.get_device(name)
|
||||||
local device = nil
|
local device = nil
|
||||||
|
|
||||||
for _, data in pairs(ppm_sys.mounts) do
|
for _, data in pairs(_ppm.mounts) do
|
||||||
if data.type == name then
|
if data.type == name then
|
||||||
device = data.dev
|
device = data.dev
|
||||||
break
|
break
|
||||||
@@ -447,22 +447,49 @@ end
|
|||||||
---@return table|nil reactor function table
|
---@return table|nil reactor function table
|
||||||
function ppm.get_fission_reactor() return ppm.get_device("fissionReactorLogicAdapter") end
|
function ppm.get_fission_reactor() return ppm.get_device("fissionReactorLogicAdapter") end
|
||||||
|
|
||||||
|
-- get a modem by name
|
||||||
|
---@nodiscard
|
||||||
|
---@param iface string CC peripheral interface
|
||||||
|
---@return Modem|nil modem function table
|
||||||
|
function ppm.get_modem(iface)
|
||||||
|
local modem = nil
|
||||||
|
local device = _ppm.mounts[iface]
|
||||||
|
|
||||||
|
if device and device.type == "modem" then modem = device.dev end
|
||||||
|
|
||||||
|
return modem
|
||||||
|
end
|
||||||
|
|
||||||
-- get the wireless modem (if multiple, returns the first)<br>
|
-- get the wireless modem (if multiple, returns the first)<br>
|
||||||
-- if this is in a CraftOS emulated environment, wired modems will be used instead
|
-- if this is in a CraftOS emulated environment, wired modems will be used instead
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
---@return Modem|nil modem function table
|
---@return Modem|nil modem, string|nil iface
|
||||||
function ppm.get_wireless_modem()
|
function ppm.get_wireless_modem()
|
||||||
local w_modem = nil
|
local w_modem, w_iface = nil, nil
|
||||||
local emulated_env = periphemu ~= nil
|
local emulated_env = periphemu ~= nil
|
||||||
|
|
||||||
for _, device in pairs(ppm_sys.mounts) do
|
for iface, device in pairs(_ppm.mounts) do
|
||||||
if device.type == "modem" and (emulated_env or device.dev.isWireless()) then
|
if device.type == "modem" and (emulated_env or device.dev.isWireless()) then
|
||||||
|
w_iface = iface
|
||||||
w_modem = device.dev
|
w_modem = device.dev
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return w_modem
|
return w_modem, w_iface
|
||||||
|
end
|
||||||
|
|
||||||
|
-- list all connected wired modems
|
||||||
|
---@nodiscard
|
||||||
|
---@return { [string]: ppm_entry } modems
|
||||||
|
function ppm.get_wired_modem_list()
|
||||||
|
local list = {}
|
||||||
|
|
||||||
|
for iface, device in pairs(_ppm.mounts) do
|
||||||
|
if device.type == "modem" and not device.dev.isWireless() then list[iface] = device end
|
||||||
|
end
|
||||||
|
|
||||||
|
return list
|
||||||
end
|
end
|
||||||
|
|
||||||
-- list all connected monitors
|
-- list all connected monitors
|
||||||
@@ -471,7 +498,7 @@ end
|
|||||||
function ppm.get_monitor_list()
|
function ppm.get_monitor_list()
|
||||||
local list = {}
|
local list = {}
|
||||||
|
|
||||||
for iface, device in pairs(ppm_sys.mounts) do
|
for iface, device in pairs(_ppm.mounts) do
|
||||||
if device.type == "monitor" then list[iface] = device end
|
if device.type == "monitor" then list[iface] = device end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -53,12 +53,12 @@ function psil.create()
|
|||||||
if ic[key] == nil then alloc(key) end
|
if ic[key] == nil then alloc(key) end
|
||||||
|
|
||||||
if ic[key].value ~= value then
|
if ic[key].value ~= value then
|
||||||
|
ic[key].value = value
|
||||||
|
|
||||||
for i = 1, #ic[key].subscribers do
|
for i = 1, #ic[key].subscribers do
|
||||||
ic[key].subscribers[i].notify(value)
|
ic[key].subscribers[i].notify(value)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
ic[key].value = value
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- publish a toggled boolean value to a given key, passing it to all subscribers if it has changed<br>
|
-- publish a toggled boolean value to a given key, passing it to all subscribers if it has changed<br>
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ function types.new_zero_coordinate() return { x = 0, y = 0, z = 0 } end
|
|||||||
---@field type RTU_UNIT_TYPE
|
---@field type RTU_UNIT_TYPE
|
||||||
---@field index integer|false
|
---@field index integer|false
|
||||||
---@field reactor integer
|
---@field reactor integer
|
||||||
---@field rsio IO_PORT[]|nil
|
---@field rs_conns IO_PORT[][]|nil
|
||||||
|
|
||||||
-- create a new reactor database
|
-- create a new reactor database
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
@@ -212,6 +212,13 @@ end
|
|||||||
|
|
||||||
--#region ENUMERATION TYPES
|
--#region ENUMERATION TYPES
|
||||||
|
|
||||||
|
---@enum LISTEN_MODE
|
||||||
|
types.LISTEN_MODE = {
|
||||||
|
WIRELESS = 1,
|
||||||
|
WIRED = 2,
|
||||||
|
ALL = 3
|
||||||
|
}
|
||||||
|
|
||||||
---@enum TEMP_SCALE
|
---@enum TEMP_SCALE
|
||||||
types.TEMP_SCALE = {
|
types.TEMP_SCALE = {
|
||||||
KELVIN = 1,
|
KELVIN = 1,
|
||||||
@@ -465,7 +472,8 @@ types.ALARM = {
|
|||||||
ReactorHighWaste = 9,
|
ReactorHighWaste = 9,
|
||||||
RPSTransient = 10,
|
RPSTransient = 10,
|
||||||
RCSTransient = 11,
|
RCSTransient = 11,
|
||||||
TurbineTrip = 12
|
TurbineTrip = 12,
|
||||||
|
FacilityRadiation = 13
|
||||||
}
|
}
|
||||||
|
|
||||||
types.ALARM_NAMES = {
|
types.ALARM_NAMES = {
|
||||||
@@ -480,7 +488,8 @@ types.ALARM_NAMES = {
|
|||||||
"ReactorHighWaste",
|
"ReactorHighWaste",
|
||||||
"RPSTransient",
|
"RPSTransient",
|
||||||
"RCSTransient",
|
"RCSTransient",
|
||||||
"TurbineTrip"
|
"TurbineTrip",
|
||||||
|
"FacilityRadiation"
|
||||||
}
|
}
|
||||||
|
|
||||||
---@enum ALARM_PRIORITY
|
---@enum ALARM_PRIORITY
|
||||||
@@ -559,7 +568,7 @@ types.ALARM_STATE_NAMES = {
|
|||||||
---| "websocket_failure"
|
---| "websocket_failure"
|
||||||
---| "websocket_message"
|
---| "websocket_message"
|
||||||
---| "websocket_success"
|
---| "websocket_success"
|
||||||
---| "clock_start" (custom)
|
---| "conn_test_complete" (custom)
|
||||||
|
|
||||||
---@alias fluid
|
---@alias fluid
|
||||||
---| "mekanism:empty_gas"
|
---| "mekanism:empty_gas"
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ local t_pack = table.pack
|
|||||||
local util = {}
|
local util = {}
|
||||||
|
|
||||||
-- scada-common version
|
-- scada-common version
|
||||||
util.version = "1.4.12"
|
util.version = "1.6.0"
|
||||||
|
|
||||||
util.TICK_TIME_S = 0.05
|
util.TICK_TIME_S = 0.05
|
||||||
util.TICK_TIME_MS = 50
|
util.TICK_TIME_MS = 50
|
||||||
|
|||||||
137
supervisor/alarm_ctl.lua
Normal file
137
supervisor/alarm_ctl.lua
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
local log = require("scada-common.log")
|
||||||
|
local types = require("scada-common.types")
|
||||||
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
|
local ALARM_STATE = types.ALARM_STATE
|
||||||
|
|
||||||
|
---@class alarm_def
|
||||||
|
---@field state ALARM_INT_STATE internal alarm state
|
||||||
|
---@field trip_time integer time (ms) when first tripped
|
||||||
|
---@field hold_time integer time (s) to hold before tripping
|
||||||
|
---@field id ALARM alarm ID
|
||||||
|
---@field tier integer alarm urgency tier (0 = highest)
|
||||||
|
|
||||||
|
local AISTATE_NAMES = {
|
||||||
|
"INACTIVE",
|
||||||
|
"TRIPPING",
|
||||||
|
"TRIPPED",
|
||||||
|
"ACKED",
|
||||||
|
"RING_BACK",
|
||||||
|
"RING_BACK_TRIPPING"
|
||||||
|
}
|
||||||
|
|
||||||
|
---@enum ALARM_INT_STATE
|
||||||
|
local AISTATE = {
|
||||||
|
INACTIVE = 1,
|
||||||
|
TRIPPING = 2,
|
||||||
|
TRIPPED = 3,
|
||||||
|
ACKED = 4,
|
||||||
|
RING_BACK = 5,
|
||||||
|
RING_BACK_TRIPPING = 6
|
||||||
|
}
|
||||||
|
|
||||||
|
local alarm_ctl = {}
|
||||||
|
|
||||||
|
alarm_ctl.AISTATE = AISTATE
|
||||||
|
alarm_ctl.AISTATE_NAMES = AISTATE_NAMES
|
||||||
|
|
||||||
|
-- update an alarm state based on its current status and if it is tripped
|
||||||
|
---@param caller_tag string tag to use in log messages
|
||||||
|
---@param alarm_states { [ALARM]: ALARM_STATE } unit instance
|
||||||
|
---@param tripped boolean if the alarm condition is sti ll active
|
||||||
|
---@param alarm alarm_def alarm table
|
||||||
|
---@param no_ring_back boolean? true to skip the ring back state, returning to inactive instead
|
||||||
|
---@return boolean new_trip if the alarm just changed to being tripped
|
||||||
|
function alarm_ctl.update_alarm_state(caller_tag, alarm_states, tripped, alarm, no_ring_back)
|
||||||
|
local int_state = alarm.state
|
||||||
|
local ext_state = alarm_states[alarm.id]
|
||||||
|
|
||||||
|
-- alarm inactive
|
||||||
|
if int_state == AISTATE.INACTIVE then
|
||||||
|
if tripped then
|
||||||
|
alarm.trip_time = util.time_ms()
|
||||||
|
if alarm.hold_time > 0 then
|
||||||
|
alarm.state = AISTATE.TRIPPING
|
||||||
|
alarm_states[alarm.id] = ALARM_STATE.INACTIVE
|
||||||
|
else
|
||||||
|
alarm.state = AISTATE.TRIPPED
|
||||||
|
alarm_states[alarm.id] = ALARM_STATE.TRIPPED
|
||||||
|
log.info(util.c(caller_tag, " ALARM ", alarm.id, " (", types.ALARM_NAMES[alarm.id], "): TRIPPED [PRIORITY ",
|
||||||
|
types.ALARM_PRIORITY_NAMES[alarm.tier],"]"))
|
||||||
|
end
|
||||||
|
else
|
||||||
|
alarm.trip_time = util.time_ms()
|
||||||
|
alarm_states[alarm.id] = ALARM_STATE.INACTIVE
|
||||||
|
end
|
||||||
|
-- alarm condition met, but not yet for required hold time
|
||||||
|
elseif (int_state == AISTATE.TRIPPING) or (int_state == AISTATE.RING_BACK_TRIPPING) then
|
||||||
|
if tripped then
|
||||||
|
local elapsed = util.time_ms() - alarm.trip_time
|
||||||
|
if elapsed > (alarm.hold_time * 1000) then
|
||||||
|
alarm.state = AISTATE.TRIPPED
|
||||||
|
alarm_states[alarm.id] = ALARM_STATE.TRIPPED
|
||||||
|
log.info(util.c(caller_tag, " ALARM ", alarm.id, " (", types.ALARM_NAMES[alarm.id], "): TRIPPED [PRIORITY ",
|
||||||
|
types.ALARM_PRIORITY_NAMES[alarm.tier],"]"))
|
||||||
|
end
|
||||||
|
elseif int_state == AISTATE.RING_BACK_TRIPPING then
|
||||||
|
alarm.trip_time = 0
|
||||||
|
alarm.state = AISTATE.RING_BACK
|
||||||
|
alarm_states[alarm.id] = ALARM_STATE.RING_BACK
|
||||||
|
else
|
||||||
|
alarm.trip_time = 0
|
||||||
|
alarm.state = AISTATE.INACTIVE
|
||||||
|
alarm_states[alarm.id] = ALARM_STATE.INACTIVE
|
||||||
|
end
|
||||||
|
-- alarm tripped and alarming
|
||||||
|
elseif int_state == AISTATE.TRIPPED then
|
||||||
|
if tripped then
|
||||||
|
if ext_state == ALARM_STATE.ACKED then
|
||||||
|
-- was acked by coordinator
|
||||||
|
alarm.state = AISTATE.ACKED
|
||||||
|
end
|
||||||
|
elseif no_ring_back then
|
||||||
|
alarm.state = AISTATE.INACTIVE
|
||||||
|
alarm_states[alarm.id] = ALARM_STATE.INACTIVE
|
||||||
|
else
|
||||||
|
alarm.state = AISTATE.RING_BACK
|
||||||
|
alarm_states[alarm.id] = ALARM_STATE.RING_BACK
|
||||||
|
end
|
||||||
|
-- alarm acknowledged but still tripped
|
||||||
|
elseif int_state == AISTATE.ACKED then
|
||||||
|
if not tripped then
|
||||||
|
if no_ring_back then
|
||||||
|
alarm.state = AISTATE.INACTIVE
|
||||||
|
alarm_states[alarm.id] = ALARM_STATE.INACTIVE
|
||||||
|
else
|
||||||
|
alarm.state = AISTATE.RING_BACK
|
||||||
|
alarm_states[alarm.id] = ALARM_STATE.RING_BACK
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- alarm no longer tripped, operator must reset to clear
|
||||||
|
elseif int_state == AISTATE.RING_BACK then
|
||||||
|
if tripped then
|
||||||
|
alarm.trip_time = util.time_ms()
|
||||||
|
if alarm.hold_time > 0 then
|
||||||
|
alarm.state = AISTATE.RING_BACK_TRIPPING
|
||||||
|
else
|
||||||
|
alarm.state = AISTATE.TRIPPED
|
||||||
|
alarm_states[alarm.id] = ALARM_STATE.TRIPPED
|
||||||
|
end
|
||||||
|
elseif ext_state == ALARM_STATE.INACTIVE then
|
||||||
|
-- was reset by coordinator
|
||||||
|
alarm.state = AISTATE.INACTIVE
|
||||||
|
alarm.trip_time = 0
|
||||||
|
end
|
||||||
|
else
|
||||||
|
log.error(util.c(caller_tag, " invalid alarm state for alarm ", alarm.id), true)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- check for state change
|
||||||
|
if alarm.state ~= int_state then
|
||||||
|
local change_str = util.c(AISTATE_NAMES[int_state], " -> ", AISTATE_NAMES[alarm.state])
|
||||||
|
log.debug(util.c(caller_tag, " ALARM ", alarm.id, " (", types.ALARM_NAMES[alarm.id], "): ", change_str))
|
||||||
|
return alarm.state == AISTATE.TRIPPED
|
||||||
|
else return false end
|
||||||
|
end
|
||||||
|
|
||||||
|
return alarm_ctl
|
||||||
171
supervisor/backplane.lua
Normal file
171
supervisor/backplane.lua
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
--
|
||||||
|
-- Supervisor System Core Peripheral Backplane
|
||||||
|
--
|
||||||
|
|
||||||
|
local log = require("scada-common.log")
|
||||||
|
local network = require("scada-common.network")
|
||||||
|
local ppm = require("scada-common.ppm")
|
||||||
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
|
local databus = require("supervisor.databus")
|
||||||
|
|
||||||
|
local println = util.println
|
||||||
|
|
||||||
|
---@class supervisor_backplane
|
||||||
|
local backplane = {}
|
||||||
|
|
||||||
|
local _bp = {
|
||||||
|
config = nil, ---@type svr_config
|
||||||
|
lan_iface = false, ---@type string|false wired comms modem name
|
||||||
|
|
||||||
|
wd_nic = nil, ---@type nic|nil wired nic
|
||||||
|
wl_nic = nil, ---@type nic|nil wireless nic
|
||||||
|
nic_map = {} ---@type nic[] connected nics
|
||||||
|
}
|
||||||
|
|
||||||
|
-- network interfaces indexed by peripheral names
|
||||||
|
backplane.nics = _bp.nic_map
|
||||||
|
|
||||||
|
-- initialize the system peripheral backplane
|
||||||
|
---@param config svr_config
|
||||||
|
---@return boolean success
|
||||||
|
function backplane.init(config)
|
||||||
|
_bp.lan_iface = config.WiredModem
|
||||||
|
|
||||||
|
-- setup the wired modem, if configured
|
||||||
|
if type(_bp.lan_iface) == "string" then
|
||||||
|
local modem = ppm.get_modem(_bp.lan_iface)
|
||||||
|
local wd_nic = network.nic(modem)
|
||||||
|
|
||||||
|
log.info("BKPLN: WIRED PHY_" .. util.trinary(modem, "UP ", "DOWN ") .. _bp.lan_iface)
|
||||||
|
|
||||||
|
if not (modem and _bp.lan_iface) then
|
||||||
|
println("startup> wired comms modem not found")
|
||||||
|
log.fatal("BKPLN: no wired comms modem on startup")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
_bp.wd_nic = wd_nic
|
||||||
|
_bp.nic_map[_bp.lan_iface] = wd_nic
|
||||||
|
|
||||||
|
wd_nic.closeAll()
|
||||||
|
wd_nic.open(config.SVR_Channel)
|
||||||
|
|
||||||
|
databus.tx_hw_wd_modem(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- setup the wireless modem, if configured
|
||||||
|
if config.WirelessModem then
|
||||||
|
local modem, iface = ppm.get_wireless_modem()
|
||||||
|
local wl_nic = network.nic(modem)
|
||||||
|
|
||||||
|
log.info("BKPLN: WIRELESS PHY_" .. util.trinary(modem, "UP ", "DOWN") .. (iface or ""))
|
||||||
|
|
||||||
|
if not (modem and iface) then
|
||||||
|
println("startup> wireless comms modem not found")
|
||||||
|
log.fatal("BKPLN: no wireless comms modem on startup")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
_bp.wl_nic = wl_nic
|
||||||
|
_bp.nic_map[iface] = wl_nic
|
||||||
|
|
||||||
|
wl_nic.closeAll()
|
||||||
|
wl_nic.open(config.SVR_Channel)
|
||||||
|
|
||||||
|
databus.tx_hw_wl_modem(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- handle a backplane peripheral attach
|
||||||
|
---@param iface string
|
||||||
|
---@param type string
|
||||||
|
---@param device table
|
||||||
|
---@param print_no_fp function
|
||||||
|
function backplane.attach(iface, type, device, print_no_fp)
|
||||||
|
if type == "modem" then
|
||||||
|
---@cast device Modem
|
||||||
|
|
||||||
|
local m_is_wl = device.isWireless()
|
||||||
|
|
||||||
|
log.info(util.c("BKPLN: ", util.trinary(m_is_wl, "WIRELESS", "WIRED"), " PHY_ATTACH ", iface))
|
||||||
|
|
||||||
|
if _bp.wd_nic and (_bp.lan_iface == iface) then
|
||||||
|
-- connect this as the wired NIC
|
||||||
|
_bp.wd_nic.connect(device)
|
||||||
|
|
||||||
|
log.info("BKPLN: WIRED PHY_UP " .. iface)
|
||||||
|
print_no_fp("wired comms modem reconnected")
|
||||||
|
|
||||||
|
databus.tx_hw_wd_modem(true)
|
||||||
|
elseif _bp.wl_nic and (not _bp.wl_nic.is_connected()) and m_is_wl then
|
||||||
|
-- connect this as the wireless NIC
|
||||||
|
_bp.wl_nic.connect(device)
|
||||||
|
_bp.nic_map[iface] = _bp.wl_nic
|
||||||
|
|
||||||
|
log.info("BKPLN: WIRELESS PHY_UP " .. iface)
|
||||||
|
print_no_fp("wireless comms modem reconnected")
|
||||||
|
|
||||||
|
databus.tx_hw_wl_modem(true)
|
||||||
|
elseif _bp.wl_nic and m_is_wl then
|
||||||
|
-- the wireless NIC already has a modem
|
||||||
|
device.closeAll()
|
||||||
|
|
||||||
|
print_no_fp("standby wireless modem connected")
|
||||||
|
log.info("BKPLN: standby wireless modem connected")
|
||||||
|
else
|
||||||
|
device.closeAll()
|
||||||
|
|
||||||
|
print_no_fp("unassigned modem connected")
|
||||||
|
log.warning("BKPLN: unassigned modem connected")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- handle a backplane peripheral detach
|
||||||
|
---@param iface string
|
||||||
|
---@param type string
|
||||||
|
---@param device table
|
||||||
|
---@param print_no_fp function
|
||||||
|
function backplane.detach(iface, type, device, print_no_fp)
|
||||||
|
if type == "modem" then
|
||||||
|
---@cast device Modem
|
||||||
|
|
||||||
|
log.info(util.c("BKPLN: PHY_DETACH ", iface))
|
||||||
|
|
||||||
|
_bp.nic_map[iface] = nil
|
||||||
|
|
||||||
|
if _bp.wd_nic and _bp.wd_nic.is_modem(device) then
|
||||||
|
_bp.wd_nic.disconnect()
|
||||||
|
log.info("BKPLN: WIRED PHY_DOWN " .. iface)
|
||||||
|
|
||||||
|
print_no_fp("wired modem disconnected")
|
||||||
|
log.warning("BKPLN: wired comms modem disconnected")
|
||||||
|
|
||||||
|
databus.tx_hw_wd_modem(false)
|
||||||
|
elseif _bp.wl_nic and _bp.wl_nic.is_modem(device) then
|
||||||
|
_bp.wl_nic.disconnect()
|
||||||
|
log.info("BKPLN: WIRELESS PHY_DOWN " .. iface)
|
||||||
|
|
||||||
|
print_no_fp("wireless comms modem disconnected")
|
||||||
|
log.warning("BKPLN: wireless comms modem disconnected")
|
||||||
|
|
||||||
|
local modem, m_iface = ppm.get_wireless_modem()
|
||||||
|
if modem then
|
||||||
|
log.info("BKPLN: found another wireless modem, using it for comms")
|
||||||
|
|
||||||
|
_bp.wl_nic.connect(modem)
|
||||||
|
log.info("BKPLN: WIRELESS PHY_UP " .. m_iface)
|
||||||
|
else
|
||||||
|
databus.tx_hw_wl_modem(false)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
print_no_fp("unassigned modem disconnected")
|
||||||
|
log.warning("BKPLN: unassigned modem disconnected")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return backplane
|
||||||
@@ -18,8 +18,6 @@ local tri = util.trinary
|
|||||||
local cpair = core.cpair
|
local cpair = core.cpair
|
||||||
|
|
||||||
local self = {
|
local self = {
|
||||||
tank_fluid_opts = {}, ---@type Radio2D[]
|
|
||||||
|
|
||||||
vis_draw = nil, ---@type function
|
vis_draw = nil, ---@type function
|
||||||
draw_fluid_ops = nil, ---@type function
|
draw_fluid_ops = nil, ---@type function
|
||||||
|
|
||||||
@@ -621,7 +619,7 @@ function facility.create(tool_ctl, main_pane, cfg_sys, fac_cfg, style)
|
|||||||
|
|
||||||
if type == 0 then type = 1 end
|
if type == 0 then type = 1 end
|
||||||
|
|
||||||
self.tank_fluid_opts[i] = nil
|
tool_ctl.tank_fluid_opts[i] = nil
|
||||||
|
|
||||||
if tank_list[i] == 1 then
|
if tank_list[i] == 1 then
|
||||||
local row = Div{parent=tank_fluid_list,height=2}
|
local row = Div{parent=tank_fluid_list,height=2}
|
||||||
@@ -636,7 +634,7 @@ function facility.create(tool_ctl, main_pane, cfg_sys, fac_cfg, style)
|
|||||||
tank_fluid.disable()
|
tank_fluid.disable()
|
||||||
end
|
end
|
||||||
|
|
||||||
self.tank_fluid_opts[i] = tank_fluid
|
tool_ctl.tank_fluid_opts[i] = tank_fluid
|
||||||
elseif tank_list[i] == 2 then
|
elseif tank_list[i] == 2 then
|
||||||
local row = Div{parent=tank_fluid_list,height=2}
|
local row = Div{parent=tank_fluid_list,height=2}
|
||||||
|
|
||||||
@@ -661,7 +659,7 @@ function facility.create(tool_ctl, main_pane, cfg_sys, fac_cfg, style)
|
|||||||
tank_fluid.disable()
|
tank_fluid.disable()
|
||||||
end
|
end
|
||||||
|
|
||||||
self.tank_fluid_opts[i] = tank_fluid
|
tool_ctl.tank_fluid_opts[i] = tank_fluid
|
||||||
|
|
||||||
next_f = next_f + 1
|
next_f = next_f + 1
|
||||||
end
|
end
|
||||||
@@ -676,11 +674,9 @@ function facility.create(tool_ctl, main_pane, cfg_sys, fac_cfg, style)
|
|||||||
tmp_cfg.TankFluidTypes = {}
|
tmp_cfg.TankFluidTypes = {}
|
||||||
|
|
||||||
for i = 1, #tmp_cfg.FacilityTankList do
|
for i = 1, #tmp_cfg.FacilityTankList do
|
||||||
if self.tank_fluid_opts[i] ~= nil then
|
if tool_ctl.tank_fluid_opts[i] ~= nil then
|
||||||
tmp_cfg.TankFluidTypes[i] = self.tank_fluid_opts[i].get_value()
|
tmp_cfg.TankFluidTypes[i] = tool_ctl.tank_fluid_opts[i].get_value()
|
||||||
else
|
else tmp_cfg.TankFluidTypes[i] = 0 end
|
||||||
tmp_cfg.TankFluidTypes[i] = 0
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
fac_pane.set_value(8)
|
fac_pane.set_value(8)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
local log = require("scada-common.log")
|
local log = require("scada-common.log")
|
||||||
|
local ppm = require("scada-common.ppm")
|
||||||
local types = require("scada-common.types")
|
local types = require("scada-common.types")
|
||||||
local util = require("scada-common.util")
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
@@ -14,6 +15,7 @@ local TextBox = require("graphics.elements.TextBox")
|
|||||||
|
|
||||||
local Checkbox = require("graphics.elements.controls.Checkbox")
|
local Checkbox = require("graphics.elements.controls.Checkbox")
|
||||||
local PushButton = require("graphics.elements.controls.PushButton")
|
local PushButton = require("graphics.elements.controls.PushButton")
|
||||||
|
local Radio2D = require("graphics.elements.controls.Radio2D")
|
||||||
local RadioButton = require("graphics.elements.controls.RadioButton")
|
local RadioButton = require("graphics.elements.controls.RadioButton")
|
||||||
|
|
||||||
local NumberField = require("graphics.elements.form.NumberField")
|
local NumberField = require("graphics.elements.form.NumberField")
|
||||||
@@ -25,14 +27,22 @@ local tri = util.trinary
|
|||||||
|
|
||||||
local cpair = core.cpair
|
local cpair = core.cpair
|
||||||
|
|
||||||
|
local LISTEN_MODE = types.LISTEN_MODE
|
||||||
|
|
||||||
local RIGHT = core.ALIGN.RIGHT
|
local RIGHT = core.ALIGN.RIGHT
|
||||||
|
|
||||||
local self = {
|
local self = {
|
||||||
importing_legacy = false,
|
importing_legacy = false,
|
||||||
|
|
||||||
|
update_net_cfg = nil, ---@type function
|
||||||
show_auth_key = nil, ---@type function
|
show_auth_key = nil, ---@type function
|
||||||
|
|
||||||
|
pkt_test = nil, ---@type Checkbox
|
||||||
|
pkt_chan = nil, ---@type NumberField
|
||||||
|
pkt_timeout = nil, ---@type NumberField
|
||||||
show_key_btn = nil, ---@type PushButton
|
show_key_btn = nil, ---@type PushButton
|
||||||
auth_key_textbox = nil, ---@type TextBox
|
auth_key_textbox = nil, ---@type TextBox
|
||||||
|
|
||||||
auth_key_value = ""
|
auth_key_value = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,115 +72,230 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit
|
|||||||
local net_c_2 = Div{parent=net_cfg,x=2,y=4,width=49}
|
local net_c_2 = Div{parent=net_cfg,x=2,y=4,width=49}
|
||||||
local net_c_3 = Div{parent=net_cfg,x=2,y=4,width=49}
|
local net_c_3 = Div{parent=net_cfg,x=2,y=4,width=49}
|
||||||
local net_c_4 = Div{parent=net_cfg,x=2,y=4,width=49}
|
local net_c_4 = Div{parent=net_cfg,x=2,y=4,width=49}
|
||||||
|
local net_c_5 = Div{parent=net_cfg,x=2,y=4,width=49}
|
||||||
|
local net_c_6 = Div{parent=net_cfg,x=2,y=4,width=49}
|
||||||
|
|
||||||
local net_pane = MultiPane{parent=net_cfg,x=1,y=4,panes={net_c_1,net_c_2,net_c_3,net_c_4}}
|
local net_pane = MultiPane{parent=net_cfg,x=1,y=4,panes={net_c_1,net_c_2,net_c_3,net_c_4,net_c_5,net_c_6}}
|
||||||
|
|
||||||
TextBox{parent=net_cfg,x=1,y=2,text=" Network Configuration",fg_bg=cpair(colors.black,colors.lightBlue)}
|
TextBox{parent=net_cfg,x=1,y=2,text=" Network Configuration",fg_bg=cpair(colors.black,colors.lightBlue)}
|
||||||
|
|
||||||
TextBox{parent=net_c_1,x=1,y=1,text="Please set the network channels below."}
|
TextBox{parent=net_c_1,x=1,y=1,text="Please select the network interface(s)."}
|
||||||
TextBox{parent=net_c_1,x=1,y=3,height=4,text="Each of the 5 uniquely named channels must be the same for each device in this SCADA network. For multiplayer servers, it is recommended to not use the default channels.",fg_bg=g_lg_fg_bg}
|
TextBox{parent=net_c_1,x=41,y=1,text="new!",fg_bg=cpair(colors.red,colors._INHERIT)} ---@todo remove NEW tag on next revision
|
||||||
|
|
||||||
TextBox{parent=net_c_1,x=1,y=8,width=18,text="Supervisor Channel"}
|
local function on_wired_change(_) tool_ctl.gen_modem_list() end
|
||||||
local svr_chan = NumberField{parent=net_c_1,x=21,y=8,width=7,default=ini_cfg.SVR_Channel,min=1,max=65535,fg_bg=bw_fg_bg}
|
|
||||||
TextBox{parent=net_c_1,x=29,y=8,height=4,text="[SVR_CHANNEL]",fg_bg=g_lg_fg_bg}
|
|
||||||
|
|
||||||
TextBox{parent=net_c_1,x=1,y=9,width=11,text="PLC Channel"}
|
local wireless = Checkbox{parent=net_c_1,x=1,y=3,label="Wireless/Ender Modem",default=ini_cfg.WirelessModem,box_fg_bg=cpair(colors.lightBlue,colors.black)}
|
||||||
local plc_chan = NumberField{parent=net_c_1,x=21,y=9,width=7,default=ini_cfg.PLC_Channel,min=1,max=65535,fg_bg=bw_fg_bg}
|
TextBox{parent=net_c_1,x=24,y=3,text="(required for Pocket)",fg_bg=g_lg_fg_bg}
|
||||||
TextBox{parent=net_c_1,x=29,y=9,height=4,text="[PLC_CHANNEL]",fg_bg=g_lg_fg_bg}
|
local wired = Checkbox{parent=net_c_1,x=1,y=5,label="Wired Modem",default=ini_cfg.WiredModem~=false,box_fg_bg=cpair(colors.lightBlue,colors.black),callback=on_wired_change}
|
||||||
|
TextBox{parent=net_c_1,x=3,y=6,text="this one MUST ONLY connect to SCADA computers",fg_bg=cpair(colors.red,colors._INHERIT)}
|
||||||
|
TextBox{parent=net_c_1,x=3,y=7,text="connecting it to peripherals will cause issues",fg_bg=g_lg_fg_bg}
|
||||||
|
local modem_list = ListBox{parent=net_c_1,x=1,y=8,height=5,width=49,scroll_height=100,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
|
||||||
|
|
||||||
TextBox{parent=net_c_1,x=1,y=10,width=19,text="RTU Gateway Channel"}
|
local modem_err = TextBox{parent=net_c_1,x=8,y=14,width=35,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||||
local rtu_chan = NumberField{parent=net_c_1,x=21,y=10,width=7,default=ini_cfg.RTU_Channel,min=1,max=65535,fg_bg=bw_fg_bg}
|
|
||||||
TextBox{parent=net_c_1,x=29,y=10,height=4,text="[RTU_CHANNEL]",fg_bg=g_lg_fg_bg}
|
|
||||||
|
|
||||||
TextBox{parent=net_c_1,x=1,y=11,width=19,text="Coordinator Channel"}
|
local function submit_interfaces()
|
||||||
local crd_chan = NumberField{parent=net_c_1,x=21,y=11,width=7,default=ini_cfg.CRD_Channel,min=1,max=65535,fg_bg=bw_fg_bg}
|
tmp_cfg.WirelessModem = wireless.get_value()
|
||||||
TextBox{parent=net_c_1,x=29,y=11,height=4,text="[CRD_CHANNEL]",fg_bg=g_lg_fg_bg}
|
|
||||||
|
|
||||||
TextBox{parent=net_c_1,x=1,y=12,width=14,text="Pocket Channel"}
|
if not wired.get_value() then
|
||||||
local pkt_chan = NumberField{parent=net_c_1,x=21,y=12,width=7,default=ini_cfg.PKT_Channel,min=1,max=65535,fg_bg=bw_fg_bg}
|
tmp_cfg.WiredModem = false
|
||||||
TextBox{parent=net_c_1,x=29,y=12,height=4,text="[PKT_CHANNEL]",fg_bg=g_lg_fg_bg}
|
tool_ctl.gen_modem_list()
|
||||||
|
end
|
||||||
|
|
||||||
local chan_err = TextBox{parent=net_c_1,x=8,y=14,width=35,text="Please set all channels.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
if not (wired.get_value() or wireless.get_value()) then
|
||||||
|
modem_err.set_value("Please select a modem type.")
|
||||||
|
modem_err.show()
|
||||||
|
elseif wired.get_value() and type(tmp_cfg.WiredModem) ~= "string" then
|
||||||
|
modem_err.set_value("Please select a wired modem.")
|
||||||
|
modem_err.show()
|
||||||
|
else
|
||||||
|
self.update_net_cfg()
|
||||||
|
net_pane.set_value(2)
|
||||||
|
modem_err.hide(true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
PushButton{parent=net_c_1,x=1,y=14,text="\x1b Back",callback=function()main_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
PushButton{parent=net_c_1,x=44,y=14,text="Next \x1a",callback=submit_interfaces,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
|
TextBox{parent=net_c_2,x=1,y=1,text="Please assign device connection interfaces if you selected multiple network interfaces."}
|
||||||
|
TextBox{parent=net_c_2,x=39,y=2,text="new!",fg_bg=cpair(colors.red,colors._INHERIT)} ---@todo remove NEW tag on next revision
|
||||||
|
TextBox{parent=net_c_2,x=1,y=4,text="Reactor PLC\nRTU Gateway\nCoordinator",fg_bg=g_lg_fg_bg}
|
||||||
|
local opts = { "Wireless", "Wired", "Both" }
|
||||||
|
local plc_listen = Radio2D{parent=net_c_2,x=14,y=4,rows=1,columns=3,default=ini_cfg.PLC_Listen,options=opts,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lightBlue,disable_color=colors.gray,disable_fg_bg=g_lg_fg_bg}
|
||||||
|
local rtu_listen = Radio2D{parent=net_c_2,x=14,rows=1,columns=3,default=ini_cfg.RTU_Listen,options=opts,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lightBlue,disable_color=colors.gray,disable_fg_bg=g_lg_fg_bg}
|
||||||
|
local crd_listen = Radio2D{parent=net_c_2,x=14,rows=1,columns=3,default=ini_cfg.CRD_Listen,options=opts,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lightBlue,disable_color=colors.gray,disable_fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
|
local function on_pocket_en(en)
|
||||||
|
if not en then
|
||||||
|
self.pkt_test.set_value(false)
|
||||||
|
self.pkt_test.disable()
|
||||||
|
else self.pkt_test.enable() end
|
||||||
|
end
|
||||||
|
|
||||||
|
TextBox{parent=net_c_2,y=8,text="With a wireless modem, configure Pocket access."}
|
||||||
|
local pkt_en = Checkbox{parent=net_c_2,y=10,label="Enable Pocket Access",default=ini_cfg.PocketEnabled,callback=on_pocket_en,box_fg_bg=cpair(colors.lightBlue,colors.black),disable_fg_bg=g_lg_fg_bg}
|
||||||
|
TextBox{parent=net_c_2,x=24,y=10,text="new!",fg_bg=cpair(colors.red,colors._INHERIT)} ---@todo remove NEW tag on next revision
|
||||||
|
self.pkt_test = Checkbox{parent=net_c_2,label="Enable Pocket Remote System Testing",default=ini_cfg.PocketTest,box_fg_bg=cpair(colors.lightBlue,colors.black),disable_fg_bg=g_lg_fg_bg}
|
||||||
|
TextBox{parent=net_c_2,x=39,y=11,text="new!",fg_bg=cpair(colors.red,colors._INHERIT)} ---@todo remove NEW tag on next revision
|
||||||
|
TextBox{parent=net_c_2,x=3,text="This allows remotely playing alarm sounds.",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
|
local function submit_net_cfg_opts()
|
||||||
|
if tmp_cfg.WirelessModem and tmp_cfg.WiredModem then
|
||||||
|
tmp_cfg.PLC_Listen = plc_listen.get_value()
|
||||||
|
tmp_cfg.RTU_Listen = rtu_listen.get_value()
|
||||||
|
tmp_cfg.CRD_Listen = crd_listen.get_value()
|
||||||
|
else
|
||||||
|
if tmp_cfg.WiredModem then
|
||||||
|
tmp_cfg.PLC_Listen = LISTEN_MODE.WIRED
|
||||||
|
tmp_cfg.RTU_Listen = LISTEN_MODE.WIRED
|
||||||
|
tmp_cfg.CRD_Listen = LISTEN_MODE.WIRED
|
||||||
|
else
|
||||||
|
tmp_cfg.PLC_Listen = LISTEN_MODE.WIRELESS
|
||||||
|
tmp_cfg.RTU_Listen = LISTEN_MODE.WIRELESS
|
||||||
|
tmp_cfg.CRD_Listen = LISTEN_MODE.WIRELESS
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if tmp_cfg.WirelessModem then
|
||||||
|
tmp_cfg.PocketEnabled = pkt_en.get_value()
|
||||||
|
tmp_cfg.PocketTest = self.pkt_test.get_value()
|
||||||
|
else
|
||||||
|
tmp_cfg.PocketEnabled = false
|
||||||
|
tmp_cfg.PocketTest = false
|
||||||
|
end
|
||||||
|
|
||||||
|
if tmp_cfg.PocketEnabled then
|
||||||
|
self.pkt_chan.enable()
|
||||||
|
self.pkt_timeout.enable()
|
||||||
|
else
|
||||||
|
self.pkt_chan.disable()
|
||||||
|
self.pkt_timeout.disable()
|
||||||
|
end
|
||||||
|
|
||||||
|
net_pane.set_value(3)
|
||||||
|
end
|
||||||
|
|
||||||
|
PushButton{parent=net_c_2,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
PushButton{parent=net_c_2,x=44,y=14,text="Next \x1a",callback=submit_net_cfg_opts,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
|
TextBox{parent=net_c_3,x=1,y=1,text="Please set the network channels below."}
|
||||||
|
TextBox{parent=net_c_3,x=1,y=3,height=4,text="Each of the 5 uniquely named channels must be the same for each device in this SCADA network. For multiplayer servers, it is recommended to not use the default channels.",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
|
TextBox{parent=net_c_3,x=1,y=8,width=18,text="Supervisor Channel"}
|
||||||
|
local svr_chan = NumberField{parent=net_c_3,x=21,y=8,width=7,default=ini_cfg.SVR_Channel,min=1,max=65535,fg_bg=bw_fg_bg}
|
||||||
|
TextBox{parent=net_c_3,x=29,y=8,height=4,text="[SVR_CHANNEL]",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
|
TextBox{parent=net_c_3,x=1,y=9,width=11,text="PLC Channel"}
|
||||||
|
local plc_chan = NumberField{parent=net_c_3,x=21,y=9,width=7,default=ini_cfg.PLC_Channel,min=1,max=65535,fg_bg=bw_fg_bg}
|
||||||
|
TextBox{parent=net_c_3,x=29,y=9,height=4,text="[PLC_CHANNEL]",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
|
TextBox{parent=net_c_3,x=1,y=10,width=19,text="RTU Gateway Channel"}
|
||||||
|
local rtu_chan = NumberField{parent=net_c_3,x=21,y=10,width=7,default=ini_cfg.RTU_Channel,min=1,max=65535,fg_bg=bw_fg_bg}
|
||||||
|
TextBox{parent=net_c_3,x=29,y=10,height=4,text="[RTU_CHANNEL]",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
|
TextBox{parent=net_c_3,x=1,y=11,width=19,text="Coordinator Channel"}
|
||||||
|
local crd_chan = NumberField{parent=net_c_3,x=21,y=11,width=7,default=ini_cfg.CRD_Channel,min=1,max=65535,fg_bg=bw_fg_bg}
|
||||||
|
TextBox{parent=net_c_3,x=29,y=11,height=4,text="[CRD_CHANNEL]",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
|
TextBox{parent=net_c_3,x=1,y=12,width=14,text="Pocket Channel"}
|
||||||
|
self.pkt_chan = NumberField{parent=net_c_3,x=21,y=12,width=7,default=ini_cfg.PKT_Channel,min=1,max=65535,fg_bg=bw_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)}
|
||||||
|
TextBox{parent=net_c_3,x=29,y=12,height=4,text="[PKT_CHANNEL]",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
|
local chan_err = TextBox{parent=net_c_3,x=8,y=14,width=35,text="Please set all channels.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||||
|
|
||||||
local function submit_channels()
|
local function submit_channels()
|
||||||
local svr_c, plc_c, rtu_c = tonumber(svr_chan.get_value()), tonumber(plc_chan.get_value()), tonumber(rtu_chan.get_value())
|
local svr_c, plc_c, rtu_c = tonumber(svr_chan.get_value()), tonumber(plc_chan.get_value()), tonumber(rtu_chan.get_value())
|
||||||
local crd_c, pkt_c = tonumber(crd_chan.get_value()), tonumber(pkt_chan.get_value())
|
local crd_c, pkt_c = tonumber(crd_chan.get_value()), tonumber(self.pkt_chan.get_value())
|
||||||
|
|
||||||
|
if not tmp_cfg.PocketEnabled then pkt_c = tmp_cfg.PKT_Channel or 16244 end
|
||||||
|
|
||||||
if svr_c ~= nil and plc_c ~= nil and rtu_c ~= nil and crd_c ~= nil and pkt_c ~= nil then
|
if svr_c ~= nil and plc_c ~= nil and rtu_c ~= nil and crd_c ~= nil and pkt_c ~= nil then
|
||||||
tmp_cfg.SVR_Channel, tmp_cfg.PLC_Channel, tmp_cfg.RTU_Channel = svr_c, plc_c, rtu_c
|
tmp_cfg.SVR_Channel, tmp_cfg.PLC_Channel, tmp_cfg.RTU_Channel = svr_c, plc_c, rtu_c
|
||||||
tmp_cfg.CRD_Channel, tmp_cfg.PKT_Channel = crd_c, pkt_c
|
tmp_cfg.CRD_Channel, tmp_cfg.PKT_Channel = crd_c, pkt_c
|
||||||
net_pane.set_value(2)
|
net_pane.set_value(4)
|
||||||
chan_err.hide(true)
|
chan_err.hide(true)
|
||||||
else chan_err.show() end
|
else chan_err.show() end
|
||||||
end
|
end
|
||||||
|
|
||||||
PushButton{parent=net_c_1,x=1,y=14,text="\x1b Back",callback=function()main_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=net_c_3,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
PushButton{parent=net_c_1,x=44,y=14,text="Next \x1a",callback=submit_channels,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=net_c_3,x=44,y=14,text="Next \x1a",callback=submit_channels,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=net_c_2,x=1,y=1,text="Please set the connection timeouts below."}
|
TextBox{parent=net_c_4,x=1,y=1,text="Please set the connection timeouts below."}
|
||||||
TextBox{parent=net_c_2,x=1,y=3,height=4,text="You generally should not need to modify these. On slow servers, you can try to increase this to make the system wait longer before assuming a disconnection. The default for all is 5 seconds.",fg_bg=g_lg_fg_bg}
|
TextBox{parent=net_c_4,x=1,y=3,height=4,text="You generally should not need to modify these. On slow servers, you can try to increase this to make the system wait longer before assuming a disconnection. The default for all is 5 seconds.",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=net_c_2,x=1,y=8,width=11,text="PLC Timeout"}
|
TextBox{parent=net_c_4,x=1,y=8,width=11,text="PLC Timeout"}
|
||||||
local plc_timeout = NumberField{parent=net_c_2,x=21,y=8,width=7,default=ini_cfg.PLC_Timeout,min=2,max=25,max_chars=6,max_frac_digits=2,allow_decimal=true,fg_bg=bw_fg_bg}
|
local plc_timeout = NumberField{parent=net_c_4,x=21,y=8,width=7,default=ini_cfg.PLC_Timeout,min=2,max=25,max_chars=6,max_frac_digits=2,allow_decimal=true,fg_bg=bw_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=net_c_2,x=1,y=9,width=19,text="RTU Gateway Timeout"}
|
TextBox{parent=net_c_4,x=1,y=9,width=19,text="RTU Gateway Timeout"}
|
||||||
local rtu_timeout = NumberField{parent=net_c_2,x=21,y=9,width=7,default=ini_cfg.RTU_Timeout,min=2,max=25,max_chars=6,max_frac_digits=2,allow_decimal=true,fg_bg=bw_fg_bg}
|
local rtu_timeout = NumberField{parent=net_c_4,x=21,y=9,width=7,default=ini_cfg.RTU_Timeout,min=2,max=25,max_chars=6,max_frac_digits=2,allow_decimal=true,fg_bg=bw_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=net_c_2,x=1,y=10,width=19,text="Coordinator Timeout"}
|
TextBox{parent=net_c_4,x=1,y=10,width=19,text="Coordinator Timeout"}
|
||||||
local crd_timeout = NumberField{parent=net_c_2,x=21,y=10,width=7,default=ini_cfg.CRD_Timeout,min=2,max=25,max_chars=6,max_frac_digits=2,allow_decimal=true,fg_bg=bw_fg_bg}
|
local crd_timeout = NumberField{parent=net_c_4,x=21,y=10,width=7,default=ini_cfg.CRD_Timeout,min=2,max=25,max_chars=6,max_frac_digits=2,allow_decimal=true,fg_bg=bw_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=net_c_2,x=1,y=11,width=14,text="Pocket Timeout"}
|
TextBox{parent=net_c_4,x=1,y=11,width=14,text="Pocket Timeout"}
|
||||||
local pkt_timeout = NumberField{parent=net_c_2,x=21,y=11,width=7,default=ini_cfg.PKT_Timeout,min=2,max=25,max_chars=6,max_frac_digits=2,allow_decimal=true,fg_bg=bw_fg_bg}
|
self.pkt_timeout = NumberField{parent=net_c_4,x=21,y=11,width=7,default=ini_cfg.PKT_Timeout,min=2,max=25,max_chars=6,max_frac_digits=2,allow_decimal=true,fg_bg=bw_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)}
|
||||||
|
|
||||||
TextBox{parent=net_c_2,x=29,y=8,height=4,width=7,text="seconds\nseconds\nseconds\nseconds",fg_bg=g_lg_fg_bg}
|
TextBox{parent=net_c_4,x=29,y=8,height=4,width=7,text="seconds\nseconds\nseconds\nseconds",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
local ct_err = TextBox{parent=net_c_2,x=8,y=14,width=35,text="Please set all connection timeouts.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
local ct_err = TextBox{parent=net_c_4,x=8,y=14,width=35,text="Please set all connection timeouts.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||||
|
|
||||||
local function submit_timeouts()
|
local function submit_timeouts()
|
||||||
local plc_cto, rtu_cto, crd_cto, pkt_cto = tonumber(plc_timeout.get_value()), tonumber(rtu_timeout.get_value()), tonumber(crd_timeout.get_value()), tonumber(pkt_timeout.get_value())
|
local plc_cto, rtu_cto, crd_cto, pkt_cto = tonumber(plc_timeout.get_value()), tonumber(rtu_timeout.get_value()), tonumber(crd_timeout.get_value()), tonumber(self.pkt_timeout.get_value())
|
||||||
|
|
||||||
|
if not tmp_cfg.PocketEnabled then pkt_cto = tmp_cfg.PKT_Timeout or 5 end
|
||||||
|
|
||||||
if plc_cto ~= nil and rtu_cto ~= nil and crd_cto ~= nil and pkt_cto ~= nil then
|
if plc_cto ~= nil and rtu_cto ~= nil and crd_cto ~= nil and pkt_cto ~= nil then
|
||||||
tmp_cfg.PLC_Timeout, tmp_cfg.RTU_Timeout, tmp_cfg.CRD_Timeout, tmp_cfg.PKT_Timeout = plc_cto, rtu_cto, crd_cto, pkt_cto
|
tmp_cfg.PLC_Timeout, tmp_cfg.RTU_Timeout, tmp_cfg.CRD_Timeout, tmp_cfg.PKT_Timeout = plc_cto, rtu_cto, crd_cto, pkt_cto
|
||||||
net_pane.set_value(3)
|
|
||||||
|
if tmp_cfg.WirelessModem then
|
||||||
|
net_pane.set_value(5)
|
||||||
|
else
|
||||||
|
tmp_cfg.TrustedRange = 0
|
||||||
|
tmp_cfg.AuthKey = ""
|
||||||
|
main_pane.set_value(4)
|
||||||
|
end
|
||||||
|
|
||||||
ct_err.hide(true)
|
ct_err.hide(true)
|
||||||
else ct_err.show() end
|
else ct_err.show() end
|
||||||
end
|
end
|
||||||
|
|
||||||
PushButton{parent=net_c_2,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=net_c_4,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(3)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
PushButton{parent=net_c_2,x=44,y=14,text="Next \x1a",callback=submit_timeouts,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=net_c_4,x=44,y=14,text="Next \x1a",callback=submit_timeouts,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=net_c_3,x=1,y=1,text="Please set the trusted range below."}
|
TextBox{parent=net_c_5,x=1,y=1,text="Please set the wireless trusted range below."}
|
||||||
TextBox{parent=net_c_3,x=1,y=3,height=3,text="Setting this to a value larger than 0 prevents connections with devices that many meters (blocks) away in any direction.",fg_bg=g_lg_fg_bg}
|
TextBox{parent=net_c_5,x=1,y=3,height=3,text="Setting this to a value larger than 0 prevents wireless connections with devices that many meters (blocks) away in any direction.",fg_bg=g_lg_fg_bg}
|
||||||
TextBox{parent=net_c_3,x=1,y=7,height=2,text="This is optional. You can disable this functionality by setting the value to 0.",fg_bg=g_lg_fg_bg}
|
TextBox{parent=net_c_5,x=1,y=7,height=2,text="This is optional. You can disable this functionality by setting the value to 0.",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
local range = NumberField{parent=net_c_3,x=1,y=10,width=10,default=ini_cfg.TrustedRange,min=0,max_chars=20,allow_decimal=true,fg_bg=bw_fg_bg}
|
local range = NumberField{parent=net_c_5,x=1,y=10,width=10,default=ini_cfg.TrustedRange,min=0,max_chars=20,allow_decimal=true,fg_bg=bw_fg_bg}
|
||||||
|
|
||||||
local tr_err = TextBox{parent=net_c_3,x=8,y=14,width=35,text="Please set the trusted range.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
local tr_err = TextBox{parent=net_c_5,x=8,y=14,width=35,text="Please set the trusted range.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||||
|
|
||||||
local function submit_tr()
|
local function submit_tr()
|
||||||
local range_val = tonumber(range.get_value())
|
local range_val = tonumber(range.get_value())
|
||||||
if range_val ~= nil then
|
if range_val ~= nil then
|
||||||
tmp_cfg.TrustedRange = range_val
|
tmp_cfg.TrustedRange = range_val
|
||||||
net_pane.set_value(4)
|
net_pane.set_value(6)
|
||||||
tr_err.hide(true)
|
tr_err.hide(true)
|
||||||
else tr_err.show() end
|
else tr_err.show() end
|
||||||
end
|
end
|
||||||
|
|
||||||
PushButton{parent=net_c_3,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=net_c_5,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(4)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
PushButton{parent=net_c_3,x=44,y=14,text="Next \x1a",callback=submit_tr,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=net_c_5,x=44,y=14,text="Next \x1a",callback=submit_tr,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=net_c_4,x=1,y=1,height=2,text="Optionally, set the facility authentication key below. Do NOT use one of your passwords."}
|
TextBox{parent=net_c_6,x=1,y=1,height=2,text="Optionally, set the facility authentication key below. Do NOT use one of your passwords."}
|
||||||
TextBox{parent=net_c_4,x=1,y=4,height=6,text="This enables verifying that messages are authentic, so it is intended for security on multiplayer servers. All devices on the same network MUST use the same key if any device has a key. This does result in some extra computation (can slow things down).",fg_bg=g_lg_fg_bg}
|
TextBox{parent=net_c_6,x=1,y=4,height=6,text="This enables verifying that messages are authentic, so it is intended for wireless security on multiplayer servers. All devices on the same wireless network MUST use the same key if any device has a key. This does result in some extra computation (can slow things down).",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=net_c_4,x=1,y=11,text="Facility Auth Key"}
|
TextBox{parent=net_c_6,x=1,y=11,text="Auth Key (Wireless Only, Not Used for Wired)"}
|
||||||
local key, _ = TextField{parent=net_c_4,x=1,y=12,max_len=64,value=ini_cfg.AuthKey,width=32,height=1,fg_bg=bw_fg_bg}
|
local key, _ = TextField{parent=net_c_6,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) key.censor(tri(enable, "*", nil)) end
|
local function censor_key(enable) key.censor(tri(enable, "*", nil)) end
|
||||||
|
|
||||||
local hide_key = Checkbox{parent=net_c_4,x=34,y=12,label="Hide",box_fg_bg=cpair(colors.lightBlue,colors.black),callback=censor_key}
|
local hide_key = Checkbox{parent=net_c_6,x=34,y=12,label="Hide",box_fg_bg=cpair(colors.lightBlue,colors.black),callback=censor_key}
|
||||||
|
|
||||||
hide_key.set_value(true)
|
hide_key.set_value(true)
|
||||||
censor_key(true)
|
censor_key(true)
|
||||||
|
|
||||||
local key_err = TextBox{parent=net_c_4,x=8,y=14,width=35,text="Key must be at least 8 characters.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
local key_err = TextBox{parent=net_c_6,x=8,y=14,width=35,text="Key must be at least 8 characters.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||||
|
|
||||||
local function submit_auth()
|
local function submit_auth()
|
||||||
local v = key.get_value()
|
local v = key.get_value()
|
||||||
@@ -181,8 +306,8 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit
|
|||||||
else key_err.show() end
|
else key_err.show() end
|
||||||
end
|
end
|
||||||
|
|
||||||
PushButton{parent=net_c_4,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(3)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=net_c_6,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(5)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
PushButton{parent=net_c_4,x=44,y=14,text="Next \x1a",callback=submit_auth,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=net_c_6,x=44,y=14,text="Next \x1a",callback=submit_auth,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
--#endregion
|
--#endregion
|
||||||
|
|
||||||
@@ -195,7 +320,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit
|
|||||||
TextBox{parent=log_c_1,x=1,y=1,text="Please configure logging below."}
|
TextBox{parent=log_c_1,x=1,y=1,text="Please configure logging below."}
|
||||||
|
|
||||||
TextBox{parent=log_c_1,x=1,y=3,text="Log File Mode"}
|
TextBox{parent=log_c_1,x=1,y=3,text="Log File Mode"}
|
||||||
local mode = RadioButton{parent=log_c_1,x=1,y=4,default=ini_cfg.LogMode+1,options={"Append on Startup","Replace on Startup"},callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.pink}
|
local mode = RadioButton{parent=log_c_1,x=1,y=4,default=ini_cfg.LogMode+1,options={"Append on Startup","Replace on Startup"},radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.pink}
|
||||||
|
|
||||||
TextBox{parent=log_c_1,x=1,y=7,text="Log File Path"}
|
TextBox{parent=log_c_1,x=1,y=7,text="Log File Path"}
|
||||||
local path = TextField{parent=log_c_1,x=1,y=8,width=49,height=1,value=ini_cfg.LogPath,max_len=128,fg_bg=bw_fg_bg}
|
local path = TextField{parent=log_c_1,x=1,y=8,width=49,height=1,value=ini_cfg.LogPath,max_len=128,fg_bg=bw_fg_bg}
|
||||||
@@ -237,7 +362,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit
|
|||||||
TextBox{parent=clr_c_1,x=1,y=4,height=2,text="Click 'Accessibility' below to access colorblind assistive options.",fg_bg=g_lg_fg_bg}
|
TextBox{parent=clr_c_1,x=1,y=4,height=2,text="Click 'Accessibility' below to access colorblind assistive options.",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=clr_c_1,x=1,y=7,text="Front Panel Theme"}
|
TextBox{parent=clr_c_1,x=1,y=7,text="Front Panel Theme"}
|
||||||
local fp_theme = RadioButton{parent=clr_c_1,x=1,y=8,default=ini_cfg.FrontPanelTheme,options=themes.FP_THEME_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.magenta}
|
local fp_theme = RadioButton{parent=clr_c_1,x=1,y=8,default=ini_cfg.FrontPanelTheme,options=themes.FP_THEME_NAMES,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=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."}
|
||||||
|
|
||||||
@@ -374,15 +499,22 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit
|
|||||||
|
|
||||||
try_set(tool_ctl.num_units, ini_cfg.UnitCount)
|
try_set(tool_ctl.num_units, ini_cfg.UnitCount)
|
||||||
try_set(tool_ctl.tank_mode, ini_cfg.FacilityTankMode)
|
try_set(tool_ctl.tank_mode, ini_cfg.FacilityTankMode)
|
||||||
|
try_set(wireless, ini_cfg.WirelessModem)
|
||||||
|
try_set(wired, ini_cfg.WiredModem ~= false)
|
||||||
|
try_set(plc_listen, ini_cfg.PLC_Listen)
|
||||||
|
try_set(rtu_listen, ini_cfg.RTU_Listen)
|
||||||
|
try_set(crd_listen, ini_cfg.CRD_Listen)
|
||||||
|
try_set(pkt_en, ini_cfg.PocketEnabled)
|
||||||
|
try_set(self.pkt_test, ini_cfg.PocketTest)
|
||||||
try_set(svr_chan, ini_cfg.SVR_Channel)
|
try_set(svr_chan, ini_cfg.SVR_Channel)
|
||||||
try_set(plc_chan, ini_cfg.PLC_Channel)
|
try_set(plc_chan, ini_cfg.PLC_Channel)
|
||||||
try_set(rtu_chan, ini_cfg.RTU_Channel)
|
try_set(rtu_chan, ini_cfg.RTU_Channel)
|
||||||
try_set(crd_chan, ini_cfg.CRD_Channel)
|
try_set(crd_chan, ini_cfg.CRD_Channel)
|
||||||
try_set(pkt_chan, ini_cfg.PKT_Channel)
|
try_set(self.pkt_chan, ini_cfg.PKT_Channel)
|
||||||
try_set(plc_timeout, ini_cfg.PLC_Timeout)
|
try_set(plc_timeout, ini_cfg.PLC_Timeout)
|
||||||
try_set(rtu_timeout, ini_cfg.RTU_Timeout)
|
try_set(rtu_timeout, ini_cfg.RTU_Timeout)
|
||||||
try_set(crd_timeout, ini_cfg.CRD_Timeout)
|
try_set(crd_timeout, ini_cfg.CRD_Timeout)
|
||||||
try_set(pkt_timeout, ini_cfg.PKT_Timeout)
|
try_set(self.pkt_timeout, ini_cfg.PKT_Timeout)
|
||||||
try_set(range, ini_cfg.TrustedRange)
|
try_set(range, ini_cfg.TrustedRange)
|
||||||
try_set(key, ini_cfg.AuthKey)
|
try_set(key, ini_cfg.AuthKey)
|
||||||
try_set(mode, ini_cfg.LogMode)
|
try_set(mode, ini_cfg.LogMode)
|
||||||
@@ -406,6 +538,17 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit
|
|||||||
try_set(tool_ctl.aux_cool_elems[i].enable, ini_cfg.AuxiliaryCoolant[i])
|
try_set(tool_ctl.aux_cool_elems[i].enable, ini_cfg.AuxiliaryCoolant[i])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
for i = 1, #ini_cfg.TankFluidTypes do
|
||||||
|
if tool_ctl.tank_fluid_opts[i] then
|
||||||
|
if (ini_cfg.TankFluidTypes[i] > 0) then
|
||||||
|
tool_ctl.tank_fluid_opts[i].enable()
|
||||||
|
tool_ctl.tank_fluid_opts[i].set_value(ini_cfg.TankFluidTypes[i])
|
||||||
|
else
|
||||||
|
tool_ctl.tank_fluid_opts[i].disable()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
tool_ctl.en_fac_tanks.set_value(ini_cfg.FacilityTankMode > 0)
|
tool_ctl.en_fac_tanks.set_value(ini_cfg.FacilityTankMode > 0)
|
||||||
|
|
||||||
tool_ctl.view_cfg.enable()
|
tool_ctl.view_cfg.enable()
|
||||||
@@ -470,6 +613,39 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit
|
|||||||
|
|
||||||
--#region Tool Functions
|
--#region Tool Functions
|
||||||
|
|
||||||
|
-- expose the auth key on the summary page
|
||||||
|
function self.show_auth_key()
|
||||||
|
self.show_key_btn.disable()
|
||||||
|
self.auth_key_textbox.set_value(self.auth_key_value)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- update the network interface configuration options
|
||||||
|
function self.update_net_cfg()
|
||||||
|
if tmp_cfg.WirelessModem and tmp_cfg.WiredModem then
|
||||||
|
plc_listen.enable()
|
||||||
|
rtu_listen.enable()
|
||||||
|
crd_listen.enable()
|
||||||
|
else
|
||||||
|
plc_listen.disable()
|
||||||
|
rtu_listen.disable()
|
||||||
|
crd_listen.disable()
|
||||||
|
end
|
||||||
|
|
||||||
|
if tmp_cfg.WirelessModem then
|
||||||
|
pkt_en.enable()
|
||||||
|
self.pkt_test.enable()
|
||||||
|
self.pkt_chan.enable()
|
||||||
|
self.pkt_timeout.enable()
|
||||||
|
else
|
||||||
|
pkt_en.set_value(false)
|
||||||
|
self.pkt_test.set_value(false)
|
||||||
|
pkt_en.disable()
|
||||||
|
self.pkt_test.disable()
|
||||||
|
self.pkt_chan.disable()
|
||||||
|
self.pkt_timeout.disable()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- load a legacy config file
|
-- load a legacy config file
|
||||||
function tool_ctl.load_legacy()
|
function tool_ctl.load_legacy()
|
||||||
local config = require("supervisor.config")
|
local config = require("supervisor.config")
|
||||||
@@ -524,6 +700,9 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit
|
|||||||
|
|
||||||
tmp_cfg.FacilityTankList, tmp_cfg.FacilityTankConns = facility.generate_tank_list_and_conns(tmp_cfg.FacilityTankMode, tmp_cfg.FacilityTankDefs)
|
tmp_cfg.FacilityTankList, tmp_cfg.FacilityTankConns = facility.generate_tank_list_and_conns(tmp_cfg.FacilityTankMode, tmp_cfg.FacilityTankDefs)
|
||||||
|
|
||||||
|
for i = 1, tmp_cfg.UnitCount do tmp_cfg.AuxiliaryCoolant[i] = false end
|
||||||
|
for i = 1, tmp_cfg.FacilityTankList do tmp_cfg.TankFluidTypes[i] = types.COOLANT_TYPE.WATER end
|
||||||
|
|
||||||
tmp_cfg.SVR_Channel = config.SVR_CHANNEL
|
tmp_cfg.SVR_Channel = config.SVR_CHANNEL
|
||||||
tmp_cfg.PLC_Channel = config.PLC_CHANNEL
|
tmp_cfg.PLC_Channel = config.PLC_CHANNEL
|
||||||
tmp_cfg.RTU_Channel = config.RTU_CHANNEL
|
tmp_cfg.RTU_Channel = config.RTU_CHANNEL
|
||||||
@@ -547,12 +726,6 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit
|
|||||||
self.importing_legacy = true
|
self.importing_legacy = true
|
||||||
end
|
end
|
||||||
|
|
||||||
-- expose the auth key on the summary page
|
|
||||||
function self.show_auth_key()
|
|
||||||
self.show_key_btn.disable()
|
|
||||||
self.auth_key_textbox.set_value(self.auth_key_value)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- generate the summary list
|
-- generate the summary list
|
||||||
---@param cfg svr_config
|
---@param cfg svr_config
|
||||||
function tool_ctl.gen_summary(cfg)
|
function tool_ctl.gen_summary(cfg)
|
||||||
@@ -675,6 +848,10 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit
|
|||||||
end
|
end
|
||||||
|
|
||||||
if val == "" then val = "no auxiliary coolant" end
|
if val == "" then val = "no auxiliary coolant" end
|
||||||
|
elseif f[1] == "PLC_Listen" or f[1] == "RTU_Listen" or f[1] == "CRD_Listen" then
|
||||||
|
if raw == LISTEN_MODE.WIRELESS then val = "Wireless Only"
|
||||||
|
elseif raw == LISTEN_MODE.WIRED then val = "Wired Only"
|
||||||
|
elseif raw == LISTEN_MODE.ALL then val = "Wireless and Wired" end
|
||||||
end
|
end
|
||||||
|
|
||||||
if not skip then
|
if not skip then
|
||||||
@@ -703,6 +880,59 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- generate the list of available/assigned wired modems
|
||||||
|
function tool_ctl.gen_modem_list()
|
||||||
|
modem_list.remove_all()
|
||||||
|
|
||||||
|
local enable = wired.get_value()
|
||||||
|
|
||||||
|
local function select(iface)
|
||||||
|
tmp_cfg.WiredModem = iface
|
||||||
|
tool_ctl.gen_modem_list()
|
||||||
|
end
|
||||||
|
|
||||||
|
local modems = ppm.get_wired_modem_list()
|
||||||
|
local missing = { tmp = true, ini = true }
|
||||||
|
|
||||||
|
for iface, _ in pairs(modems) do
|
||||||
|
if ini_cfg.WiredModem == iface then missing.ini = false end
|
||||||
|
if tmp_cfg.WiredModem == iface then missing.tmp = false end
|
||||||
|
end
|
||||||
|
|
||||||
|
if missing.tmp and tmp_cfg.WiredModem then
|
||||||
|
local line = Div{parent=modem_list,x=1,y=1,height=1}
|
||||||
|
|
||||||
|
TextBox{parent=line,x=1,y=1,width=4,text="Used",fg_bg=cpair(tri(enable,colors.blue,colors.gray),colors.white)}
|
||||||
|
PushButton{parent=line,x=6,y=1,min_width=8,height=1,text="SELECT",callback=function()end,fg_bg=cpair(colors.black,colors.lightBlue),active_fg_bg=btn_act_fg_bg,dis_fg_bg=g_lg_fg_bg}.disable()
|
||||||
|
TextBox{parent=line,x=15,y=1,text="[missing]",fg_bg=cpair(colors.red,colors.white)}
|
||||||
|
TextBox{parent=line,x=25,y=1,text=tmp_cfg.WiredModem}
|
||||||
|
end
|
||||||
|
|
||||||
|
if missing.ini and ini_cfg.WiredModem and (tmp_cfg.WiredModem ~= ini_cfg.WiredModem) then
|
||||||
|
local line = Div{parent=modem_list,x=1,y=1,height=1}
|
||||||
|
local used = tmp_cfg.WiredModem == ini_cfg.WiredModem
|
||||||
|
|
||||||
|
TextBox{parent=line,x=1,y=1,width=4,text=tri(used,"Used","----"),fg_bg=cpair(tri(used and enable,colors.blue,colors.gray),colors.white)}
|
||||||
|
local select_btn = PushButton{parent=line,x=6,y=1,min_width=8,height=1,text="SELECT",callback=function()select(ini_cfg.WiredModem)end,fg_bg=cpair(colors.black,colors.lightBlue),active_fg_bg=btn_act_fg_bg,dis_fg_bg=g_lg_fg_bg}
|
||||||
|
TextBox{parent=line,x=15,y=1,text="[missing]",fg_bg=cpair(colors.red,colors.white)}
|
||||||
|
TextBox{parent=line,x=25,y=1,text=ini_cfg.WiredModem}
|
||||||
|
|
||||||
|
if used or not enable then select_btn.disable() end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- list wired modems
|
||||||
|
for iface, _ in pairs(modems) do
|
||||||
|
local line = Div{parent=modem_list,x=1,y=1,height=1}
|
||||||
|
local used = tmp_cfg.WiredModem == iface
|
||||||
|
|
||||||
|
TextBox{parent=line,x=1,y=1,width=4,text=tri(used,"Used","----"),fg_bg=cpair(tri(used and enable,colors.blue,colors.gray),colors.white)}
|
||||||
|
local select_btn = PushButton{parent=line,x=6,y=1,min_width=8,height=1,text="SELECT",callback=function()select(iface)end,fg_bg=cpair(colors.black,colors.lightBlue),active_fg_bg=btn_act_fg_bg,dis_fg_bg=g_lg_fg_bg}
|
||||||
|
TextBox{parent=line,x=15,y=1,text=iface}
|
||||||
|
|
||||||
|
if used or not enable then select_btn.disable() end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
--#endregion
|
--#endregion
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,9 @@
|
|||||||
--
|
--
|
||||||
|
|
||||||
local log = require("scada-common.log")
|
local log = require("scada-common.log")
|
||||||
|
local ppm = require("scada-common.ppm")
|
||||||
local tcd = require("scada-common.tcd")
|
local tcd = require("scada-common.tcd")
|
||||||
|
local types = require("scada-common.types")
|
||||||
local util = require("scada-common.util")
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
local facility = require("supervisor.config.facility")
|
local facility = require("supervisor.config.facility")
|
||||||
@@ -31,7 +33,8 @@ local CENTER = core.ALIGN.CENTER
|
|||||||
local changes = {
|
local changes = {
|
||||||
{ "v1.2.12", { "Added front panel UI theme", "Added color accessibility modes" } },
|
{ "v1.2.12", { "Added front panel UI theme", "Added color accessibility modes" } },
|
||||||
{ "v1.3.2", { "Added standard with black off state color mode", "Added blue indicator color modes" } },
|
{ "v1.3.2", { "Added standard with black off state color mode", "Added blue indicator color modes" } },
|
||||||
{ "v1.6.0", { "Added sodium emergency coolant option" } }
|
{ "v1.6.0", { "Added sodium emergency coolant option" } },
|
||||||
|
{ "v1.8.0", { "Added support for wired communications modems", "Added option for allowing Pocket connections", "Added option for allowing Pocket test commands" } }
|
||||||
}
|
}
|
||||||
|
|
||||||
---@class svr_configurator
|
---@class svr_configurator
|
||||||
@@ -67,13 +70,16 @@ local tool_ctl = {
|
|||||||
num_units = nil, ---@type NumberField
|
num_units = nil, ---@type NumberField
|
||||||
en_fac_tanks = nil, ---@type Checkbox
|
en_fac_tanks = nil, ---@type Checkbox
|
||||||
tank_mode = nil, ---@type RadioButton
|
tank_mode = nil, ---@type RadioButton
|
||||||
|
tank_fluid_opts = {}, ---@type Radio2D[]
|
||||||
|
|
||||||
gen_summary = nil, ---@type function
|
gen_summary = nil, ---@type function
|
||||||
load_legacy = nil, ---@type function
|
load_legacy = nil, ---@type function
|
||||||
|
|
||||||
cooling_elems = {}, ---@type { line: Div, turbines: NumberField, boilers: NumberField, tank: Checkbox }[]
|
cooling_elems = {}, ---@type { line: Div, turbines: NumberField, boilers: NumberField, tank: Checkbox }[]
|
||||||
tank_elems = {}, ---@type { div: Div, tank_opt: Radio2D, no_tank: TextBox }[]
|
tank_elems = {}, ---@type { div: Div, tank_opt: Radio2D, no_tank: TextBox }[]
|
||||||
aux_cool_elems = {} ---@type { line: Div, enable: Checkbox }[]
|
aux_cool_elems = {}, ---@type { line: Div, enable: Checkbox }[]
|
||||||
|
|
||||||
|
gen_modem_list = function () end
|
||||||
}
|
}
|
||||||
|
|
||||||
---@class svr_config
|
---@class svr_config
|
||||||
@@ -87,6 +93,13 @@ local tmp_cfg = {
|
|||||||
TankFluidTypes = {}, ---@type integer[] which type of fluid each tank in the tank list should be containing
|
TankFluidTypes = {}, ---@type integer[] which type of fluid each tank in the tank list should be containing
|
||||||
AuxiliaryCoolant = {}, ---@type boolean[] if a unit has auxiliary coolant
|
AuxiliaryCoolant = {}, ---@type boolean[] if a unit has auxiliary coolant
|
||||||
ExtChargeIdling = false,
|
ExtChargeIdling = false,
|
||||||
|
WirelessModem = true,
|
||||||
|
WiredModem = false, ---@type string|false
|
||||||
|
PLC_Listen = 1, ---@type LISTEN_MODE
|
||||||
|
RTU_Listen = 1, ---@type LISTEN_MODE
|
||||||
|
CRD_Listen = 1, ---@type LISTEN_MODE
|
||||||
|
PocketEnabled = true,
|
||||||
|
PocketTest = true,
|
||||||
SVR_Channel = nil, ---@type integer
|
SVR_Channel = nil, ---@type integer
|
||||||
PLC_Channel = nil, ---@type integer
|
PLC_Channel = nil, ---@type integer
|
||||||
RTU_Channel = nil, ---@type integer
|
RTU_Channel = nil, ---@type integer
|
||||||
@@ -121,6 +134,13 @@ local fields = {
|
|||||||
{ "TankFluidTypes", "Tank Fluid Types", {} },
|
{ "TankFluidTypes", "Tank Fluid Types", {} },
|
||||||
{ "AuxiliaryCoolant", "Auxiliary Water Coolant", {} },
|
{ "AuxiliaryCoolant", "Auxiliary Water Coolant", {} },
|
||||||
{ "ExtChargeIdling", "Extended Charge Idling", false },
|
{ "ExtChargeIdling", "Extended Charge Idling", false },
|
||||||
|
{ "WirelessModem", "Wireless/Ender Comms Modem", true },
|
||||||
|
{ "WiredModem", "Wired Comms Modem", false },
|
||||||
|
{ "PLC_Listen", "PLC Listen Mode", types.LISTEN_MODE.WIRELESS },
|
||||||
|
{ "RTU_Listen", "RTU Gateway Listen Mode", types.LISTEN_MODE.WIRELESS },
|
||||||
|
{ "CRD_Listen", "Coordinator Listen Mode", types.LISTEN_MODE.WIRELESS },
|
||||||
|
{ "PocketEnabled", "Pocket Connectivity", true },
|
||||||
|
{ "PocketTest", "Pocket Testing Features", true },
|
||||||
{ "SVR_Channel", "SVR Channel", 16240 },
|
{ "SVR_Channel", "SVR Channel", 16240 },
|
||||||
{ "PLC_Channel", "PLC Channel", 16241 },
|
{ "PLC_Channel", "PLC Channel", 16241 },
|
||||||
{ "RTU_Channel", "RTU Channel", 16242 },
|
{ "RTU_Channel", "RTU Channel", 16242 },
|
||||||
@@ -131,7 +151,7 @@ local fields = {
|
|||||||
{ "CRD_Timeout", "CRD Connection Timeout", 5 },
|
{ "CRD_Timeout", "CRD Connection Timeout", 5 },
|
||||||
{ "PKT_Timeout", "PKT Connection Timeout", 5 },
|
{ "PKT_Timeout", "PKT Connection Timeout", 5 },
|
||||||
{ "TrustedRange", "Trusted Range", 0 },
|
{ "TrustedRange", "Trusted Range", 0 },
|
||||||
{ "AuthKey", "Facility Auth Key" , ""},
|
{ "AuthKey", "Facility Auth Key" , "" },
|
||||||
{ "LogMode", "Log Mode", log.MODE.APPEND },
|
{ "LogMode", "Log Mode", log.MODE.APPEND },
|
||||||
{ "LogPath", "Log Path", "/log.txt" },
|
{ "LogPath", "Log Path", "/log.txt" },
|
||||||
{ "LogDebug", "Log Debug Messages", false },
|
{ "LogDebug", "Log Debug Messages", false },
|
||||||
@@ -286,11 +306,14 @@ function configurator.configure(ask_config)
|
|||||||
tool_ctl.has_config = load_settings(ini_cfg)
|
tool_ctl.has_config = load_settings(ini_cfg)
|
||||||
|
|
||||||
-- these need to be initialized as they are used before being set
|
-- these need to be initialized as they are used before being set
|
||||||
|
tmp_cfg.WiredModem = ini_cfg.WiredModem
|
||||||
tmp_cfg.FacilityTankMode = ini_cfg.FacilityTankMode
|
tmp_cfg.FacilityTankMode = ini_cfg.FacilityTankMode
|
||||||
tmp_cfg.TankFluidTypes = { table.unpack(ini_cfg.TankFluidTypes) }
|
tmp_cfg.TankFluidTypes = { table.unpack(ini_cfg.TankFluidTypes) }
|
||||||
|
|
||||||
reset_term()
|
reset_term()
|
||||||
|
|
||||||
|
ppm.mount_all()
|
||||||
|
|
||||||
-- set overridden colors
|
-- set overridden colors
|
||||||
for i = 1, #style.colors do
|
for i = 1, #style.colors do
|
||||||
term.setPaletteColor(style.colors[i].c, style.colors[i].hex)
|
term.setPaletteColor(style.colors[i].c, style.colors[i].hex)
|
||||||
@@ -300,6 +323,8 @@ function configurator.configure(ask_config)
|
|||||||
local display = DisplayBox{window=term.current(),fg_bg=style.root}
|
local display = DisplayBox{window=term.current(),fg_bg=style.root}
|
||||||
config_view(display)
|
config_view(display)
|
||||||
|
|
||||||
|
tool_ctl.gen_modem_list()
|
||||||
|
|
||||||
while true do
|
while true do
|
||||||
local event, param1, param2, param3 = util.pull_event()
|
local event, param1, param2, param3 = util.pull_event()
|
||||||
|
|
||||||
@@ -314,6 +339,14 @@ function configurator.configure(ask_config)
|
|||||||
if k_e then display.handle_key(k_e) end
|
if k_e then display.handle_key(k_e) end
|
||||||
elseif event == "paste" then
|
elseif event == "paste" then
|
||||||
display.handle_paste(param1)
|
display.handle_paste(param1)
|
||||||
|
elseif event == "peripheral_detach" then
|
||||||
|
---@diagnostic disable-next-line: discard-returns
|
||||||
|
ppm.handle_unmount(param1)
|
||||||
|
tool_ctl.gen_modem_list()
|
||||||
|
elseif event == "peripheral" then
|
||||||
|
---@diagnostic disable-next-line: discard-returns
|
||||||
|
ppm.mount(param1)
|
||||||
|
tool_ctl.gen_modem_list()
|
||||||
end
|
end
|
||||||
|
|
||||||
if event == "terminate" then return end
|
if event == "terminate" then return end
|
||||||
|
|||||||
@@ -2,15 +2,12 @@
|
|||||||
-- Data Bus - Central Communication Linking for Supervisor Front Panel
|
-- Data Bus - Central Communication Linking for Supervisor Front Panel
|
||||||
--
|
--
|
||||||
|
|
||||||
local psil = require("scada-common.psil")
|
local const = require("scada-common.constants")
|
||||||
local util = require("scada-common.util")
|
local psil = require("scada-common.psil")
|
||||||
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
local pgi = require("supervisor.panel.pgi")
|
local pgi = require("supervisor.panel.pgi")
|
||||||
|
|
||||||
-- nominal RTT is ping (0ms to 10ms usually) + 150ms for SV main loop tick
|
|
||||||
local WARN_RTT = 300 -- 2x as long as expected w/ 0 ping
|
|
||||||
local HIGH_RTT = 500 -- 3.33x as long as expected w/ 0 ping
|
|
||||||
|
|
||||||
local databus = {}
|
local databus = {}
|
||||||
|
|
||||||
-- databus PSIL
|
-- databus PSIL
|
||||||
@@ -19,7 +16,7 @@ databus.ps = psil.create()
|
|||||||
-- call to toggle heartbeat signal
|
-- call to toggle heartbeat signal
|
||||||
function databus.heartbeat() databus.ps.toggle("heartbeat") end
|
function databus.heartbeat() databus.ps.toggle("heartbeat") end
|
||||||
|
|
||||||
-- transmit firmware versions across the bus
|
-- transmit firmware versions
|
||||||
---@param sv_v string supervisor version
|
---@param sv_v string supervisor version
|
||||||
---@param comms_v string comms version
|
---@param comms_v string comms version
|
||||||
function databus.tx_versions(sv_v, comms_v)
|
function databus.tx_versions(sv_v, comms_v)
|
||||||
@@ -27,10 +24,16 @@ function databus.tx_versions(sv_v, comms_v)
|
|||||||
databus.ps.publish("comms_version", comms_v)
|
databus.ps.publish("comms_version", comms_v)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- transmit hardware status for modem connection state
|
-- transmit hardware status for the wired comms modem
|
||||||
---@param has_modem boolean
|
---@param has_modem boolean
|
||||||
function databus.tx_hw_modem(has_modem)
|
function databus.tx_hw_wd_modem(has_modem)
|
||||||
databus.ps.publish("has_modem", has_modem)
|
databus.ps.publish("has_wd_modem", has_modem)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- transmit hardware status for the wireless comms modem
|
||||||
|
---@param has_modem boolean
|
||||||
|
function databus.tx_hw_wl_modem(has_modem)
|
||||||
|
databus.ps.publish("has_wl_modem", has_modem)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- transmit PLC firmware version and session connection state
|
-- transmit PLC firmware version and session connection state
|
||||||
@@ -59,9 +62,9 @@ end
|
|||||||
function databus.tx_plc_rtt(reactor_id, rtt)
|
function databus.tx_plc_rtt(reactor_id, rtt)
|
||||||
databus.ps.publish("plc_" .. reactor_id .. "_rtt", rtt)
|
databus.ps.publish("plc_" .. reactor_id .. "_rtt", rtt)
|
||||||
|
|
||||||
if rtt > HIGH_RTT then
|
if rtt > const.HIGH_RTT then
|
||||||
databus.ps.publish("plc_" .. reactor_id .. "_rtt_color", colors.red)
|
databus.ps.publish("plc_" .. reactor_id .. "_rtt_color", colors.red)
|
||||||
elseif rtt > WARN_RTT then
|
elseif rtt > const.WARN_RTT then
|
||||||
databus.ps.publish("plc_" .. reactor_id .. "_rtt_color", colors.yellow_hc)
|
databus.ps.publish("plc_" .. reactor_id .. "_rtt_color", colors.yellow_hc)
|
||||||
else
|
else
|
||||||
databus.ps.publish("plc_" .. reactor_id .. "_rtt_color", colors.green_hc)
|
databus.ps.publish("plc_" .. reactor_id .. "_rtt_color", colors.green_hc)
|
||||||
@@ -90,9 +93,9 @@ end
|
|||||||
function databus.tx_rtu_rtt(session_id, rtt)
|
function databus.tx_rtu_rtt(session_id, rtt)
|
||||||
databus.ps.publish("rtu_" .. session_id .. "_rtt", rtt)
|
databus.ps.publish("rtu_" .. session_id .. "_rtt", rtt)
|
||||||
|
|
||||||
if rtt > HIGH_RTT then
|
if rtt > const.HIGH_RTT then
|
||||||
databus.ps.publish("rtu_" .. session_id .. "_rtt_color", colors.red)
|
databus.ps.publish("rtu_" .. session_id .. "_rtt_color", colors.red)
|
||||||
elseif rtt > WARN_RTT then
|
elseif rtt > const.WARN_RTT then
|
||||||
databus.ps.publish("rtu_" .. session_id .. "_rtt_color", colors.yellow_hc)
|
databus.ps.publish("rtu_" .. session_id .. "_rtt_color", colors.yellow_hc)
|
||||||
else
|
else
|
||||||
databus.ps.publish("rtu_" .. session_id .. "_rtt_color", colors.green_hc)
|
databus.ps.publish("rtu_" .. session_id .. "_rtt_color", colors.green_hc)
|
||||||
@@ -129,9 +132,9 @@ end
|
|||||||
function databus.tx_crd_rtt(rtt)
|
function databus.tx_crd_rtt(rtt)
|
||||||
databus.ps.publish("crd_rtt", rtt)
|
databus.ps.publish("crd_rtt", rtt)
|
||||||
|
|
||||||
if rtt > HIGH_RTT then
|
if rtt > const.HIGH_RTT then
|
||||||
databus.ps.publish("crd_rtt_color", colors.red)
|
databus.ps.publish("crd_rtt_color", colors.red)
|
||||||
elseif rtt > WARN_RTT then
|
elseif rtt > const.WARN_RTT then
|
||||||
databus.ps.publish("crd_rtt_color", colors.yellow_hc)
|
databus.ps.publish("crd_rtt_color", colors.yellow_hc)
|
||||||
else
|
else
|
||||||
databus.ps.publish("crd_rtt_color", colors.green_hc)
|
databus.ps.publish("crd_rtt_color", colors.green_hc)
|
||||||
@@ -160,20 +163,13 @@ end
|
|||||||
function databus.tx_pdg_rtt(session_id, rtt)
|
function databus.tx_pdg_rtt(session_id, rtt)
|
||||||
databus.ps.publish("pdg_" .. session_id .. "_rtt", rtt)
|
databus.ps.publish("pdg_" .. session_id .. "_rtt", rtt)
|
||||||
|
|
||||||
if rtt > HIGH_RTT then
|
if rtt > const.HIGH_RTT then
|
||||||
databus.ps.publish("pdg_" .. session_id .. "_rtt_color", colors.red)
|
databus.ps.publish("pdg_" .. session_id .. "_rtt_color", colors.red)
|
||||||
elseif rtt > WARN_RTT then
|
elseif rtt > const.WARN_RTT then
|
||||||
databus.ps.publish("pdg_" .. session_id .. "_rtt_color", colors.yellow_hc)
|
databus.ps.publish("pdg_" .. session_id .. "_rtt_color", colors.yellow_hc)
|
||||||
else
|
else
|
||||||
databus.ps.publish("pdg_" .. session_id .. "_rtt_color", colors.green_hc)
|
databus.ps.publish("pdg_" .. session_id .. "_rtt_color", colors.green_hc)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- link a function to receive data from the bus
|
|
||||||
---@param field string field name
|
|
||||||
---@param func function function to link
|
|
||||||
function databus.rx_field(field, func)
|
|
||||||
databus.ps.subscribe(field, func)
|
|
||||||
end
|
|
||||||
|
|
||||||
return databus
|
return databus
|
||||||
|
|||||||
@@ -2,13 +2,19 @@ local log = require("scada-common.log")
|
|||||||
local types = require("scada-common.types")
|
local types = require("scada-common.types")
|
||||||
local util = require("scada-common.util")
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
|
local alarm_ctl = require("supervisor.alarm_ctl")
|
||||||
local unit = require("supervisor.unit")
|
local unit = require("supervisor.unit")
|
||||||
local fac_update = require("supervisor.facility_update")
|
local fac_update = require("supervisor.facility_update")
|
||||||
|
|
||||||
local rsctl = require("supervisor.session.rsctl")
|
local rsctl = require("supervisor.session.rsctl")
|
||||||
local svsessions = require("supervisor.session.svsessions")
|
local svsessions = require("supervisor.session.svsessions")
|
||||||
|
|
||||||
|
local AISTATE = alarm_ctl.AISTATE
|
||||||
|
|
||||||
|
local ALARM = types.ALARM
|
||||||
|
local ALARM_STATE = types.ALARM_STATE
|
||||||
local AUTO_GROUP = types.AUTO_GROUP
|
local AUTO_GROUP = types.AUTO_GROUP
|
||||||
|
local PRIO = types.ALARM_PRIORITY
|
||||||
local PROCESS = types.PROCESS
|
local PROCESS = types.PROCESS
|
||||||
local RTU_ID_FAIL = types.RTU_ID_FAIL
|
local RTU_ID_FAIL = types.RTU_ID_FAIL
|
||||||
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
|
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
|
||||||
@@ -138,7 +144,17 @@ function facility.new(config)
|
|||||||
imtx_last_charge = 0,
|
imtx_last_charge = 0,
|
||||||
imtx_last_charge_t = 0,
|
imtx_last_charge_t = 0,
|
||||||
-- track faulted induction matrix update times to reject
|
-- track faulted induction matrix update times to reject
|
||||||
imtx_faulted_times = { 0, 0, 0 }
|
imtx_faulted_times = { 0, 0, 0 },
|
||||||
|
-- facility alarms
|
||||||
|
---@type { [string]: alarm_def }
|
||||||
|
alarms = {
|
||||||
|
-- radiation monitor alarm for the facility
|
||||||
|
FacilityRadiation = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 0, id = ALARM.FacilityRadiation, tier = PRIO.CRITICAL },
|
||||||
|
},
|
||||||
|
---@type { [ALARM]: ALARM_STATE }
|
||||||
|
alarm_states = {
|
||||||
|
[ALARM.FacilityRadiation] = ALARM_STATE.INACTIVE
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
--#region SETUP
|
--#region SETUP
|
||||||
@@ -157,7 +173,7 @@ function facility.new(config)
|
|||||||
self.rtu_list = { self.redstone, self.induction, self.sps, self.tanks, self.envd }
|
self.rtu_list = { self.redstone, self.induction, self.sps, self.tanks, self.envd }
|
||||||
|
|
||||||
-- init redstone RTU I/O controller
|
-- init redstone RTU I/O controller
|
||||||
self.io_ctl = rsctl.new(self.redstone)
|
self.io_ctl = rsctl.new(self.redstone, 0)
|
||||||
|
|
||||||
-- fill blank alarm/tone states
|
-- fill blank alarm/tone states
|
||||||
for _ = 1, 12 do table.insert(self.test_alarm_states, false) end
|
for _ = 1, 12 do table.insert(self.test_alarm_states, false) end
|
||||||
@@ -335,6 +351,9 @@ function facility.new(config)
|
|||||||
-- unit tasks
|
-- unit tasks
|
||||||
f_update.unit_mgmt()
|
f_update.unit_mgmt()
|
||||||
|
|
||||||
|
-- update alarm states right before updating the audio
|
||||||
|
f_update.update_alarms()
|
||||||
|
|
||||||
-- update alarm tones
|
-- update alarm tones
|
||||||
f_update.alarm_audio()
|
f_update.alarm_audio()
|
||||||
end
|
end
|
||||||
@@ -404,10 +423,14 @@ function facility.new(config)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- ack all alarms on all reactor units
|
-- ack all alarms on all reactor units and the facility
|
||||||
function public.ack_all()
|
function public.ack_all()
|
||||||
for i = 1, #self.units do
|
-- unit alarms
|
||||||
self.units[i].ack_all()
|
for i = 1, #self.units do self.units[i].ack_all() end
|
||||||
|
|
||||||
|
-- facility alarms
|
||||||
|
for id, state in pairs(self.alarm_states) do
|
||||||
|
if state == ALARM_STATE.TRIPPED then self.alarm_states[id] = ALARM_STATE.ACKED end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ local rsio = require("scada-common.rsio")
|
|||||||
local types = require("scada-common.types")
|
local types = require("scada-common.types")
|
||||||
local util = require("scada-common.util")
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
|
local alarm_ctl = require("supervisor.alarm_ctl")
|
||||||
|
|
||||||
local plc = require("supervisor.session.plc")
|
local plc = require("supervisor.session.plc")
|
||||||
local svsessions = require("supervisor.session.svsessions")
|
local svsessions = require("supervisor.session.svsessions")
|
||||||
|
|
||||||
@@ -643,7 +645,7 @@ function update.auto_safety()
|
|||||||
end
|
end
|
||||||
|
|
||||||
if (self.mode ~= PROCESS.INACTIVE) and (self.mode ~= PROCESS.SYSTEM_ALARM_IDLE) then
|
if (self.mode ~= PROCESS.INACTIVE) and (self.mode ~= PROCESS.SYSTEM_ALARM_IDLE) then
|
||||||
local scram = astatus.matrix_fault or astatus.matrix_fill or astatus.crit_alarm or astatus.gen_fault
|
local scram = astatus.matrix_fault or astatus.matrix_fill or astatus.crit_alarm or astatus.radiation or astatus.gen_fault
|
||||||
|
|
||||||
if scram and not self.ascram then
|
if scram and not self.ascram then
|
||||||
-- SCRAM all units
|
-- SCRAM all units
|
||||||
@@ -714,11 +716,17 @@ function update.post_auto()
|
|||||||
self.mode = next_mode
|
self.mode = next_mode
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- update facility alarm states
|
||||||
|
function update.update_alarms()
|
||||||
|
-- Facility Radiation
|
||||||
|
alarm_ctl.update_alarm_state("FAC", self.alarm_states, self.ascram_status.radiation, self.alarms.FacilityRadiation, true)
|
||||||
|
end
|
||||||
|
|
||||||
-- update alarm audio control
|
-- update alarm audio control
|
||||||
function update.alarm_audio()
|
function update.alarm_audio()
|
||||||
local allow_test = self.allow_testing and self.test_tone_set
|
local allow_test = self.allow_testing and self.test_tone_set
|
||||||
|
|
||||||
local alarms = { false, false, false, false, false, false, false, false, false, false, false, false }
|
local alarms = { false, false, false, false, false, false, false, false, false, false, false, false, false }
|
||||||
|
|
||||||
-- reset tone states before re-evaluting
|
-- reset tone states before re-evaluting
|
||||||
for i = 1, #self.tone_states do self.tone_states[i] = false end
|
for i = 1, #self.tone_states do self.tone_states[i] = false end
|
||||||
@@ -734,8 +742,11 @@ function update.alarm_audio()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- record facility alarms
|
||||||
|
alarms[ALARM.FacilityRadiation] = self.alarm_states[ALARM.FacilityRadiation] == ALARM_STATE.TRIPPED
|
||||||
|
|
||||||
|
-- clear testing alarms if we aren't using them
|
||||||
if not self.test_tone_reset then
|
if not self.test_tone_reset then
|
||||||
-- clear testing alarms if we aren't using them
|
|
||||||
for i = 1, #self.test_alarm_states do self.test_alarm_states[i] = false end
|
for i = 1, #self.test_alarm_states do self.test_alarm_states[i] = false end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -774,7 +785,7 @@ function update.alarm_audio()
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- radiation is a big concern, always play this CRITICAL level alarm if active
|
-- radiation is a big concern, always play this CRITICAL level alarm if active
|
||||||
if alarms[ALARM.ContainmentRadiation] then
|
if alarms[ALARM.ContainmentRadiation] or alarms[ALARM.FacilityRadiation] then
|
||||||
self.tone_states[TONE.T_800Hz_1000Hz_Alt] = true
|
self.tone_states[TONE.T_800Hz_1000Hz_Alt] = true
|
||||||
-- we are going to disable the RPS trip alarm audio due to conflict, and if it was enabled
|
-- we are going to disable the RPS trip alarm audio due to conflict, and if it was enabled
|
||||||
-- then we can re-enable the reactor lost alarm audio since it doesn't painfully combine with this one
|
-- then we can re-enable the reactor lost alarm audio since it doesn't painfully combine with this one
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ local core = require("graphics.core")
|
|||||||
local Div = require("graphics.elements.Div")
|
local Div = require("graphics.elements.Div")
|
||||||
local ListBox = require("graphics.elements.ListBox")
|
local ListBox = require("graphics.elements.ListBox")
|
||||||
local MultiPane = require("graphics.elements.MultiPane")
|
local MultiPane = require("graphics.elements.MultiPane")
|
||||||
|
local Rectangle = require("graphics.elements.Rectangle")
|
||||||
local TextBox = require("graphics.elements.TextBox")
|
local TextBox = require("graphics.elements.TextBox")
|
||||||
|
|
||||||
local TabBar = require("graphics.elements.controls.TabBar")
|
local TabBar = require("graphics.elements.controls.TabBar")
|
||||||
@@ -29,12 +30,14 @@ local DataIndicator = require("graphics.elements.indicators.DataIndicator")
|
|||||||
local ALIGN = core.ALIGN
|
local ALIGN = core.ALIGN
|
||||||
|
|
||||||
local cpair = core.cpair
|
local cpair = core.cpair
|
||||||
|
local border = core.border
|
||||||
|
|
||||||
local ind_grn = style.ind_grn
|
local ind_grn = style.ind_grn
|
||||||
|
|
||||||
-- create new front panel view
|
-- create new front panel view
|
||||||
---@param panel DisplayBox main displaybox
|
---@param panel DisplayBox main displaybox
|
||||||
local function init(panel)
|
---@param config svr_config configuraiton
|
||||||
|
local function init(panel, config)
|
||||||
local s_hi_box = style.theme.highlight_box
|
local s_hi_box = style.theme.highlight_box
|
||||||
local s_hi_bright = style.theme.highlight_box_bright
|
local s_hi_bright = style.theme.highlight_box_bright
|
||||||
|
|
||||||
@@ -53,7 +56,7 @@ local function init(panel)
|
|||||||
|
|
||||||
local main_page = Div{parent=page_div,x=1,y=1}
|
local main_page = Div{parent=page_div,x=1,y=1}
|
||||||
|
|
||||||
local system = Div{parent=main_page,width=14,height=17,x=2,y=2}
|
local system = Div{parent=main_page,width=18,height=17,x=2,y=2}
|
||||||
|
|
||||||
local on = LED{parent=system,label="STATUS",colors=cpair(colors.green,colors.red)}
|
local on = LED{parent=system,label="STATUS",colors=cpair(colors.green,colors.red)}
|
||||||
local heartbeat = LED{parent=system,label="HEARTBEAT",colors=ind_grn}
|
local heartbeat = LED{parent=system,label="HEARTBEAT",colors=ind_grn}
|
||||||
@@ -62,25 +65,28 @@ local function init(panel)
|
|||||||
|
|
||||||
heartbeat.register(databus.ps, "heartbeat", heartbeat.update)
|
heartbeat.register(databus.ps, "heartbeat", heartbeat.update)
|
||||||
|
|
||||||
local modem = LED{parent=system,label="MODEM",colors=ind_grn}
|
if config.WirelessModem and config.WiredModem then
|
||||||
system.line_break()
|
local wd_modem = LED{parent=system,label="WD MODEM",colors=ind_grn}
|
||||||
|
local wl_modem = LED{parent=system,label="WL MODEM",colors=ind_grn}
|
||||||
|
wd_modem.register(databus.ps, "has_wd_modem", wd_modem.update)
|
||||||
|
wl_modem.register(databus.ps, "has_wl_modem", wl_modem.update)
|
||||||
|
else
|
||||||
|
local modem = LED{parent=system,label="MODEM",colors=ind_grn}
|
||||||
|
modem.register(databus.ps, util.trinary(config.WirelessModem, "has_wl_modem", "has_wd_modem"), modem.update)
|
||||||
|
end
|
||||||
|
|
||||||
modem.register(databus.ps, "has_modem", modem.update)
|
--
|
||||||
|
-- hardware labeling
|
||||||
|
--
|
||||||
|
|
||||||
|
local hw_labels = Rectangle{parent=main_page,x=2,y=term_h-7,width=14,height=5,border=border(1,s_hi_box.bkg,true),even_inner=true}
|
||||||
|
|
||||||
---@diagnostic disable-next-line: undefined-field
|
---@diagnostic disable-next-line: undefined-field
|
||||||
local comp_id = util.sprintf("(%d)", os.getComputerID())
|
local comp_id = util.sprintf("%03d", os.getComputerID())
|
||||||
TextBox{parent=system,x=9,y=4,width=6,text=comp_id,fg_bg=style.fp.disabled_fg}
|
|
||||||
|
|
||||||
--
|
TextBox{parent=hw_labels,text="FW "..databus.ps.get("version"),fg_bg=s_hi_box}
|
||||||
-- about footer
|
TextBox{parent=hw_labels,text="NT v"..databus.ps.get("comms_version"),fg_bg=s_hi_box}
|
||||||
--
|
TextBox{parent=hw_labels,text="SN "..comp_id.."-SVR",fg_bg=s_hi_box}
|
||||||
|
|
||||||
local about = Div{parent=main_page,width=15,height=2,y=term_h-3,fg_bg=style.fp.disabled_fg}
|
|
||||||
local fw_v = TextBox{parent=about,text="FW: v00.00.00"}
|
|
||||||
local comms_v = TextBox{parent=about,text="NT: v00.00.00"}
|
|
||||||
|
|
||||||
fw_v.register(databus.ps, "version", function (version) fw_v.set_value(util.c("FW: ", version)) end)
|
|
||||||
comms_v.register(databus.ps, "comms_version", function (version) comms_v.set_value(util.c("NT: v", version)) end)
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- page handling
|
-- page handling
|
||||||
|
|||||||
@@ -19,15 +19,14 @@ local ui = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
-- try to start the UI
|
-- try to start the UI
|
||||||
---@param theme FP_THEME front panel theme
|
---@param config svr_config configuration
|
||||||
---@param color_mode COLOR_MODE color mode
|
|
||||||
---@return boolean success, any error_msg
|
---@return boolean success, any error_msg
|
||||||
function renderer.try_start_ui(theme, color_mode)
|
function renderer.try_start_ui(config)
|
||||||
local status, msg = true, nil
|
local status, msg = true, nil
|
||||||
|
|
||||||
if ui.display == nil then
|
if ui.display == nil then
|
||||||
-- set theme
|
-- set theme
|
||||||
style.set_theme(theme, color_mode)
|
style.set_theme(config.FrontPanelTheme, config.ColorMode)
|
||||||
|
|
||||||
-- reset terminal
|
-- reset terminal
|
||||||
term.setTextColor(colors.white)
|
term.setTextColor(colors.white)
|
||||||
@@ -41,7 +40,7 @@ function renderer.try_start_ui(theme, color_mode)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- apply color mode
|
-- apply color mode
|
||||||
local c_mode_overrides = style.theme.color_modes[color_mode]
|
local c_mode_overrides = style.theme.color_modes[config.ColorMode]
|
||||||
for i = 1, #c_mode_overrides do
|
for i = 1, #c_mode_overrides do
|
||||||
term.setPaletteColor(c_mode_overrides[i].c, c_mode_overrides[i].hex)
|
term.setPaletteColor(c_mode_overrides[i].c, c_mode_overrides[i].hex)
|
||||||
end
|
end
|
||||||
@@ -49,7 +48,7 @@ function renderer.try_start_ui(theme, color_mode)
|
|||||||
-- init front panel view
|
-- init front panel view
|
||||||
status, msg = pcall(function ()
|
status, msg = pcall(function ()
|
||||||
ui.display = DisplayBox{window=term.current(),fg_bg=style.fp.root}
|
ui.display = DisplayBox{window=term.current(),fg_bg=style.fp.root}
|
||||||
panel_view(ui.display)
|
panel_view(ui.display, config)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
if status then
|
if status then
|
||||||
|
|||||||
@@ -2,10 +2,12 @@ local comms = require("scada-common.comms")
|
|||||||
local log = require("scada-common.log")
|
local log = require("scada-common.log")
|
||||||
local mqueue = require("scada-common.mqueue")
|
local mqueue = require("scada-common.mqueue")
|
||||||
local util = require("scada-common.util")
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
local databus = require("supervisor.databus")
|
local databus = require("supervisor.databus")
|
||||||
|
|
||||||
local pocket = {}
|
local pocket = {}
|
||||||
|
|
||||||
|
local DEV_TYPE = comms.DEVICE_TYPE
|
||||||
local PROTOCOL = comms.PROTOCOL
|
local PROTOCOL = comms.PROTOCOL
|
||||||
local MGMT_TYPE = comms.MGMT_TYPE
|
local MGMT_TYPE = comms.MGMT_TYPE
|
||||||
|
|
||||||
@@ -34,9 +36,11 @@ local PERIODICS = {
|
|||||||
---@param in_queue mqueue in message queue
|
---@param in_queue mqueue in message queue
|
||||||
---@param out_queue mqueue out message queue
|
---@param out_queue mqueue out message queue
|
||||||
---@param timeout number communications timeout
|
---@param timeout number communications timeout
|
||||||
|
---@param sessions svsessions_list list of computer sessions, read-only
|
||||||
---@param facility facility facility data table
|
---@param facility facility facility data table
|
||||||
---@param fp_ok boolean if the front panel UI is running
|
---@param fp_ok boolean if the front panel UI is running
|
||||||
function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout, facility, fp_ok)
|
---@param allow_test boolean if this should allow pocket testing commands
|
||||||
|
function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout, sessions, facility, fp_ok, allow_test)
|
||||||
-- print a log message to the terminal as long as the UI isn't running
|
-- print a log message to the terminal as long as the UI isn't running
|
||||||
local function println(message) if not fp_ok then util.println_ts(message) end end
|
local function println(message) if not fp_ok then util.println_ts(message) end end
|
||||||
|
|
||||||
@@ -140,7 +144,7 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout,
|
|||||||
local valid = false
|
local valid = false
|
||||||
|
|
||||||
-- attempt to set a tone state
|
-- attempt to set a tone state
|
||||||
if pkt.scada_frame.is_authenticated() then
|
if allow_test then
|
||||||
if pkt.length == 2 then
|
if pkt.length == 2 then
|
||||||
if type(pkt.data[1]) == "number" and type(pkt.data[2]) == "boolean" then
|
if type(pkt.data[1]) == "number" and type(pkt.data[2]) == "boolean" then
|
||||||
valid = true
|
valid = true
|
||||||
@@ -148,22 +152,16 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout,
|
|||||||
-- try to set tone states, then send back if testing is allowed
|
-- try to set tone states, then send back if testing is allowed
|
||||||
local allow_testing, test_tone_states = facility.diag_set_test_tone(pkt.data[1], pkt.data[2])
|
local allow_testing, test_tone_states = facility.diag_set_test_tone(pkt.data[1], pkt.data[2])
|
||||||
_send_mgmt(MGMT_TYPE.DIAG_TONE_SET, { allow_testing, test_tone_states })
|
_send_mgmt(MGMT_TYPE.DIAG_TONE_SET, { allow_testing, test_tone_states })
|
||||||
else
|
else log.debug(log_tag .. "SCADA diag tone set packet data type mismatch") end
|
||||||
log.debug(log_tag .. "SCADA diag tone set packet data type mismatch")
|
else log.debug(log_tag .. "SCADA diag tone set packet length mismatch") end
|
||||||
end
|
else log.warning(log_tag .. "DIAG_TONE_SET is blocked without pocket test commands enabled") end
|
||||||
else
|
|
||||||
log.debug(log_tag .. "SCADA diag tone set packet length mismatch")
|
|
||||||
end
|
|
||||||
else
|
|
||||||
log.debug(log_tag .. "DIAG_TONE_SET is blocked without HMAC for security")
|
|
||||||
end
|
|
||||||
|
|
||||||
if not valid then _send_mgmt(MGMT_TYPE.DIAG_TONE_SET, { false }) end
|
if not valid then _send_mgmt(MGMT_TYPE.DIAG_TONE_SET, { false }) end
|
||||||
elseif pkt.type == MGMT_TYPE.DIAG_ALARM_SET then
|
elseif pkt.type == MGMT_TYPE.DIAG_ALARM_SET then
|
||||||
local valid = false
|
local valid = false
|
||||||
|
|
||||||
-- attempt to set an alarm state
|
-- attempt to set an alarm state
|
||||||
if pkt.scada_frame.is_authenticated() then
|
if allow_test then
|
||||||
if pkt.length == 2 then
|
if pkt.length == 2 then
|
||||||
if type(pkt.data[1]) == "number" and type(pkt.data[2]) == "boolean" then
|
if type(pkt.data[1]) == "number" and type(pkt.data[2]) == "boolean" then
|
||||||
valid = true
|
valid = true
|
||||||
@@ -171,17 +169,52 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout,
|
|||||||
-- try to set alarm states, then send back if testing is allowed
|
-- try to set alarm states, then send back if testing is allowed
|
||||||
local allow_testing, test_alarm_states = facility.diag_set_test_alarm(pkt.data[1], pkt.data[2])
|
local allow_testing, test_alarm_states = facility.diag_set_test_alarm(pkt.data[1], pkt.data[2])
|
||||||
_send_mgmt(MGMT_TYPE.DIAG_ALARM_SET, { allow_testing, test_alarm_states })
|
_send_mgmt(MGMT_TYPE.DIAG_ALARM_SET, { allow_testing, test_alarm_states })
|
||||||
else
|
else log.debug(log_tag .. "SCADA diag alarm set packet data type mismatch") end
|
||||||
log.debug(log_tag .. "SCADA diag alarm set packet data type mismatch")
|
else log.debug(log_tag .. "SCADA diag alarm set packet length mismatch") end
|
||||||
end
|
else log.warning(log_tag .. "DIAG_ALARM_SET is blocked without pocket test commands enabled") end
|
||||||
else
|
|
||||||
log.debug(log_tag .. "SCADA diag alarm set packet length mismatch")
|
|
||||||
end
|
|
||||||
else
|
|
||||||
log.debug(log_tag .. "DIAG_ALARM_SET is blocked without HMAC for security")
|
|
||||||
end
|
|
||||||
|
|
||||||
if not valid then _send_mgmt(MGMT_TYPE.DIAG_ALARM_SET, { false }) end
|
if not valid then _send_mgmt(MGMT_TYPE.DIAG_ALARM_SET, { false }) end
|
||||||
|
elseif pkt.type == MGMT_TYPE.INFO_LIST_CMP then
|
||||||
|
local get = databus.ps.get
|
||||||
|
|
||||||
|
---@diagnostic disable-next-line: undefined-field
|
||||||
|
local devices = { { DEV_TYPE.SVR, os.getComputerID(), get("version"), 0 } }
|
||||||
|
|
||||||
|
-- add the coordinator if connected
|
||||||
|
if get("crd_conn") then
|
||||||
|
table.insert(devices, { DEV_TYPE.CRD, get("crd_addr"), get("crd_fw"), get("crd_rtt") })
|
||||||
|
end
|
||||||
|
|
||||||
|
-- add the PLCs if connected
|
||||||
|
for i = 1, #facility.get_units() do
|
||||||
|
local tag = "plc_" .. i
|
||||||
|
|
||||||
|
local addr = -1
|
||||||
|
for _, s in ipairs(sessions.plc) do
|
||||||
|
if s.reactor == i then
|
||||||
|
addr = s.s_addr
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if get(tag .. "_conn") then
|
||||||
|
table.insert(devices, { DEV_TYPE.PLC, addr, get(tag .. "_fw"), get(tag .. "_rtt"), i })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- add connected RTUs
|
||||||
|
for i = 1, #sessions.rtu do
|
||||||
|
local s = sessions.rtu[i]
|
||||||
|
table.insert(devices, { DEV_TYPE.RTU, s.s_addr, s.version, get("rtu_" .. s.instance.get_id() .. "_rtt") })
|
||||||
|
end
|
||||||
|
|
||||||
|
-- add connected pocket computers
|
||||||
|
for i = 1, #sessions.pdg do
|
||||||
|
local s = sessions.pdg[i]
|
||||||
|
table.insert(devices, { DEV_TYPE.PKT, s.s_addr, s.version, get("pdg_" .. s.instance.get_id() .. "_rtt") })
|
||||||
|
end
|
||||||
|
|
||||||
|
_send_mgmt(MGMT_TYPE.INFO_LIST_CMP, devices)
|
||||||
else
|
else
|
||||||
log.debug(log_tag .. "handler received unsupported SCADA_MGMT packet type " .. pkt.type)
|
log.debug(log_tag .. "handler received unsupported SCADA_MGMT packet type " .. pkt.type)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ local rsctl = {}
|
|||||||
-- create a new redstone RTU I/O controller
|
-- create a new redstone RTU I/O controller
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
---@param redstone_rtus redstone_session[] redstone RTU sessions
|
---@param redstone_rtus redstone_session[] redstone RTU sessions
|
||||||
function rsctl.new(redstone_rtus)
|
---@param bank integer I/O bank (unit/facility assignment) to interface with
|
||||||
|
function rsctl.new(redstone_rtus, bank)
|
||||||
---@class rs_controller
|
---@class rs_controller
|
||||||
local public = {}
|
local public = {}
|
||||||
|
|
||||||
@@ -18,7 +19,7 @@ function rsctl.new(redstone_rtus)
|
|||||||
---@return boolean
|
---@return boolean
|
||||||
function public.is_connected(port)
|
function public.is_connected(port)
|
||||||
for i = 1, #redstone_rtus do
|
for i = 1, #redstone_rtus do
|
||||||
if redstone_rtus[i].get_db().io[port] ~= nil then return true end
|
if redstone_rtus[i].get_db().io[bank][port] ~= nil then return true end
|
||||||
end
|
end
|
||||||
|
|
||||||
return false
|
return false
|
||||||
@@ -29,7 +30,7 @@ function rsctl.new(redstone_rtus)
|
|||||||
---@param value boolean
|
---@param value boolean
|
||||||
function public.digital_write(port, value)
|
function public.digital_write(port, value)
|
||||||
for i = 1, #redstone_rtus do
|
for i = 1, #redstone_rtus do
|
||||||
local io = redstone_rtus[i].get_db().io[port]
|
local io = redstone_rtus[i].get_db().io[bank][port]
|
||||||
if io ~= nil then io.write(value) end
|
if io ~= nil then io.write(value) end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -40,7 +41,7 @@ function rsctl.new(redstone_rtus)
|
|||||||
---@return boolean|nil
|
---@return boolean|nil
|
||||||
function public.digital_read(port)
|
function public.digital_read(port)
|
||||||
for i = 1, #redstone_rtus do
|
for i = 1, #redstone_rtus do
|
||||||
local io = redstone_rtus[i].get_db().io[port]
|
local io = redstone_rtus[i].get_db().io[bank][port]
|
||||||
if io ~= nil then return io.read() --[[@as boolean|nil]] end
|
if io ~= nil then return io.read() --[[@as boolean|nil]] end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -52,7 +53,7 @@ function rsctl.new(redstone_rtus)
|
|||||||
---@param max number maximum value for scaling 0 to 15
|
---@param max number maximum value for scaling 0 to 15
|
||||||
function public.analog_write(port, value, min, max)
|
function public.analog_write(port, value, min, max)
|
||||||
for i = 1, #redstone_rtus do
|
for i = 1, #redstone_rtus do
|
||||||
local io = redstone_rtus[i].get_db().io[port]
|
local io = redstone_rtus[i].get_db().io[bank][port]
|
||||||
if io ~= nil then io.write(rsio.analog_write(value, min, max)) end
|
if io ~= nil then io.write(rsio.analog_write(value, min, max)) end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ function rtu.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout, ad
|
|||||||
type = self.advert[i][1],
|
type = self.advert[i][1],
|
||||||
index = self.advert[i][2],
|
index = self.advert[i][2],
|
||||||
reactor = self.advert[i][3],
|
reactor = self.advert[i][3],
|
||||||
rsio = self.advert[i][4]
|
rs_conns = self.advert[i][4]
|
||||||
}
|
}
|
||||||
|
|
||||||
local u_type = unit_advert.type ---@type RTU_UNIT_TYPE|boolean
|
local u_type = unit_advert.type ---@type RTU_UNIT_TYPE|boolean
|
||||||
@@ -104,14 +104,17 @@ function rtu.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout, ad
|
|||||||
advert_validator.assert(util.is_int(unit_advert.index) or (unit_advert.index == false))
|
advert_validator.assert(util.is_int(unit_advert.index) or (unit_advert.index == false))
|
||||||
advert_validator.assert_type_int(unit_advert.reactor)
|
advert_validator.assert_type_int(unit_advert.reactor)
|
||||||
|
|
||||||
if u_type == RTU_UNIT_TYPE.REDSTONE then
|
|
||||||
advert_validator.assert_type_table(unit_advert.rsio)
|
|
||||||
end
|
|
||||||
|
|
||||||
if advert_validator.valid() then
|
if advert_validator.valid() then
|
||||||
if util.is_int(unit_advert.index) then advert_validator.assert_min(unit_advert.index, 1) end
|
if util.is_int(unit_advert.index) then advert_validator.assert_min(unit_advert.index, 1) end
|
||||||
advert_validator.assert_min(unit_advert.reactor, 0)
|
|
||||||
advert_validator.assert_max(unit_advert.reactor, #self.fac_units)
|
if (unit_advert.reactor == -1) or (u_type == RTU_UNIT_TYPE.REDSTONE) then
|
||||||
|
advert_validator.assert((unit_advert.reactor == -1) and (u_type == RTU_UNIT_TYPE.REDSTONE))
|
||||||
|
advert_validator.assert_type_table(unit_advert.rs_conns)
|
||||||
|
else
|
||||||
|
advert_validator.assert_min(unit_advert.reactor, 0)
|
||||||
|
advert_validator.assert_max(unit_advert.reactor, #self.fac_units)
|
||||||
|
end
|
||||||
|
|
||||||
if not advert_validator.valid() then u_type = false end
|
if not advert_validator.valid() then u_type = false end
|
||||||
else
|
else
|
||||||
u_type = false
|
u_type = false
|
||||||
@@ -126,15 +129,34 @@ function rtu.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout, ad
|
|||||||
-- validation fail
|
-- validation fail
|
||||||
log.debug(log_tag .. "_handle_advertisement(): advertisement unit validation failure")
|
log.debug(log_tag .. "_handle_advertisement(): advertisement unit validation failure")
|
||||||
else
|
else
|
||||||
if unit_advert.reactor > 0 then
|
if unit_advert.reactor == -1 then
|
||||||
local target_unit = self.fac_units[unit_advert.reactor]
|
-- redstone RTUs can be used in multiple different assignments
|
||||||
|
|
||||||
-- unit RTUs
|
|
||||||
if u_type == RTU_UNIT_TYPE.REDSTONE then
|
if u_type == RTU_UNIT_TYPE.REDSTONE then
|
||||||
-- redstone
|
-- redstone
|
||||||
unit = svrs_redstone.new(id, i, unit_advert, self.modbus_q)
|
unit = svrs_redstone.new(id, i, unit_advert, self.modbus_q)
|
||||||
if type(unit) ~= "nil" then target_unit.add_redstone(unit) end
|
|
||||||
elseif u_type == RTU_UNIT_TYPE.BOILER_VALVE then
|
-- link this to any subsystems this RTU provides connections for
|
||||||
|
if type(unit) ~= "nil" then
|
||||||
|
for assignment, conns in pairs(unit_advert.rs_conns) do
|
||||||
|
if #conns > 0 then
|
||||||
|
if assignment == 0 then
|
||||||
|
facility.add_redstone(unit)
|
||||||
|
elseif assignment > 0 and assignment <= #self.fac_units then
|
||||||
|
self.fac_units[assignment].add_redstone(unit)
|
||||||
|
else
|
||||||
|
log.warning(util.c(log_tag, "_handle_advertisement(): invalid redstone RTU assignment ", assignment))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
log.warning(util.c(log_tag, "_handle_advertisement(): encountered unsupported multi-assignment RTU type ", type_string))
|
||||||
|
end
|
||||||
|
elseif unit_advert.reactor > 0 then
|
||||||
|
local target_unit = self.fac_units[unit_advert.reactor]
|
||||||
|
|
||||||
|
-- unit RTUs
|
||||||
|
if u_type == RTU_UNIT_TYPE.BOILER_VALVE then
|
||||||
-- boiler
|
-- boiler
|
||||||
unit = svrs_boilerv.new(id, i, unit_advert, self.modbus_q)
|
unit = svrs_boilerv.new(id, i, unit_advert, self.modbus_q)
|
||||||
if type(unit) ~= "nil" then target_unit.add_boiler(unit) end
|
if type(unit) ~= "nil" then target_unit.add_boiler(unit) end
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ local redstone = {}
|
|||||||
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
|
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
|
||||||
local MODBUS_FCODE = types.MODBUS_FCODE
|
local MODBUS_FCODE = types.MODBUS_FCODE
|
||||||
|
|
||||||
local IO_PORT = rsio.IO
|
|
||||||
local IO_LVL = rsio.IO_LVL
|
local IO_LVL = rsio.IO_LVL
|
||||||
local IO_MODE = rsio.IO_MODE
|
local IO_MODE = rsio.IO_MODE
|
||||||
|
|
||||||
@@ -39,6 +38,9 @@ local PERIODICS = {
|
|||||||
OUTPUT_SYNC = 200
|
OUTPUT_SYNC = 200
|
||||||
}
|
}
|
||||||
|
|
||||||
|
-- create a new block of IO banks (facility, then each unit)
|
||||||
|
local function new_io_block() return { [0] = {}, {}, {}, {}, {} } end
|
||||||
|
|
||||||
---@class dig_phy_entry
|
---@class dig_phy_entry
|
||||||
---@field phy IO_LVL actual value
|
---@field phy IO_LVL actual value
|
||||||
---@field req IO_LVL commanded value
|
---@field req IO_LVL commanded value
|
||||||
@@ -74,27 +76,27 @@ function redstone.new(session_id, unit_id, advert, out_queue)
|
|||||||
next_ir_req = 0,
|
next_ir_req = 0,
|
||||||
next_hr_sync = 0
|
next_hr_sync = 0
|
||||||
},
|
},
|
||||||
---@class rs_io_list
|
---@class rs_io_map
|
||||||
io_list = {
|
io_map = {
|
||||||
digital_in = {}, ---@type IO_PORT[] discrete inputs
|
digital_in = {}, ---@type { bank: integer, port: IO_PORT }[] discrete inputs
|
||||||
digital_out = {}, ---@type IO_PORT[] coils
|
digital_out = {}, ---@type { bank: integer, port: IO_PORT }[] coils
|
||||||
analog_in = {}, ---@type IO_PORT[] input registers
|
analog_in = {}, ---@type { bank: integer, port: IO_PORT }[] input registers
|
||||||
analog_out = {} ---@type IO_PORT[] holding registers
|
analog_out = {} ---@type { bank: integer, port: IO_PORT }[] holding registers
|
||||||
},
|
},
|
||||||
phy_trans = { coils = -1, hold_regs = -1 },
|
phy_trans = { coils = -1, hold_regs = -1 },
|
||||||
-- last set/read ports (reflecting the current state of the RTU)
|
-- last set/read ports (reflecting the current state of the RTU)
|
||||||
---@class rs_io_states
|
---@class rs_io_states
|
||||||
phy_io = {
|
phy_io = {
|
||||||
digital_in = {}, ---@type dig_phy_entry[] discrete inputs
|
digital_in = new_io_block(), ---@type dig_phy_entry[][] discrete inputs
|
||||||
digital_out = {}, ---@type dig_phy_entry[] coils
|
digital_out = new_io_block(), ---@type dig_phy_entry[][] coils
|
||||||
analog_in = {}, ---@type ana_phy_entry[] input registers
|
analog_in = new_io_block(), ---@type ana_phy_entry[][] input registers
|
||||||
analog_out = {} ---@type ana_phy_entry[] holding registers
|
analog_out = new_io_block() ---@type ana_phy_entry[][] holding registers
|
||||||
},
|
},
|
||||||
---@class redstone_session_db
|
---@class redstone_session_db
|
||||||
db = {
|
db = {
|
||||||
-- read/write functions for connected I/O
|
-- read/write functions for connected I/O
|
||||||
---@type (rs_db_dig_io|rs_db_ana_io)[]
|
---@type (rs_db_dig_io|rs_db_ana_io)[][]
|
||||||
io = {}
|
io = new_io_block()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,93 +105,91 @@ function redstone.new(session_id, unit_id, advert, out_queue)
|
|||||||
|
|
||||||
-- INITIALIZE --
|
-- INITIALIZE --
|
||||||
|
|
||||||
-- create all ports as disconnected
|
|
||||||
for _ = 1, #IO_PORT do
|
|
||||||
table.insert(self.db, IO_LVL.DISCONNECT)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- setup I/O
|
-- setup I/O
|
||||||
for i = 1, #advert.rsio do
|
for bank = 0, 4 do
|
||||||
local port = advert.rsio[i]
|
for i = 1, #advert.rs_conns[bank] do
|
||||||
|
local port = advert.rs_conns[bank][i]
|
||||||
|
|
||||||
if rsio.is_valid_port(port) then
|
if rsio.is_valid_port(port) then
|
||||||
local mode = rsio.get_io_mode(port)
|
local mode = rsio.get_io_mode(port)
|
||||||
|
local io_entry = { bank = bank, port = port }
|
||||||
|
|
||||||
if mode == IO_MODE.DIGITAL_IN then
|
if mode == IO_MODE.DIGITAL_IN then
|
||||||
self.has_di = true
|
self.has_di = true
|
||||||
table.insert(self.io_list.digital_in, port)
|
table.insert(self.io_map.digital_in, io_entry)
|
||||||
|
|
||||||
self.phy_io.digital_in[port] = { phy = IO_LVL.FLOATING, req = IO_LVL.FLOATING }
|
self.phy_io.digital_in[bank][port] = { phy = IO_LVL.FLOATING, req = IO_LVL.FLOATING }
|
||||||
|
|
||||||
---@class rs_db_dig_io
|
---@class rs_db_dig_io
|
||||||
local io_f = {
|
local io_f = {
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
read = function () return rsio.digital_is_active(port, self.phy_io.digital_in[port].phy) end,
|
read = function () return rsio.digital_is_active(port, self.phy_io.digital_in[bank][port].phy) end,
|
||||||
write = function () end
|
write = function () end
|
||||||
}
|
}
|
||||||
|
|
||||||
self.db.io[port] = io_f
|
self.db.io[bank][port] = io_f
|
||||||
elseif mode == IO_MODE.DIGITAL_OUT then
|
elseif mode == IO_MODE.DIGITAL_OUT then
|
||||||
self.has_do = true
|
self.has_do = true
|
||||||
table.insert(self.io_list.digital_out, port)
|
table.insert(self.io_map.digital_out, io_entry)
|
||||||
|
|
||||||
self.phy_io.digital_out[port] = { phy = IO_LVL.FLOATING, req = IO_LVL.FLOATING }
|
self.phy_io.digital_out[bank][port] = { phy = IO_LVL.FLOATING, req = IO_LVL.FLOATING }
|
||||||
|
|
||||||
---@class rs_db_dig_io
|
---@class rs_db_dig_io
|
||||||
local io_f = {
|
local io_f = {
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
read = function () return rsio.digital_is_active(port, self.phy_io.digital_out[port].phy) end,
|
read = function () return rsio.digital_is_active(port, self.phy_io.digital_out[bank][port].phy) end,
|
||||||
---@param active boolean
|
---@param active boolean
|
||||||
write = function (active)
|
write = function (active)
|
||||||
local level = rsio.digital_write_active(port, active)
|
local level = rsio.digital_write_active(port, active)
|
||||||
if level ~= nil then self.phy_io.digital_out[port].req = level end
|
if level ~= nil then self.phy_io.digital_out[bank][port].req = level end
|
||||||
end
|
|
||||||
}
|
|
||||||
|
|
||||||
self.db.io[port] = io_f
|
|
||||||
elseif mode == IO_MODE.ANALOG_IN then
|
|
||||||
self.has_ai = true
|
|
||||||
table.insert(self.io_list.analog_in, port)
|
|
||||||
|
|
||||||
self.phy_io.analog_in[port] = { phy = 0, req = 0 }
|
|
||||||
|
|
||||||
---@class rs_db_ana_io
|
|
||||||
local io_f = {
|
|
||||||
---@nodiscard
|
|
||||||
---@return integer
|
|
||||||
read = function () return self.phy_io.analog_in[port].phy end,
|
|
||||||
write = function () end
|
|
||||||
}
|
|
||||||
|
|
||||||
self.db.io[port] = io_f
|
|
||||||
elseif mode == IO_MODE.ANALOG_OUT then
|
|
||||||
self.has_ao = true
|
|
||||||
table.insert(self.io_list.analog_out, port)
|
|
||||||
|
|
||||||
self.phy_io.analog_out[port] = { phy = 0, req = 0 }
|
|
||||||
|
|
||||||
---@class rs_db_ana_io
|
|
||||||
local io_f = {
|
|
||||||
---@nodiscard
|
|
||||||
---@return integer
|
|
||||||
read = function () return self.phy_io.analog_out[port].phy end,
|
|
||||||
---@param value integer
|
|
||||||
write = function (value)
|
|
||||||
if value >= 0 and value <= 15 then
|
|
||||||
self.phy_io.analog_out[port].req = value
|
|
||||||
end
|
end
|
||||||
end
|
}
|
||||||
}
|
|
||||||
|
|
||||||
self.db.io[port] = io_f
|
self.db.io[bank][port] = io_f
|
||||||
|
elseif mode == IO_MODE.ANALOG_IN then
|
||||||
|
self.has_ai = true
|
||||||
|
table.insert(self.io_map.analog_in, io_entry)
|
||||||
|
|
||||||
|
self.phy_io.analog_in[bank][port] = { phy = 0, req = 0 }
|
||||||
|
|
||||||
|
---@class rs_db_ana_io
|
||||||
|
local io_f = {
|
||||||
|
---@nodiscard
|
||||||
|
---@return integer
|
||||||
|
read = function () return self.phy_io.analog_in[bank][port].phy end,
|
||||||
|
write = function () end
|
||||||
|
}
|
||||||
|
|
||||||
|
self.db.io[bank][port] = io_f
|
||||||
|
elseif mode == IO_MODE.ANALOG_OUT then
|
||||||
|
self.has_ao = true
|
||||||
|
table.insert(self.io_map.analog_out, io_entry)
|
||||||
|
|
||||||
|
self.phy_io.analog_out[bank][port] = { phy = 0, req = 0 }
|
||||||
|
|
||||||
|
---@class rs_db_ana_io
|
||||||
|
local io_f = {
|
||||||
|
---@nodiscard
|
||||||
|
---@return integer
|
||||||
|
read = function () return self.phy_io.analog_out[bank][port].phy end,
|
||||||
|
---@param value integer
|
||||||
|
write = function (value)
|
||||||
|
if value >= 0 and value <= 15 then
|
||||||
|
self.phy_io.analog_out[bank][port].req = value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
self.db.io[bank][port] = io_f
|
||||||
|
else
|
||||||
|
-- should be unreachable code, we already validated ports
|
||||||
|
log.error(util.c(log_tag, "failed to identify advertisement port IO mode (", bank, ":", port, ")"), true)
|
||||||
|
return nil
|
||||||
|
end
|
||||||
else
|
else
|
||||||
-- should be unreachable code, we already validated ports
|
log.error(util.c(log_tag, "invalid advertisement port (", bank, ":", port, ")"), true)
|
||||||
log.error(util.c(log_tag, "failed to identify advertisement port IO mode (", port, ")"), true)
|
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
else
|
|
||||||
log.error(util.c(log_tag, "invalid advertisement port (", port, ")"), true)
|
|
||||||
return nil
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -197,12 +197,12 @@ function redstone.new(session_id, unit_id, advert, out_queue)
|
|||||||
|
|
||||||
-- query discrete inputs
|
-- query discrete inputs
|
||||||
local function _request_discrete_inputs()
|
local function _request_discrete_inputs()
|
||||||
self.session.send_request(TXN_TYPES.DI_READ, MODBUS_FCODE.READ_DISCRETE_INPUTS, { 1, #self.io_list.digital_in })
|
self.session.send_request(TXN_TYPES.DI_READ, MODBUS_FCODE.READ_DISCRETE_INPUTS, { 1, #self.io_map.digital_in })
|
||||||
end
|
end
|
||||||
|
|
||||||
-- query input registers
|
-- query input registers
|
||||||
local function _request_input_registers()
|
local function _request_input_registers()
|
||||||
self.session.send_request(TXN_TYPES.INPUT_REG_READ, MODBUS_FCODE.READ_INPUT_REGS, { 1, #self.io_list.analog_in })
|
self.session.send_request(TXN_TYPES.INPUT_REG_READ, MODBUS_FCODE.READ_INPUT_REGS, { 1, #self.io_map.analog_in })
|
||||||
end
|
end
|
||||||
|
|
||||||
-- write all coil outputs
|
-- write all coil outputs
|
||||||
@@ -210,9 +210,9 @@ function redstone.new(session_id, unit_id, advert, out_queue)
|
|||||||
local params = { 1 }
|
local params = { 1 }
|
||||||
|
|
||||||
local outputs = self.phy_io.digital_out
|
local outputs = self.phy_io.digital_out
|
||||||
for i = 1, #self.io_list.digital_out do
|
for i = 1, #self.io_map.digital_out do
|
||||||
local port = self.io_list.digital_out[i]
|
local entry = self.io_map.digital_out[i]
|
||||||
table.insert(params, outputs[port].req)
|
table.insert(params, outputs[entry.bank][entry.port].req)
|
||||||
end
|
end
|
||||||
|
|
||||||
self.phy_trans.coils = self.session.send_request(TXN_TYPES.COIL_WRITE, MODBUS_FCODE.WRITE_MUL_COILS, params)
|
self.phy_trans.coils = self.session.send_request(TXN_TYPES.COIL_WRITE, MODBUS_FCODE.WRITE_MUL_COILS, params)
|
||||||
@@ -220,7 +220,7 @@ function redstone.new(session_id, unit_id, advert, out_queue)
|
|||||||
|
|
||||||
-- read all coil outputs
|
-- read all coil outputs
|
||||||
local function _read_coils()
|
local function _read_coils()
|
||||||
self.session.send_request(TXN_TYPES.COIL_READ, MODBUS_FCODE.READ_COILS, { 1, #self.io_list.digital_out })
|
self.session.send_request(TXN_TYPES.COIL_READ, MODBUS_FCODE.READ_COILS, { 1, #self.io_map.digital_out })
|
||||||
end
|
end
|
||||||
|
|
||||||
-- write all holding register outputs
|
-- write all holding register outputs
|
||||||
@@ -228,9 +228,9 @@ function redstone.new(session_id, unit_id, advert, out_queue)
|
|||||||
local params = { 1 }
|
local params = { 1 }
|
||||||
|
|
||||||
local outputs = self.phy_io.analog_out
|
local outputs = self.phy_io.analog_out
|
||||||
for i = 1, #self.io_list.analog_out do
|
for i = 1, #self.io_map.analog_out do
|
||||||
local port = self.io_list.analog_out[i]
|
local entry = self.io_map.analog_out[i]
|
||||||
table.insert(params, outputs[port].req)
|
table.insert(params, outputs[entry.bank][entry.port].req)
|
||||||
end
|
end
|
||||||
|
|
||||||
self.phy_trans.hold_regs = self.session.send_request(TXN_TYPES.HOLD_REG_WRITE, MODBUS_FCODE.WRITE_MUL_HOLD_REGS, params)
|
self.phy_trans.hold_regs = self.session.send_request(TXN_TYPES.HOLD_REG_WRITE, MODBUS_FCODE.WRITE_MUL_HOLD_REGS, params)
|
||||||
@@ -238,7 +238,7 @@ function redstone.new(session_id, unit_id, advert, out_queue)
|
|||||||
|
|
||||||
-- read all holding register outputs
|
-- read all holding register outputs
|
||||||
local function _read_holding_registers()
|
local function _read_holding_registers()
|
||||||
self.session.send_request(TXN_TYPES.HOLD_REG_READ, MODBUS_FCODE.READ_MUL_HOLD_REGS, { 1, #self.io_list.analog_out })
|
self.session.send_request(TXN_TYPES.HOLD_REG_READ, MODBUS_FCODE.READ_MUL_HOLD_REGS, { 1, #self.io_map.analog_out })
|
||||||
end
|
end
|
||||||
|
|
||||||
-- PUBLIC FUNCTIONS --
|
-- PUBLIC FUNCTIONS --
|
||||||
@@ -259,24 +259,24 @@ function redstone.new(session_id, unit_id, advert, out_queue)
|
|||||||
end
|
end
|
||||||
elseif txn_type == TXN_TYPES.DI_READ then
|
elseif txn_type == TXN_TYPES.DI_READ then
|
||||||
-- discrete input read response
|
-- discrete input read response
|
||||||
if m_pkt.length == #self.io_list.digital_in then
|
if m_pkt.length == #self.io_map.digital_in then
|
||||||
for i = 1, m_pkt.length do
|
for i = 1, m_pkt.length do
|
||||||
local port = self.io_list.digital_in[i]
|
local entry = self.io_map.digital_in[i]
|
||||||
local value = m_pkt.data[i]
|
local value = m_pkt.data[i]
|
||||||
|
|
||||||
self.phy_io.digital_in[port].phy = value
|
self.phy_io.digital_in[entry.bank][entry.port].phy = value
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")")
|
log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")")
|
||||||
end
|
end
|
||||||
elseif txn_type == TXN_TYPES.INPUT_REG_READ then
|
elseif txn_type == TXN_TYPES.INPUT_REG_READ then
|
||||||
-- input register read response
|
-- input register read response
|
||||||
if m_pkt.length == #self.io_list.analog_in then
|
if m_pkt.length == #self.io_map.analog_in then
|
||||||
for i = 1, m_pkt.length do
|
for i = 1, m_pkt.length do
|
||||||
local port = self.io_list.analog_in[i]
|
local entry = self.io_map.analog_in[i]
|
||||||
local value = m_pkt.data[i]
|
local value = m_pkt.data[i]
|
||||||
|
|
||||||
self.phy_io.analog_in[port].phy = value
|
self.phy_io.analog_in[entry.bank][entry.port].phy = value
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")")
|
log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")")
|
||||||
@@ -288,15 +288,14 @@ function redstone.new(session_id, unit_id, advert, out_queue)
|
|||||||
-- update phy I/O table
|
-- update phy I/O table
|
||||||
-- if there are multiple outputs for the same port, they will overwrite eachother (but *should* be identical)
|
-- if there are multiple outputs for the same port, they will overwrite eachother (but *should* be identical)
|
||||||
-- given these are redstone outputs, if one worked they all should have, so no additional verification will be done
|
-- given these are redstone outputs, if one worked they all should have, so no additional verification will be done
|
||||||
if m_pkt.length == #self.io_list.digital_out then
|
if m_pkt.length == #self.io_map.digital_out then
|
||||||
for i = 1, m_pkt.length do
|
for i = 1, m_pkt.length do
|
||||||
local port = self.io_list.digital_out[i]
|
local entry = self.io_map.digital_out[i]
|
||||||
|
local state = self.phy_io.digital_out[entry.bank][entry.port]
|
||||||
local value = m_pkt.data[i]
|
local value = m_pkt.data[i]
|
||||||
|
|
||||||
self.phy_io.digital_out[port].phy = value
|
state.phy = value
|
||||||
if self.phy_io.digital_out[port].req == IO_LVL.FLOATING then
|
if state.req == IO_LVL.FLOATING then state.req = value end
|
||||||
self.phy_io.digital_out[port].req = value
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
self.phy_trans.coils = TXN_READY
|
self.phy_trans.coils = TXN_READY
|
||||||
@@ -310,12 +309,12 @@ function redstone.new(session_id, unit_id, advert, out_queue)
|
|||||||
-- update phy I/O table
|
-- update phy I/O table
|
||||||
-- if there are multiple outputs for the same port, they will overwrite eachother (but *should* be identical)
|
-- if there are multiple outputs for the same port, they will overwrite eachother (but *should* be identical)
|
||||||
-- given these are redstone outputs, if one worked they all should have, so no additional verification will be done
|
-- given these are redstone outputs, if one worked they all should have, so no additional verification will be done
|
||||||
if m_pkt.length == #self.io_list.analog_out then
|
if m_pkt.length == #self.io_map.analog_out then
|
||||||
for i = 1, m_pkt.length do
|
for i = 1, m_pkt.length do
|
||||||
local port = self.io_list.analog_out[i]
|
local entry = self.io_map.analog_out[i]
|
||||||
local value = m_pkt.data[i]
|
local value = m_pkt.data[i]
|
||||||
|
|
||||||
self.phy_io.analog_out[port].phy = value
|
self.phy_io.analog_out[entry.bank][entry.port].phy = value
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")")
|
log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")")
|
||||||
@@ -343,8 +342,17 @@ function redstone.new(session_id, unit_id, advert, out_queue)
|
|||||||
-- sync digital outputs
|
-- sync digital outputs
|
||||||
if self.has_do then
|
if self.has_do then
|
||||||
if (self.periodics.next_cl_sync <= time_now) and (self.phy_trans.coils == TXN_READY) then
|
if (self.periodics.next_cl_sync <= time_now) and (self.phy_trans.coils == TXN_READY) then
|
||||||
for _, entry in pairs(self.phy_io.digital_out) do
|
for bank = 0, 4 do
|
||||||
if entry.phy ~= entry.req then
|
local changed = false
|
||||||
|
|
||||||
|
for _, entry in pairs(self.phy_io.digital_out[bank]) do
|
||||||
|
if entry.phy ~= entry.req then
|
||||||
|
changed = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if changed then
|
||||||
_write_coils()
|
_write_coils()
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
@@ -365,8 +373,17 @@ function redstone.new(session_id, unit_id, advert, out_queue)
|
|||||||
-- sync analog outputs
|
-- sync analog outputs
|
||||||
if self.has_ao then
|
if self.has_ao then
|
||||||
if (self.periodics.next_hr_sync <= time_now) and (self.phy_trans.hold_regs == TXN_READY) then
|
if (self.periodics.next_hr_sync <= time_now) and (self.phy_trans.hold_regs == TXN_READY) then
|
||||||
for _, entry in pairs(self.phy_io.analog_out) do
|
for bank = 0, 4 do
|
||||||
if entry.phy ~= entry.req then
|
local changed = false
|
||||||
|
|
||||||
|
for _, entry in pairs(self.phy_io.analog_out[bank]) do
|
||||||
|
if entry.phy ~= entry.req then
|
||||||
|
changed = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if changed then
|
||||||
_write_holding_registers()
|
_write_holding_registers()
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
@@ -379,9 +396,10 @@ function redstone.new(session_id, unit_id, advert, out_queue)
|
|||||||
self.session.post_update()
|
self.session.post_update()
|
||||||
end
|
end
|
||||||
|
|
||||||
-- invalidate build cache
|
-- force a re-read of cached outputs
|
||||||
function public.invalidate_cache()
|
function public.invalidate_cache()
|
||||||
-- no build cache for this device
|
_read_coils()
|
||||||
|
_read_holding_registers()
|
||||||
end
|
end
|
||||||
|
|
||||||
-- get the unit session database
|
-- get the unit session database
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
-- Supervisor Sessions Handler
|
-- Supervisor Sessions Handler
|
||||||
--
|
--
|
||||||
|
|
||||||
|
local comms = require("scada-common.comms")
|
||||||
local log = require("scada-common.log")
|
local log = require("scada-common.log")
|
||||||
local mqueue = require("scada-common.mqueue")
|
local mqueue = require("scada-common.mqueue")
|
||||||
local types = require("scada-common.types")
|
local types = require("scada-common.types")
|
||||||
@@ -41,20 +42,19 @@ svsessions.SESSION_TYPE = SESSION_TYPE
|
|||||||
|
|
||||||
local self = {
|
local self = {
|
||||||
-- references to supervisor state and other data
|
-- references to supervisor state and other data
|
||||||
nic = nil, ---@type nic|nil
|
|
||||||
fp_ok = false,
|
fp_ok = false,
|
||||||
config = nil, ---@type svr_config
|
config = nil, ---@type svr_config|nil
|
||||||
facility = nil, ---@type facility|nil
|
facility = nil, ---@type facility|nil
|
||||||
plc_ini_reset = {},
|
plc_ini_reset = {},
|
||||||
-- lists of connected sessions
|
-- lists of connected sessions
|
||||||
|
---@class svsessions_list
|
||||||
---@diagnostic disable: missing-fields
|
---@diagnostic disable: missing-fields
|
||||||
sessions = {
|
sessions = {
|
||||||
rtu = {}, ---@type rtu_session_struct
|
rtu = {}, ---@type rtu_session_struct[]
|
||||||
plc = {}, ---@type plc_session_struct
|
plc = {}, ---@type plc_session_struct[]
|
||||||
crd = {}, ---@type crd_session_struct
|
crd = {}, ---@type crd_session_struct[]
|
||||||
pdg = {} ---@type pdg_session_struct
|
pdg = {} ---@type pdg_session_struct[]
|
||||||
},
|
},
|
||||||
---@diagnostic enable: missing-fields
|
|
||||||
-- next session IDs
|
-- next session IDs
|
||||||
next_ids = { rtu = 0, plc = 0, crd = 0, pdg = 0 },
|
next_ids = { rtu = 0, plc = 0, crd = 0, pdg = 0 },
|
||||||
-- rtu device tracking and invalid assignment detection
|
-- rtu device tracking and invalid assignment detection
|
||||||
@@ -83,7 +83,7 @@ local function _sv_handle_outq(session)
|
|||||||
if msg ~= nil then
|
if msg ~= nil then
|
||||||
if msg.qtype == mqueue.TYPE.PACKET then
|
if msg.qtype == mqueue.TYPE.PACKET then
|
||||||
-- handle a packet to be sent
|
-- handle a packet to be sent
|
||||||
self.nic.transmit(session.r_chan, self.config.SVR_Channel, msg.message)
|
session.nic.transmit(session.r_chan, self.config.SVR_Channel, msg.message)
|
||||||
elseif msg.qtype == mqueue.TYPE.COMMAND then
|
elseif msg.qtype == mqueue.TYPE.COMMAND then
|
||||||
-- handle instruction/notification
|
-- handle instruction/notification
|
||||||
elseif msg.qtype == mqueue.TYPE.DATA then
|
elseif msg.qtype == mqueue.TYPE.DATA then
|
||||||
@@ -139,12 +139,9 @@ end
|
|||||||
local function _iterate(sessions)
|
local function _iterate(sessions)
|
||||||
for i = 1, #sessions do
|
for i = 1, #sessions do
|
||||||
local session = sessions[i]
|
local session = sessions[i]
|
||||||
|
|
||||||
if session.open and session.instance.iterate() then
|
if session.open and session.instance.iterate() then
|
||||||
_sv_handle_outq(session)
|
_sv_handle_outq(session)
|
||||||
else
|
else session.open = false end
|
||||||
session.open = false
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -158,7 +155,7 @@ local function _shutdown(session)
|
|||||||
while session.out_queue.ready() do
|
while session.out_queue.ready() do
|
||||||
local msg = session.out_queue.pop()
|
local msg = session.out_queue.pop()
|
||||||
if msg ~= nil and msg.qtype == mqueue.TYPE.PACKET then
|
if msg ~= nil and msg.qtype == mqueue.TYPE.PACKET then
|
||||||
self.nic.transmit(session.r_chan, self.config.SVR_Channel, msg.message)
|
session.nic.transmit(session.r_chan, self.config.SVR_Channel, msg.message)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -358,12 +355,10 @@ function svsessions.check_rtu_id(unit, list, max)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- initialize svsessions
|
-- initialize svsessions
|
||||||
---@param nic nic network interface device
|
|
||||||
---@param fp_ok boolean front panel active
|
---@param fp_ok boolean front panel active
|
||||||
---@param config svr_config supervisor configuration
|
---@param config svr_config supervisor configuration
|
||||||
---@param facility facility
|
---@param facility facility
|
||||||
function svsessions.init(nic, fp_ok, config, facility)
|
function svsessions.init(fp_ok, config, facility)
|
||||||
self.nic = nic
|
|
||||||
self.fp_ok = fp_ok
|
self.fp_ok = fp_ok
|
||||||
self.config = config
|
self.config = config
|
||||||
self.facility = facility
|
self.facility = facility
|
||||||
@@ -466,19 +461,24 @@ end
|
|||||||
|
|
||||||
-- establish a new PLC session
|
-- establish a new PLC session
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
|
---@param nic nic interface to use for this session
|
||||||
---@param source_addr integer PLC computer ID
|
---@param source_addr integer PLC computer ID
|
||||||
---@param i_seq_num integer initial (most recent) sequence number
|
---@param i_seq_num integer initial (most recent) sequence number
|
||||||
---@param for_reactor integer unit ID
|
---@param for_reactor integer unit ID
|
||||||
---@param version string PLC version
|
---@param version string PLC version
|
||||||
---@return integer|false session_id
|
---@return integer|boolean session_id session ID, false if unit is already connected, and true if this is a successful connection test
|
||||||
function svsessions.establish_plc_session(source_addr, i_seq_num, for_reactor, version)
|
function svsessions.establish_plc_session(nic, source_addr, i_seq_num, for_reactor, version)
|
||||||
if svsessions.get_reactor_session(for_reactor) == nil and for_reactor >= 1 and for_reactor <= self.config.UnitCount then
|
if svsessions.get_reactor_session(for_reactor) == nil and for_reactor >= 1 and for_reactor <= self.config.UnitCount then
|
||||||
|
-- don't actually establish this if it is a connection test
|
||||||
|
if version == comms.CONN_TEST_FWV then return true end
|
||||||
|
|
||||||
---@class plc_session_struct
|
---@class plc_session_struct
|
||||||
local plc_s = {
|
local plc_s = {
|
||||||
s_type = "plc",
|
s_type = "plc",
|
||||||
open = true,
|
open = true,
|
||||||
reactor = for_reactor,
|
reactor = for_reactor,
|
||||||
version = version,
|
version = version,
|
||||||
|
nic = nic,
|
||||||
r_chan = self.config.PLC_Channel,
|
r_chan = self.config.PLC_Channel,
|
||||||
s_addr = source_addr,
|
s_addr = source_addr,
|
||||||
in_queue = mqueue.new(),
|
in_queue = mqueue.new(),
|
||||||
@@ -516,17 +516,19 @@ end
|
|||||||
|
|
||||||
-- establish a new RTU gateway session
|
-- establish a new RTU gateway session
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
|
---@param nic nic interface to use for this session
|
||||||
---@param source_addr integer RTU gateway computer ID
|
---@param source_addr integer RTU gateway computer ID
|
||||||
---@param i_seq_num integer initial (most recent) sequence number
|
---@param i_seq_num integer initial (most recent) sequence number
|
||||||
---@param advertisement table RTU capability advertisement
|
---@param advertisement table RTU capability advertisement
|
||||||
---@param version string RTU gateway version
|
---@param version string RTU gateway version
|
||||||
---@return integer session_id
|
---@return integer session_id
|
||||||
function svsessions.establish_rtu_session(source_addr, i_seq_num, advertisement, version)
|
function svsessions.establish_rtu_session(nic, source_addr, i_seq_num, advertisement, version)
|
||||||
---@class rtu_session_struct
|
---@class rtu_session_struct
|
||||||
local rtu_s = {
|
local rtu_s = {
|
||||||
s_type = "rtu",
|
s_type = "rtu",
|
||||||
open = true,
|
open = true,
|
||||||
version = version,
|
version = version,
|
||||||
|
nic = nic,
|
||||||
r_chan = self.config.RTU_Channel,
|
r_chan = self.config.RTU_Channel,
|
||||||
s_addr = source_addr,
|
s_addr = source_addr,
|
||||||
in_queue = mqueue.new(),
|
in_queue = mqueue.new(),
|
||||||
@@ -557,17 +559,19 @@ end
|
|||||||
|
|
||||||
-- establish a new coordinator session
|
-- establish a new coordinator session
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
|
---@param nic nic interface to use for this session
|
||||||
---@param source_addr integer coordinator computer ID
|
---@param source_addr integer coordinator computer ID
|
||||||
---@param i_seq_num integer initial (most recent) sequence number
|
---@param i_seq_num integer initial (most recent) sequence number
|
||||||
---@param version string coordinator version
|
---@param version string coordinator version
|
||||||
---@return integer|false session_id
|
---@return integer|false session_id
|
||||||
function svsessions.establish_crd_session(source_addr, i_seq_num, version)
|
function svsessions.establish_crd_session(nic, source_addr, i_seq_num, version)
|
||||||
if svsessions.get_crd_session() == nil then
|
if svsessions.get_crd_session() == nil then
|
||||||
---@class crd_session_struct
|
---@class crd_session_struct
|
||||||
local crd_s = {
|
local crd_s = {
|
||||||
s_type = "crd",
|
s_type = "crd",
|
||||||
open = true,
|
open = true,
|
||||||
version = version,
|
version = version,
|
||||||
|
nic = nic,
|
||||||
r_chan = self.config.CRD_Channel,
|
r_chan = self.config.CRD_Channel,
|
||||||
s_addr = source_addr,
|
s_addr = source_addr,
|
||||||
in_queue = mqueue.new(),
|
in_queue = mqueue.new(),
|
||||||
@@ -602,16 +606,18 @@ end
|
|||||||
|
|
||||||
-- establish a new pocket diagnostics session
|
-- establish a new pocket diagnostics session
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
|
---@param nic nic interface to use for this session
|
||||||
---@param source_addr integer pocket computer ID
|
---@param source_addr integer pocket computer ID
|
||||||
---@param i_seq_num integer initial (most recent) sequence number
|
---@param i_seq_num integer initial (most recent) sequence number
|
||||||
---@param version string pocket version
|
---@param version string pocket version
|
||||||
---@return integer|false session_id
|
---@return integer|false session_id
|
||||||
function svsessions.establish_pdg_session(source_addr, i_seq_num, version)
|
function svsessions.establish_pdg_session(nic, source_addr, i_seq_num, version)
|
||||||
---@class pdg_session_struct
|
---@class pdg_session_struct
|
||||||
local pdg_s = {
|
local pdg_s = {
|
||||||
s_type = "pkt",
|
s_type = "pkt",
|
||||||
open = true,
|
open = true,
|
||||||
version = version,
|
version = version,
|
||||||
|
nic = nic,
|
||||||
r_chan = self.config.PKT_Channel,
|
r_chan = self.config.PKT_Channel,
|
||||||
s_addr = source_addr,
|
s_addr = source_addr,
|
||||||
in_queue = mqueue.new(),
|
in_queue = mqueue.new(),
|
||||||
@@ -621,7 +627,7 @@ function svsessions.establish_pdg_session(source_addr, i_seq_num, version)
|
|||||||
|
|
||||||
local id = self.next_ids.pdg
|
local id = self.next_ids.pdg
|
||||||
|
|
||||||
pdg_s.instance = pocket.new_session(id, source_addr, i_seq_num, pdg_s.in_queue, pdg_s.out_queue, self.config.PKT_Timeout, self.facility, self.fp_ok)
|
pdg_s.instance = pocket.new_session(id, source_addr, i_seq_num, pdg_s.in_queue, pdg_s.out_queue, self.config.PKT_Timeout, self.sessions, self.facility, self.fp_ok, self.config.PocketTest)
|
||||||
table.insert(self.sessions.pdg, pdg_s)
|
table.insert(self.sessions.pdg, pdg_s)
|
||||||
|
|
||||||
local mt = {
|
local mt = {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user