Compare commits
50 Commits
main
...
580-wired-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
9591668f87 | ||
|
|
4a38ca7dd1 | ||
|
|
250db00794 | ||
|
|
391b68d357 | ||
|
|
4a7fc6200e | ||
|
|
bee1cdf01c | ||
|
|
c6143934d8 | ||
|
|
c319039a4e | ||
|
|
4b61037170 | ||
|
|
028a161af0 | ||
|
|
454d166ac9 |
@ -237,17 +237,17 @@ function hmi.create(tool_ctl, main_pane, cfg_sys, divs, style)
|
|||||||
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=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"}
|
TextBox{parent=crd_c_1,x=20,y=4,text="Po/Pu Pellet Color"}
|
||||||
TextBox{parent=crd_c_1,x=39,y=4,text="new!",fg_bg=cpair(colors.red,colors._INHERIT)} ---@todo remove NEW tag on next revision
|
TextBox{parent=crd_c_1,x=39,y=4,text="new!",fg_bg=cpair(colors.red,colors._INHERIT)} ---@todo remove NEW tag on next revision
|
||||||
tool_ctl.pellet_color = RadioButton{parent=crd_c_1,x=20,y=5,default=util.trinary(ini_cfg.GreenPuPellet,1,2),options={"Green Pu/Cyan Po","Cyan Pu/Green Po (Mek 10.4+)"},callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime}
|
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=20,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=20,y=9,default=ini_cfg.EnergyScale,options=types.ENERGY_SCALE_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime}
|
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
|
||||||
|
|||||||
@ -188,7 +188,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 +230,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."}
|
||||||
|
|
||||||
|
|||||||
@ -20,8 +20,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 = {
|
local MQ__RENDER_CMD = {
|
||||||
START_MAIN_UI = 1,
|
START_MAIN_UI = 1,
|
||||||
|
|||||||
@ -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)
|
||||||
|
|
||||||
|
|||||||
@ -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)
|
||||||
|
|
||||||
|
|||||||
@ -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()
|
||||||
|
|||||||
@ -63,7 +63,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit)
|
|||||||
|
|
||||||
TextBox{parent=ui_c_1,y=4,text="Po/Pu Pellet Color"}
|
TextBox{parent=ui_c_1,y=4,text="Po/Pu Pellet Color"}
|
||||||
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
|
TextBox{parent=ui_c_1,x=20,y=4,text="new!",fg_bg=cpair(colors.red,colors._INHERIT)} ---@todo remove NEW tag on next revision
|
||||||
local pellet_color = RadioButton{parent=ui_c_1,y=5,default=util.trinary(ini_cfg.GreenPuPellet,1,2),options={"Green Pu/Cyan Po","Cyan Pu/Green Po"},callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime}
|
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,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}
|
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}
|
||||||
|
|
||||||
@ -78,10 +78,10 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit)
|
|||||||
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=1,height=3,text="You may customize units below."}
|
||||||
|
|
||||||
TextBox{parent=ui_c_2,x=1,y=4,text="Temperature Scale"}
|
TextBox{parent=ui_c_2,x=1,y=4,text="Temperature Scale"}
|
||||||
local temp_scale = RadioButton{parent=ui_c_2,x=1,y=5,default=ini_cfg.TempScale,options=types.TEMP_SCALE_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime}
|
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"}
|
TextBox{parent=ui_c_2,x=1,y=10,text="Energy Scale"}
|
||||||
local energy_scale = RadioButton{parent=ui_c_2,x=1,y=11,default=ini_cfg.EnergyScale,options=types.ENERGY_SCALE_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime}
|
local 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()
|
local function submit_ui_units()
|
||||||
tmp_cfg.TempScale = temp_scale.get_value()
|
tmp_cfg.TempScale = temp_scale.get_value()
|
||||||
@ -216,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}
|
||||||
|
|||||||
@ -11,8 +11,8 @@ 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_DATA = pocket.MQ__RENDER_DATA
|
local MQ__RENDER_DATA = pocket.MQ__RENDER_DATA
|
||||||
|
|
||||||
|
|||||||
@ -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)
|
||||||
|
|
||||||
|
|||||||
236
reactor-plc/backplane.lua
Normal file
236
reactor-plc/backplane.lua
Normal file
@ -0,0 +1,236 @@
|
|||||||
|
--
|
||||||
|
-- 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 databus = require("reactor-plc.databus")
|
||||||
|
local plc = require("reactor-plc.plc")
|
||||||
|
|
||||||
|
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
|
||||||
|
wl_act = true,
|
||||||
|
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
|
||||||
|
|
||||||
|
-- Modem Init
|
||||||
|
|
||||||
|
if _bp.smem.networked then
|
||||||
|
-- init wired NIC
|
||||||
|
if type(config.WiredModem) == "string" then
|
||||||
|
local modem = ppm.get_modem(_bp.lan_iface)
|
||||||
|
_bp.wd_nic = network.nic(modem)
|
||||||
|
|
||||||
|
log.info("BKPLN: WIRED PHY_" .. util.trinary(modem, "UP ", "DOWN ") .. _bp.lan_iface)
|
||||||
|
|
||||||
|
plc_state.wd_modem = _bp.wd_nic.is_connected()
|
||||||
|
|
||||||
|
-- set this as active for now
|
||||||
|
_bp.wl_act = false
|
||||||
|
_bp.act_nic = _bp.wd_nic
|
||||||
|
end
|
||||||
|
|
||||||
|
-- init wireless NIC(s)
|
||||||
|
if config.WirelessModem then
|
||||||
|
local modem, iface = ppm.get_wireless_modem()
|
||||||
|
_bp.wl_nic = network.nic(modem)
|
||||||
|
|
||||||
|
log.info("BKPLN: WIRELESS PHY_" .. util.trinary(modem, "UP ", "DOWN ") .. iface)
|
||||||
|
|
||||||
|
plc_state.wl_modem = _bp.wl_nic.is_connected()
|
||||||
|
|
||||||
|
-- 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.wl_act = true
|
||||||
|
_bp.act_nic = _bp.wl_nic
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- comms modem is required if networked
|
||||||
|
if not (plc_state.wd_modem or plc_state.wl_modem) then
|
||||||
|
println("startup> comms modem not 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
|
||||||
|
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")
|
||||||
|
elseif not plc_dev.reactor.isFormed() then
|
||||||
|
println("startup> fission reactor is not formed")
|
||||||
|
log.warning("BKPLN: reactor logic adapter present, but reactor is not formed")
|
||||||
|
|
||||||
|
plc_state.degraded = true
|
||||||
|
plc_state.reactor_formed = false
|
||||||
|
else
|
||||||
|
log.info("BKPLN: reactor detected")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- get the active NIC
|
||||||
|
---@return 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_cmds.MQ__RPS_CMD
|
||||||
|
|
||||||
|
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
|
||||||
|
dev.reactor = device
|
||||||
|
state.no_reactor = false
|
||||||
|
|
||||||
|
print_no_fp("reactor reconnected")
|
||||||
|
log.info("BKPLN: 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
|
||||||
|
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))
|
||||||
|
|
||||||
|
local is_wd = _bp.wd_nic and (_bp.lan_iface == iface)
|
||||||
|
local is_wl = _bp.wl_nic and (not _bp.wl_nic.is_connected()) and m_is_wl
|
||||||
|
|
||||||
|
if is_wd 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")
|
||||||
|
|
||||||
|
state.wd_modem = true
|
||||||
|
|
||||||
|
if _bp.act_nic == _bp.wd_nic then
|
||||||
|
-- set as active
|
||||||
|
_bp.wl_act = false
|
||||||
|
_bp.act_nic = _bp.wd_nic
|
||||||
|
elseif _bp.wl_act and not _bp.wlan_pref then
|
||||||
|
-- switch back to preferred wired
|
||||||
|
_bp.wl_act = false
|
||||||
|
_bp.act_nic = _bp.wd_nic
|
||||||
|
|
||||||
|
sys.plc_comms.switch_nic(_bp.act_nic)
|
||||||
|
log.info("BKPLN: switched comms to wired modem (preferred)")
|
||||||
|
end
|
||||||
|
elseif is_wl then
|
||||||
|
-- connect this as the wireless NIC
|
||||||
|
_bp.wl_nic.connect(device)
|
||||||
|
|
||||||
|
log.info("BKPLN: WIRELESS PHY_UP " .. iface)
|
||||||
|
print_no_fp("wireless comms modem reconnected")
|
||||||
|
|
||||||
|
state.wl_modem = true
|
||||||
|
|
||||||
|
if _bp.act_nic == _bp.wl_nic then
|
||||||
|
-- set as active
|
||||||
|
_bp.wl_act = true
|
||||||
|
_bp.act_nic = _bp.wl_nic
|
||||||
|
elseif (not _bp.wl_act) and _bp.wlan_pref then
|
||||||
|
-- switch back to preferred wireless
|
||||||
|
_bp.wl_act = true
|
||||||
|
_bp.act_nic = _bp.wl_nic
|
||||||
|
|
||||||
|
sys.plc_comms.switch_nic(_bp.act_nic)
|
||||||
|
log.info("BKPLN: switched comms to wireless modem (preferred)")
|
||||||
|
end
|
||||||
|
elseif _bp.wl_nic and m_is_wl then
|
||||||
|
-- the wireless NIC already has a modem
|
||||||
|
print_no_fp("standby wireless modem connected")
|
||||||
|
log.info("BKPLN: standby wireless modem connected")
|
||||||
|
else
|
||||||
|
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)
|
||||||
|
end
|
||||||
|
|
||||||
|
return backplane
|
||||||
@ -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,8 @@ local self = {
|
|||||||
set_networked = nil, ---@type function
|
set_networked = nil, ---@type function
|
||||||
bundled_emcool = nil, ---@type function
|
bundled_emcool = nil, ---@type function
|
||||||
|
|
||||||
|
wl_pref = 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
|
||||||
@ -154,14 +159,14 @@ 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"}
|
TextBox{parent=plc_c_5,x=1,y=1,height=5,text="Advanced Options"}
|
||||||
local invert = Checkbox{parent=plc_c_5,x=1,y=3,label="Invert",default=ini_cfg.EmerCoolInvert,box_fg_bg=cpair(colors.orange,colors.black),callback=function()end}
|
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=10,y=3,text="new!",fg_bg=cpair(colors.red,colors._INHERIT)} ---@todo remove NEW tag on next revision
|
TextBox{parent=plc_c_5,x=10,y=3,text="new!",fg_bg=cpair(colors.red,colors._INHERIT)} ---@todo remove NEW tag on next revision
|
||||||
TextBox{parent=plc_c_5,x=3,y=4,height=4,text="Digital I/O is already inverted (or not) based on intended use. If you have a non-standard setup, you can use this option to avoid needing a redstone inverter.",fg_bg=cpair(colors.gray,colors.lightGray)}
|
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}
|
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()
|
tmp_cfg.EmerCoolInvert = invert.get_value()
|
||||||
next_from_plc()
|
next_from_plc()
|
||||||
end
|
end
|
||||||
@ -177,22 +182,77 @@ 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 dis_pref(value)
|
||||||
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 not 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.set_value(false)
|
||||||
TextBox{parent=net_c_1,x=1,y=11,text="PLC Channel"}
|
self.wl_pref.disable()
|
||||||
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}
|
else self.wl_pref.enable() end
|
||||||
TextBox{parent=net_c_1,x=9,y=12,height=4,text="[PLC_CHANNEL]",fg_bg=g_lg_fg_bg}
|
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(_) tool_ctl.gen_modem_list() end
|
||||||
|
|
||||||
|
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),callback=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}
|
||||||
|
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="MUST ONLY connect to SCADA computers",fg_bg=cpair(colors.red,colors._INHERIT)}
|
||||||
|
TextBox{parent=net_c_1,x=3,y=7,text="connecting to peripherals will cause problems",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}
|
||||||
|
|
||||||
|
dis_pref(ini_cfg.WirelessModem)
|
||||||
|
|
||||||
|
local function submit_interfaces()
|
||||||
|
tmp_cfg.WirelessModem = wireless.get_value()
|
||||||
|
tmp_cfg.PreferWireless = tmp_cfg.WirelessModem and self.wl_pref.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 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())
|
||||||
@ -200,7 +260,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.")
|
||||||
@ -211,54 +271,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()
|
||||||
@ -269,8 +337,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
|
||||||
|
|
||||||
@ -283,7 +351,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}
|
||||||
@ -329,7 +397,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."}
|
||||||
|
|
||||||
@ -368,7 +436,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
|
||||||
@ -471,10 +539,13 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit)
|
|||||||
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(invert, ini_cfg.EmerCoolInvert)
|
||||||
|
try_set(wireless, ini_cfg.WirelessModem)
|
||||||
|
try_set(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)
|
||||||
@ -591,7 +662,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))
|
||||||
@ -601,7 +672,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
|
||||||
@ -623,6 +694,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 = 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")
|
||||||
|
|
||||||
@ -33,7 +34,8 @@ local changes = {
|
|||||||
{ "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.8.21", { "Added option to invert emergency coolant redstone control" } },
|
||||||
|
{ "v1.9.1", { "Added support for wired communications modems" } }
|
||||||
}
|
}
|
||||||
|
|
||||||
---@class plc_configurator
|
---@class plc_configurator
|
||||||
@ -68,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
|
||||||
@ -78,6 +82,9 @@ local tmp_cfg = {
|
|||||||
EmerCoolSide = nil, ---@type string|nil
|
EmerCoolSide = nil, ---@type string|nil
|
||||||
EmerCoolColor = nil, ---@type color|nil
|
EmerCoolColor = nil, ---@type color|nil
|
||||||
EmerCoolInvert = false, ---@type boolean
|
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
|
||||||
@ -103,6 +110,9 @@ local fields = {
|
|||||||
{ "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 },
|
{ "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 },
|
||||||
@ -261,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)
|
||||||
@ -272,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()
|
||||||
|
|
||||||
@ -288,6 +305,14 @@ 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 == "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
|
||||||
|
|||||||
@ -53,7 +53,6 @@ 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("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("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)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- transmit thread (routine) statuses
|
-- transmit thread (routine) statuses
|
||||||
|
|||||||
@ -51,11 +51,11 @@ 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}
|
||||||
|
|||||||
@ -48,6 +48,9 @@ function plc.load_config()
|
|||||||
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")
|
||||||
|
config.WirelessModem = settings.get("WirelessModem")
|
||||||
|
config.WiredModem = settings.get("WiredModem")
|
||||||
|
config.PreferWireless = settings.get("PreferWireless")
|
||||||
config.TrustedRange = settings.get("TrustedRange")
|
config.TrustedRange = settings.get("TrustedRange")
|
||||||
config.AuthKey = settings.get("AuthKey")
|
config.AuthKey = settings.get("AuthKey")
|
||||||
|
|
||||||
@ -70,11 +73,15 @@ 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_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)
|
||||||
cfv.assert_min(cfg.ConnTimeout, 2)
|
cfv.assert_min(cfg.ConnTimeout, 2)
|
||||||
|
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_type_num(cfg.TrustedRange)
|
cfv.assert_type_num(cfg.TrustedRange)
|
||||||
cfv.assert_min(cfg.TrustedRange, 0)
|
cfv.assert_min(cfg.TrustedRange, 0)
|
||||||
cfv.assert_type_str(cfg.AuthKey)
|
cfv.assert_type_str(cfg.AuthKey)
|
||||||
@ -118,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
|
||||||
@ -364,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
|
||||||
@ -474,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
|
||||||
@ -495,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
|
||||||
@ -545,7 +559,9 @@ function plc.comms(version, nic, reactor, rps, conn_watchdog)
|
|||||||
max_burn_rate = nil
|
max_burn_rate = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
comms.set_trusted_range(config.TrustedRange)
|
if nic.isWireless() then
|
||||||
|
comms.set_trusted_range(config.TrustedRange)
|
||||||
|
end
|
||||||
|
|
||||||
-- PRIVATE FUNCTIONS --
|
-- PRIVATE FUNCTIONS --
|
||||||
|
|
||||||
@ -584,11 +600,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()
|
||||||
@ -707,11 +719,133 @@ function plc.comms(version, nic, reactor, rps, conn_watchdog)
|
|||||||
reactor.__p_enable_afc()
|
reactor.__p_enable_afc()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- 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
|
||||||
|
|
||||||
-- PUBLIC FUNCTIONS --
|
-- PUBLIC FUNCTIONS --
|
||||||
|
|
||||||
---@class plc_comms
|
---@class plc_comms
|
||||||
local public = {}
|
local public = {}
|
||||||
|
|
||||||
|
-- switch the current active NIC
|
||||||
|
---@param _nic nic
|
||||||
|
function public.switch_nic(_nic)
|
||||||
|
nic.closeAll()
|
||||||
|
|
||||||
|
if _nic.isWireless() then
|
||||||
|
comms.set_trusted_range(config.TrustedRange)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- configure receive channels
|
||||||
|
_nic.closeAll()
|
||||||
|
_nic.open(config.PLC_Channel)
|
||||||
|
|
||||||
|
nic = _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)
|
||||||
@ -748,8 +882,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
|
||||||
@ -803,15 +937,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
|
||||||
@ -823,16 +953,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
|
||||||
@ -867,36 +994,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
|
||||||
@ -925,68 +1023,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
|
||||||
|
|||||||
@ -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.22"
|
local R_PLC_VERSION = "v1.9.1"
|
||||||
|
|
||||||
local println = util.println
|
local println = util.println
|
||||||
local println_ts = util.println_ts
|
local println_ts = util.println_ts
|
||||||
@ -87,29 +88,30 @@ 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
|
nic = nil, ---@type nic
|
||||||
@ -122,6 +124,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 commands
|
||||||
|
q_cmds = {
|
||||||
|
MQ__RPS_CMD = {
|
||||||
|
SCRAM = 1,
|
||||||
|
DEGRADED_SCRAM = 2,
|
||||||
|
TRIP_TIMEOUT = 3
|
||||||
|
},
|
||||||
|
MQ__COMM_CMD = {
|
||||||
|
SEND_STATUS = 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,110 +144,66 @@ 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.nic = backplane.active_nic()
|
||||||
|
smem_sys.plc_comms = plc.comms(R_PLC_VERSION, smem_sys.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 +217,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,7 +32,7 @@ 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)
|
-- send status updates at 2Hz (every 10 server ticks) (every loop tick)
|
||||||
-- send link requests at 0.5Hz (every 40 server ticks) (every 8 loop ticks)
|
-- send link requests at 0.5Hz (every 40 server ticks) (every 8 loop ticks)
|
||||||
@ -51,9 +41,15 @@ function threads.thread__main(smem, init)
|
|||||||
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 plc_dev = smem.plc_dev
|
||||||
|
|
||||||
|
local MQ__RPS_CMD = smem.q_cmds.MQ__RPS_CMD
|
||||||
|
local MQ__COMM_CMD = smem.q_cmds.MQ__COMM_CMD
|
||||||
|
|
||||||
|
-- start clock
|
||||||
|
loop_clock.start()
|
||||||
|
|
||||||
-- event loop
|
-- event loop
|
||||||
while true do
|
while true do
|
||||||
@ -67,7 +63,6 @@ function threads.thread__main(smem, init)
|
|||||||
|
|
||||||
-- 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()
|
||||||
|
|
||||||
@ -93,7 +88,7 @@ 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
|
||||||
@ -106,10 +101,10 @@ function threads.thread__main(smem, init)
|
|||||||
|
|
||||||
-- 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 +113,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 and nic.is_connected() 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)
|
||||||
@ -145,29 +140,26 @@ function threads.thread__main(smem, init)
|
|||||||
plc_state.degraded = true
|
plc_state.degraded = true
|
||||||
elseif networked and type == "modem" then
|
elseif networked and type == "modem" then
|
||||||
---@cast device Modem
|
---@cast device Modem
|
||||||
-- we only care if this is our wireless modem
|
-- we only care if this is our comms modem
|
||||||
-- note, check init_ok first since nic will be nil if it is false
|
if nic.is_modem(device) then
|
||||||
if plc_state.init_ok and nic.is_modem(device) then
|
|
||||||
nic.disconnect()
|
nic.disconnect()
|
||||||
|
|
||||||
println_ts("comms modem disconnected!")
|
println_ts("comms modem disconnected!")
|
||||||
log.warning("comms modem disconnected")
|
log.warning("comms modem disconnected")
|
||||||
|
|
||||||
local other_modem = ppm.get_wireless_modem()
|
local other_modem = ppm.get_wireless_modem()
|
||||||
if other_modem then
|
if other_modem and not plc_dev.modem_wired then
|
||||||
log.info("found another wireless modem, using it for comms")
|
log.info("found another wireless modem, using it for comms")
|
||||||
nic.connect(other_modem)
|
nic.connect(other_modem)
|
||||||
else
|
else
|
||||||
plc_state.no_modem = true
|
plc_state.no_modem = true
|
||||||
plc_state.degraded = true
|
plc_state.degraded = true
|
||||||
|
|
||||||
if plc_state.init_ok then
|
-- try to scram reactor if it is still connected
|
||||||
-- try to scram reactor if it is still connected
|
smem.q.mq_rps.push_command(MQ__RPS_CMD.DEGRADED_SCRAM)
|
||||||
smem.q.mq_rps.push_command(MQ__RPS_CMD.DEGRADED_SCRAM)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
log.warning("a modem was disconnected")
|
log.warning("non-comms modem disconnected")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -177,66 +169,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 +179,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 +207,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 +228,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 +237,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_cmds.MQ__RPS_CMD
|
||||||
|
|
||||||
local was_linked = false
|
local was_linked = false
|
||||||
local last_update = util.time()
|
local last_update = util.time()
|
||||||
|
|
||||||
@ -316,49 +247,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 +286,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 +314,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 +346,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 +366,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_cmds.MQ__COMM_CMD
|
||||||
|
|
||||||
local last_update = util.time()
|
local last_update = util.time()
|
||||||
|
|
||||||
@ -465,7 +385,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 +406,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 +428,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 +441,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 +469,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 +478,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 +488,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 +510,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 +529,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 +552,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 +619,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 +641,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
|
||||||
|
|||||||
274
rtu/backplane.lua
Normal file
274
rtu/backplane.lua
Normal file
@ -0,0 +1,274 @@
|
|||||||
|
--
|
||||||
|
-- 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")
|
||||||
|
|
||||||
|
---@class rtu_backplane
|
||||||
|
local backplane = {}
|
||||||
|
|
||||||
|
local _bp = {
|
||||||
|
smem = nil, ---@type rtu_shared_memory
|
||||||
|
|
||||||
|
wlan_pref = true,
|
||||||
|
lan_iface = "",
|
||||||
|
|
||||||
|
act_nic = nil, ---@type nic|nil
|
||||||
|
wl_act = true,
|
||||||
|
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
|
||||||
|
function backplane.init(config, __shared_memory)
|
||||||
|
_bp.smem = __shared_memory
|
||||||
|
_bp.wlan_pref = config.PreferWireless
|
||||||
|
_bp.lan_iface = config.WiredModem
|
||||||
|
|
||||||
|
-- init wired NIC
|
||||||
|
if type(config.WiredModem) == "string" then
|
||||||
|
local modem = ppm.get_modem(_bp.lan_iface)
|
||||||
|
|
||||||
|
if modem then
|
||||||
|
_bp.wd_nic = network.nic(modem)
|
||||||
|
log.info("BKPLN: WIRED PHY_UP " .. _bp.lan_iface)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- init wireless NIC(s)
|
||||||
|
if config.WirelessModem then
|
||||||
|
local modem, iface = ppm.get_wireless_modem()
|
||||||
|
|
||||||
|
if modem then
|
||||||
|
_bp.wl_nic = network.nic(modem)
|
||||||
|
log.info("BKPLN: WIRELESS PHY_UP " .. iface)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- grab the preferred active NIC
|
||||||
|
if _bp.wlan_pref then
|
||||||
|
_bp.wl_act = true
|
||||||
|
_bp.act_nic = _bp.wl_nic
|
||||||
|
else
|
||||||
|
_bp.wl_act = false
|
||||||
|
_bp.act_nic = _bp.wd_nic
|
||||||
|
end
|
||||||
|
|
||||||
|
databus.tx_hw_modem(_bp.act_nic ~= nil)
|
||||||
|
|
||||||
|
-- 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(_bp.sounders, sounder)
|
||||||
|
|
||||||
|
log.debug(util.c("BKPLN: added speaker, attached as ", sounder.name))
|
||||||
|
end
|
||||||
|
|
||||||
|
databus.tx_hw_spkr_count(#_bp.sounders)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- get the active NIC
|
||||||
|
---@return nic|nil
|
||||||
|
function backplane.active_nic() return _bp.act_nic end
|
||||||
|
|
||||||
|
-- get the sounder interfaces
|
||||||
|
---@return rtu_speaker_sounder[]
|
||||||
|
function backplane.sounders() return _bp.sounders end
|
||||||
|
|
||||||
|
-- handle a backplane peripheral detach
|
||||||
|
---@param type string
|
||||||
|
---@param device table
|
||||||
|
---@param iface string
|
||||||
|
function backplane.detach(type, device, iface)
|
||||||
|
local function println_ts(message) if not _bp.smem.rtu_state.fp_ok then util.println_ts(message) end end
|
||||||
|
|
||||||
|
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()
|
||||||
|
local was_active = _bp.act_nic and _bp.act_nic.is_modem(device)
|
||||||
|
local was_wd = wd_nic and wd_nic.is_modem(device)
|
||||||
|
local was_wl = wl_nic and wl_nic.is_modem(device)
|
||||||
|
|
||||||
|
log.info(util.c("BKPLN: ", util.trinary(m_is_wl, "WIRELESS", "WIRED"), " PHY_DETACH ", iface))
|
||||||
|
|
||||||
|
if wd_nic and was_wd then
|
||||||
|
wd_nic.disconnect()
|
||||||
|
log.info("BKPLN: WIRED PHY_DOWN " .. iface)
|
||||||
|
elseif wl_nic and was_wl then
|
||||||
|
wl_nic.disconnect()
|
||||||
|
log.info("BKPLN: WIRELESS PHY_DOWN " .. iface)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- we only care if this is our active comms modem
|
||||||
|
if was_active then
|
||||||
|
println_ts("active comms modem disconnected")
|
||||||
|
log.warning("BKPLN: active comms modem disconnected")
|
||||||
|
|
||||||
|
-- failover and try to find a new comms modem
|
||||||
|
if _bp.wl_act then
|
||||||
|
-- try to find another wireless modem, otherwise switch to wired
|
||||||
|
local modem, m_iface = ppm.get_wireless_modem()
|
||||||
|
if modem then
|
||||||
|
log.info("BKPLN: found another wireless modem, using it for comms")
|
||||||
|
|
||||||
|
-- note: must assign to self.wl_nic if creating a nic, otherwise it only changes locally
|
||||||
|
if wl_nic then
|
||||||
|
wl_nic.connect(modem)
|
||||||
|
else _bp.wl_nic = network.nic(modem) end
|
||||||
|
|
||||||
|
log.info("BKPLN: WIRELESS PHY_UP " .. m_iface)
|
||||||
|
|
||||||
|
_bp.act_nic = wl_nic
|
||||||
|
comms.assign_nic(_bp.act_nic)
|
||||||
|
log.info("BKPLN: switched comms to new wireless modem")
|
||||||
|
elseif wd_nic and wd_nic.is_connected() then
|
||||||
|
_bp.wl_act = false
|
||||||
|
_bp.act_nic = _bp.wd_nic
|
||||||
|
|
||||||
|
comms.assign_nic(_bp.act_nic)
|
||||||
|
log.info("BKPLN: switched comms to wired modem")
|
||||||
|
else
|
||||||
|
_bp.act_nic = nil
|
||||||
|
databus.tx_hw_modem(false)
|
||||||
|
comms.unassign_nic()
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- switch to wireless if able
|
||||||
|
if wl_nic then
|
||||||
|
_bp.wl_act = true
|
||||||
|
_bp.act_nic = wl_nic
|
||||||
|
|
||||||
|
comms.assign_nic(_bp.act_nic)
|
||||||
|
log.info("BKPLN: switched comms to wireless modem")
|
||||||
|
else
|
||||||
|
_bp.act_nic = nil
|
||||||
|
databus.tx_hw_modem(false)
|
||||||
|
comms.unassign_nic()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif _bp.wl_nic and m_is_wl then
|
||||||
|
-- wireless, but not active
|
||||||
|
log.info("BKPLN: standby wireless modem disconnected")
|
||||||
|
else
|
||||||
|
log.warning("BKPLN: unassigned modem disconnected")
|
||||||
|
end
|
||||||
|
elseif type == "speaker" then
|
||||||
|
---@cast device Speaker
|
||||||
|
for i = 1, #_bp.sounders do
|
||||||
|
if _bp.sounders[i].speaker == device then
|
||||||
|
table.remove(_bp.sounders, i)
|
||||||
|
|
||||||
|
log.warning(util.c("BKPLN: speaker ", iface, " disconnected"))
|
||||||
|
println_ts("speaker disconnected")
|
||||||
|
|
||||||
|
databus.tx_hw_spkr_count(#_bp.sounders)
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- handle a backplane peripheral attach
|
||||||
|
---@param type string
|
||||||
|
---@param device table
|
||||||
|
---@param iface string
|
||||||
|
function backplane.attach(type, device, iface)
|
||||||
|
local function println_ts(message) if not _bp.smem.rtu_state.fp_ok then util.println_ts(message) end end
|
||||||
|
|
||||||
|
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))
|
||||||
|
|
||||||
|
local is_wd = _bp.lan_iface == iface
|
||||||
|
local is_wl = ((not _bp.wl_nic) or (not _bp.wl_nic.is_connected())) and m_is_wl
|
||||||
|
|
||||||
|
if is_wd then
|
||||||
|
-- connect this as the wired NIC
|
||||||
|
if _bp.wd_nic then
|
||||||
|
_bp.wd_nic.connect(device)
|
||||||
|
else _bp.wd_nic = network.nic(device) end
|
||||||
|
|
||||||
|
log.info("BKPLN: WIRED PHY_UP " .. iface)
|
||||||
|
|
||||||
|
if _bp.act_nic == nil then
|
||||||
|
-- set as active
|
||||||
|
_bp.wl_act = false
|
||||||
|
_bp.act_nic = _bp.wd_nic
|
||||||
|
|
||||||
|
comms.assign_nic(_bp.act_nic)
|
||||||
|
databus.tx_hw_modem(true)
|
||||||
|
println_ts("comms modem reconnected")
|
||||||
|
log.info("BKPLN: switched comms to wired modem")
|
||||||
|
elseif _bp.wl_act and not _bp.wlan_pref then
|
||||||
|
-- switch back to preferred wired
|
||||||
|
_bp.wl_act = false
|
||||||
|
_bp.act_nic = _bp.wd_nic
|
||||||
|
|
||||||
|
comms.assign_nic(_bp.act_nic)
|
||||||
|
log.info("BKPLN: switched comms to wired modem (preferred)")
|
||||||
|
end
|
||||||
|
elseif is_wl then
|
||||||
|
-- connect this as the wireless NIC
|
||||||
|
if _bp.wl_nic then
|
||||||
|
_bp.wl_nic.connect(device)
|
||||||
|
else _bp.wl_nic = network.nic(device) end
|
||||||
|
|
||||||
|
log.info("BKPLN: WIRELESS PHY_UP " .. iface)
|
||||||
|
|
||||||
|
if _bp.act_nic == nil then
|
||||||
|
-- set as active
|
||||||
|
_bp.wl_act = true
|
||||||
|
_bp.act_nic = _bp.wl_nic
|
||||||
|
|
||||||
|
comms.assign_nic(_bp.act_nic)
|
||||||
|
databus.tx_hw_modem(true)
|
||||||
|
println_ts("comms modem reconnected")
|
||||||
|
log.info("BKPLN: switched comms to wireless modem")
|
||||||
|
elseif (not _bp.wl_act) and _bp.wlan_pref then
|
||||||
|
-- switch back to preferred wireless
|
||||||
|
_bp.wl_act = true
|
||||||
|
_bp.act_nic = _bp.wl_nic
|
||||||
|
|
||||||
|
comms.assign_nic(_bp.act_nic)
|
||||||
|
log.info("BKPLN: switched comms to wireless modem (preferred)")
|
||||||
|
end
|
||||||
|
elseif m_is_wl then
|
||||||
|
-- the wireless NIC already has a modem
|
||||||
|
log.info("standby wireless modem connected")
|
||||||
|
else
|
||||||
|
log.info("wired modem connected")
|
||||||
|
end
|
||||||
|
elseif type == "speaker" then
|
||||||
|
---@cast device Speaker
|
||||||
|
table.insert(_bp.sounders, rtu.init_sounder(device))
|
||||||
|
|
||||||
|
println_ts("speaker connected")
|
||||||
|
log.info(util.c("connected speaker ", iface))
|
||||||
|
|
||||||
|
databus.tx_hw_spkr_count(#_bp.sounders)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return backplane
|
||||||
@ -484,7 +484,7 @@ function redstone.create(tool_ctl, main_pane, cfg_sys, rs_cfg, style)
|
|||||||
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}
|
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"}
|
TextBox{parent=rs_c_9,x=1,y=1,height=5,text="Advanced Options"}
|
||||||
self.rs_cfg_inverted = Checkbox{parent=rs_c_9,x=1,y=3,label="Invert",default=false,box_fg_bg=cpair(colors.red,colors.black),callback=function()end,disable_fg_bg=g_lg_fg_bg}
|
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)}
|
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}
|
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}
|
||||||
|
|
||||||
|
|||||||
@ -30,9 +30,11 @@ local self = {
|
|||||||
importing_legacy = false,
|
importing_legacy = false,
|
||||||
importing_any_dc = false,
|
importing_any_dc = false,
|
||||||
|
|
||||||
show_auth_key = nil, ---@type function
|
wl_pref = nil, ---@type Checkbox
|
||||||
show_key_btn = nil, ---@type PushButton
|
range = nil, ---@type NumberField
|
||||||
auth_key_textbox = nil, ---@type TextBox
|
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 +92,77 @@ 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 dis_pref(value)
|
||||||
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 not 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.set_value(false)
|
||||||
TextBox{parent=net_c_1,x=1,y=11,text="RTU Channel"}
|
self.wl_pref.disable()
|
||||||
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}
|
else self.wl_pref.enable() end
|
||||||
TextBox{parent=net_c_1,x=9,y=12,height=4,text="[RTU_CHANNEL]",fg_bg=g_lg_fg_bg}
|
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(_) tool_ctl.gen_modem_list() end
|
||||||
|
|
||||||
|
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),callback=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}
|
||||||
|
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="MUST ONLY connect to SCADA computers",fg_bg=cpair(colors.red,colors._INHERIT)}
|
||||||
|
TextBox{parent=net_c_1,x=3,y=7,text="connecting to peripherals will cause problems",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}
|
||||||
|
|
||||||
|
dis_pref(ini_cfg.WirelessModem)
|
||||||
|
|
||||||
|
local function submit_interfaces()
|
||||||
|
tmp_cfg.WirelessModem = wireless.get_value()
|
||||||
|
tmp_cfg.PreferWireless = tmp_cfg.WirelessModem and self.wl_pref.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 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 +170,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 +181,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 +247,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 +261,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 +303,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 +447,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(wireless, ini_cfg.WirelessModem)
|
||||||
|
try_set(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)
|
||||||
@ -665,6 +733,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
|
||||||
|
|
||||||
|
|||||||
@ -37,7 +37,8 @@ local changes = {
|
|||||||
{ "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.11.8", { "Added advanced option to invert digital redstone signals" } },
|
||||||
{ "v1.12.0", { "Added support for redstone relays" } }
|
{ "v1.12.0", { "Added support for redstone relays" } },
|
||||||
|
{ "v1.13.0", { "Added support for wired communications modems" } }
|
||||||
}
|
}
|
||||||
|
|
||||||
---@class rtu_configurator
|
---@class rtu_configurator
|
||||||
@ -64,39 +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
|
||||||
update_relay_list = nil, ---@type function
|
update_relay_list = nil, ---@type function
|
||||||
gen_peri_summary = nil, ---@type function
|
gen_peri_summary = nil, ---@type function
|
||||||
gen_rs_summary = nil, ---@type function
|
gen_rs_summary = nil, ---@type function
|
||||||
|
|
||||||
|
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
|
||||||
@ -106,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 },
|
||||||
@ -313,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)
|
||||||
|
|
||||||
@ -329,6 +341,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()
|
||||||
|
|
||||||
@ -350,11 +364,13 @@ function configurator.configure(ask_config)
|
|||||||
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.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.update_relay_list()
|
||||||
|
tool_ctl.gen_modem_list()
|
||||||
end
|
end
|
||||||
|
|
||||||
if event == "terminate" then return end
|
if event == "terminate" then return end
|
||||||
|
|||||||
@ -31,7 +31,7 @@ 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 comms modem connection state
|
||||||
---@param has_modem boolean
|
---@param has_modem boolean
|
||||||
function databus.tx_hw_modem(has_modem)
|
function databus.tx_hw_modem(has_modem)
|
||||||
databus.ps.publish("has_modem", has_modem)
|
databus.ps.publish("has_modem", has_modem)
|
||||||
|
|||||||
76
rtu/rtu.lua
76
rtu/rtu.lua
@ -36,6 +36,9 @@ function rtu.load_config()
|
|||||||
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")
|
||||||
|
config.WirelessModem = settings.get("WirelessModem")
|
||||||
|
config.WiredModem = settings.get("WiredModem")
|
||||||
|
config.PreferWireless = settings.get("PreferWireless")
|
||||||
config.TrustedRange = settings.get("TrustedRange")
|
config.TrustedRange = settings.get("TrustedRange")
|
||||||
config.AuthKey = settings.get("AuthKey")
|
config.AuthKey = settings.get("AuthKey")
|
||||||
|
|
||||||
@ -61,6 +64,10 @@ function rtu.validate_config(cfg)
|
|||||||
cfv.assert_channel(cfg.RTU_Channel)
|
cfv.assert_channel(cfg.RTU_Channel)
|
||||||
cfv.assert_type_num(cfg.ConnTimeout)
|
cfv.assert_type_num(cfg.ConnTimeout)
|
||||||
cfv.assert_min(cfg.ConnTimeout, 2)
|
cfv.assert_min(cfg.ConnTimeout, 2)
|
||||||
|
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_type_num(cfg.TrustedRange)
|
cfv.assert_type_num(cfg.TrustedRange)
|
||||||
cfv.assert_min(cfg.TrustedRange, 0)
|
cfv.assert_min(cfg.TrustedRange, 0)
|
||||||
cfv.assert_type_str(cfg.AuthKey)
|
cfv.assert_type_str(cfg.AuthKey)
|
||||||
@ -286,7 +293,7 @@ end
|
|||||||
-- RTU Communications
|
-- RTU Communications
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
---@param version string RTU version
|
---@param version string RTU version
|
||||||
---@param nic nic network interface device
|
---@param nic nic|nil network interface device
|
||||||
---@param conn_watchdog watchdog watchdog reference
|
---@param conn_watchdog watchdog watchdog reference
|
||||||
function rtu.comms(version, nic, conn_watchdog)
|
function rtu.comms(version, nic, conn_watchdog)
|
||||||
local self = {
|
local self = {
|
||||||
@ -299,28 +306,43 @@ function rtu.comms(version, nic, conn_watchdog)
|
|||||||
|
|
||||||
local insert = table.insert
|
local insert = table.insert
|
||||||
|
|
||||||
comms.set_trusted_range(config.TrustedRange)
|
-- CONDITIONAL PRIVATE FUNCTIONS --
|
||||||
|
|
||||||
-- PRIVATE FUNCTIONS --
|
-- these don't check for nic to be nil to save execution time on functions called extremely often
|
||||||
|
-- when the nic isn't present, the aliases _send and _send_modbus are cleared
|
||||||
-- 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
|
||||||
---@param msg table
|
---@param msg table
|
||||||
local function _send(msg_type, msg)
|
local function _nic_send(msg_type, msg)
|
||||||
local s_pkt = comms.scada_packet()
|
local s_pkt = comms.scada_packet()
|
||||||
local m_pkt = comms.mgmt_packet()
|
local m_pkt = comms.mgmt_packet()
|
||||||
|
|
||||||
m_pkt.make(msg_type, msg)
|
m_pkt.make(msg_type, msg)
|
||||||
s_pkt.make(self.sv_addr, self.seq_num, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable())
|
s_pkt.make(self.sv_addr, self.seq_num, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable())
|
||||||
|
|
||||||
|
---@diagnostic disable-next-line: need-check-nil
|
||||||
nic.transmit(config.SVR_Channel, config.RTU_Channel, s_pkt)
|
nic.transmit(config.SVR_Channel, config.RTU_Channel, s_pkt)
|
||||||
self.seq_num = self.seq_num + 1
|
self.seq_num = self.seq_num + 1
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- send a MODBUS TCP packet
|
||||||
|
---@param m_pkt modbus_packet
|
||||||
|
local function _nic_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())
|
||||||
|
|
||||||
|
---@diagnostic disable-next-line: need-check-nil
|
||||||
|
nic.transmit(config.SVR_Channel, config.RTU_Channel, s_pkt)
|
||||||
|
self.seq_num = self.seq_num + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
-- PRIVATE FUNCTIONS --
|
||||||
|
|
||||||
|
-- send a scada management packet
|
||||||
|
local _send = _nic_send
|
||||||
|
|
||||||
-- keep alive ack
|
-- keep alive ack
|
||||||
---@param srv_time integer
|
---@param srv_time integer
|
||||||
local function _send_keep_alive_ack(srv_time)
|
local function _send_keep_alive_ack(srv_time)
|
||||||
@ -351,13 +373,7 @@ function rtu.comms(version, nic, conn_watchdog)
|
|||||||
local public = {}
|
local public = {}
|
||||||
|
|
||||||
-- send a MODBUS TCP packet
|
-- send a MODBUS TCP packet
|
||||||
---@param m_pkt modbus_packet
|
public.send_modbus = _nic_send_modbus
|
||||||
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
|
|
||||||
|
|
||||||
-- unlink from the server
|
-- unlink from the server
|
||||||
---@param rtu_state rtu_state
|
---@param rtu_state rtu_state
|
||||||
@ -404,6 +420,8 @@ function rtu.comms(version, nic, conn_watchdog)
|
|||||||
---@param distance integer
|
---@param distance integer
|
||||||
---@return modbus_frame|mgmt_frame|nil packet
|
---@return modbus_frame|mgmt_frame|nil packet
|
||||||
function public.parse_packet(side, sender, reply_to, message, distance)
|
function public.parse_packet(side, sender, reply_to, message, distance)
|
||||||
|
-- unreachable if there isn't a nic
|
||||||
|
---@diagnostic disable-next-line: need-check-nil
|
||||||
local s_pkt = nic.receive(side, sender, reply_to, message, distance)
|
local s_pkt = nic.receive(side, sender, reply_to, message, distance)
|
||||||
local pkt = nil
|
local pkt = nil
|
||||||
|
|
||||||
@ -594,6 +612,34 @@ function rtu.comms(version, nic, conn_watchdog)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- set the current NIC
|
||||||
|
---@param _nic nic
|
||||||
|
function public.assign_nic(_nic)
|
||||||
|
if nic then nic.closeAll() end
|
||||||
|
|
||||||
|
if _nic.isWireless() then
|
||||||
|
comms.set_trusted_range(config.TrustedRange)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- configure receive channels
|
||||||
|
_nic.closeAll()
|
||||||
|
_nic.open(config.RTU_Channel)
|
||||||
|
|
||||||
|
nic = _nic
|
||||||
|
_send = _nic_send
|
||||||
|
public.send_modbus = _nic_send_modbus
|
||||||
|
end
|
||||||
|
|
||||||
|
-- clear the current NIC
|
||||||
|
function public.unassign_nic()
|
||||||
|
_send = function () end
|
||||||
|
public.send_modbus = function () end
|
||||||
|
nic = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- set the NIC if one was given
|
||||||
|
if nic then public.assign_nic(nic) else public.unassign_nic() end
|
||||||
|
|
||||||
return public
|
return public
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
522
rtu/startup.lua
522
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.12.3"
|
|
||||||
|
|
||||||
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
|
||||||
@ -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,464 +107,19 @@ 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
|
|
||||||
----------------------------------------
|
|
||||||
|
|
||||||
local units = __shared_memory.rtu_sys.units
|
|
||||||
|
|
||||||
local rtu_redstone = config.Redstone
|
|
||||||
local rtu_devices = config.Peripherals
|
|
||||||
|
|
||||||
-- get a string representation of a port interface
|
|
||||||
---@param entry rtu_rs_definition
|
|
||||||
---@return string
|
|
||||||
local function entry_iface_name(entry)
|
|
||||||
return util.trinary(entry.color ~= nil, util.c(entry.side, "/", rsio.color_name(entry.color)), entry.side)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- configure RTU gateway based on settings file definitions
|
|
||||||
local function sys_config()
|
|
||||||
--#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
|
|
||||||
local message = util.c("sys_config> invalid unit assignment at block index #", entry_idx)
|
|
||||||
println(message)
|
|
||||||
log.fatal(message)
|
|
||||||
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
|
|
||||||
local message = util.c("sys_config> invalid redstone relay '", entry.relay, '"')
|
|
||||||
println(message)
|
|
||||||
log.fatal(message)
|
|
||||||
return false
|
|
||||||
elseif not rs_rtus[entry.relay] then
|
|
||||||
log.debug(util.c("sys_config> allocated relay redstone RTU on interface ", entry.relay))
|
|
||||||
|
|
||||||
local hw_state = RTU_HW_STATE.OK
|
|
||||||
local relay = ppm.get_periph(entry.relay)
|
|
||||||
|
|
||||||
if not relay then
|
|
||||||
hw_state = RTU_HW_STATE.OFFLINE
|
|
||||||
log.warning(util.c("sys_config> redstone relay ", entry.relay, " is not connected"))
|
|
||||||
local _, v_device = ppm.mount_virtual()
|
|
||||||
relay = v_device
|
|
||||||
elseif ppm.get_type(entry.relay) ~= "redstone_relay" then
|
|
||||||
hw_state = RTU_HW_STATE.FAULTED
|
|
||||||
log.warning(util.c("sys_config> redstone relay ", entry.relay, " is not a redstone relay"))
|
|
||||||
end
|
|
||||||
|
|
||||||
rs_rtus[entry.relay] = { name = entry.relay, hw_state = hw_state, rtu = redstone_rtu.new(relay), phy = relay, banks = { [0] = {}, {}, {}, {}, {} } }
|
|
||||||
end
|
|
||||||
elseif rs_rtus[0] == nil then
|
|
||||||
log.debug(util.c("sys_config> allocated local redstone RTU"))
|
|
||||||
rs_rtus[0] = { name = "redstone_local", hw_state = RTU_HW_STATE.OK, rtu = redstone_rtu.new(), phy = rs, banks = { [0] = {}, {}, {}, {}, {} } }
|
|
||||||
end
|
|
||||||
|
|
||||||
-- verify configuration
|
|
||||||
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
|
|
||||||
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(conns, entry.port) then
|
|
||||||
local message = util.c("sys_config> 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("sys_config> 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("sys_config> failed to identify IO mode at block index #" .. entry_idx)
|
|
||||||
println("sys_config> encountered a software error, check logs")
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
table.insert(conns, entry.port)
|
|
||||||
|
|
||||||
log.debug(util.c("sys_config> 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("sys_config> failed to identify IO mode of ", rsio.to_string(conn.port), " (", entry_iface_name(conn), " @ ", phy_name, ") for ", assign))
|
|
||||||
println("sys_config> encountered a software error, check logs")
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
table.insert(conns, conn.port)
|
|
||||||
|
|
||||||
log.debug(util.c("sys_config> linked redstone ", for_reactor, ".", #conns, ": ", rsio.to_string(conn.port), " (", entry_iface_name(conn), ")", " @ ", phy_name, ") for ", assign))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
---@type rtu_registry_entry
|
|
||||||
local unit = {
|
|
||||||
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("sys_config> 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
|
|
||||||
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" 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("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" 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
|
|
||||||
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 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("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
|
|
||||||
|
|
||||||
--#endregion
|
|
||||||
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
-- start system
|
-- start system
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
|
|
||||||
log.debug("boot> running sys_config()")
|
log.debug("boot> running uinit()")
|
||||||
|
|
||||||
if sys_config() then
|
if uinit(config, __shared_memory) then
|
||||||
-- check modem
|
-- init backplane peripherals
|
||||||
if smem_dev.modem == nil then
|
backplane.init(config, __shared_memory)
|
||||||
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 UI
|
-- start UI
|
||||||
local message
|
local message
|
||||||
@ -601,9 +137,13 @@ local function main()
|
|||||||
log.debug("startup> conn watchdog started")
|
log.debug("startup> conn watchdog started")
|
||||||
|
|
||||||
-- setup comms
|
-- setup comms
|
||||||
smem_sys.nic = network.nic(smem_dev.modem)
|
local nic = backplane.active_nic()
|
||||||
smem_sys.rtu_comms = rtu.comms(RTU_VERSION, smem_sys.nic, smem_sys.conn_watchdog)
|
smem_sys.rtu_comms = rtu.comms(RTU_VERSION, nic, smem_sys.conn_watchdog)
|
||||||
log.debug("startup> comms init")
|
if nic then
|
||||||
|
log.debug("startup> comms init")
|
||||||
|
else
|
||||||
|
log.warning("startup> no comms modem on startup")
|
||||||
|
end
|
||||||
|
|
||||||
-- init threads
|
-- init threads
|
||||||
local main_thread = threads.thread__main(__shared_memory)
|
local main_thread = threads.thread__main(__shared_memory)
|
||||||
|
|||||||
@ -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
|
||||||
@ -191,12 +191,12 @@ function threads.thread__main(smem)
|
|||||||
|
|
||||||
-- 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)
|
||||||
|
|
||||||
@ -246,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)
|
||||||
-- 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
|
||||||
@ -301,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)
|
||||||
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
|
||||||
@ -391,12 +340,12 @@ function threads.thread__comms(smem)
|
|||||||
|
|
||||||
-- 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
|
||||||
|
|||||||
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,7 +17,7 @@ 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.8"
|
comms.version = "3.0.9"
|
||||||
comms.api_version = "0.0.10"
|
comms.api_version = "0.0.10"
|
||||||
|
|
||||||
---@enum PROTOCOL
|
---@enum PROTOCOL
|
||||||
@ -49,13 +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, -- (API) diagnostic: get alarm tones
|
RTU_TONE_ALARM = 6, -- instruct RTUs to play specified alarm tones
|
||||||
DIAG_TONE_SET = 7, -- (API) diagnostic: set alarm tones
|
DIAG_TONE_GET = 7, -- (API) diagnostic: get alarm tones
|
||||||
DIAG_ALARM_SET = 8, -- (API) diagnostic: set alarm to simulate audio for
|
DIAG_TONE_SET = 8, -- (API) diagnostic: set alarm tones
|
||||||
INFO_LIST_CMP = 9 -- (API) info: list all computers on the network
|
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
|
||||||
@ -89,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
|
||||||
@ -130,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
|
||||||
@ -205,7 +214,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
|
||||||
@ -251,6 +260,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
|
||||||
@ -326,7 +337,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
|
||||||
@ -412,7 +423,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
|
||||||
|
|
||||||
@ -435,11 +446,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
|
||||||
@ -498,7 +509,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
|
||||||
|
|
||||||
@ -521,11 +532,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
|
||||||
@ -580,7 +591,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
|
||||||
|
|
||||||
@ -601,11 +612,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
|
||||||
@ -659,7 +670,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
|
||||||
|
|
||||||
@ -680,11 +691,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
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -24,7 +24,7 @@ local t_pack = table.pack
|
|||||||
local util = {}
|
local util = {}
|
||||||
|
|
||||||
-- scada-common version
|
-- scada-common version
|
||||||
util.version = "1.5.4"
|
util.version = "1.5.6"
|
||||||
|
|
||||||
util.TICK_TIME_S = 0.05
|
util.TICK_TIME_S = 0.05
|
||||||
util.TICK_TIME_MS = 50
|
util.TICK_TIME_MS = 50
|
||||||
|
|||||||
187
supervisor/backplane.lua
Normal file
187
supervisor/backplane.lua
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
--
|
||||||
|
-- Supervisor System Core Peripheral Backplane
|
||||||
|
--
|
||||||
|
|
||||||
|
local log = require("scada-common.log")
|
||||||
|
local network = require("scada-common.network")
|
||||||
|
local ppm = require("scada-common.ppm")
|
||||||
|
local types = require("scada-common.types")
|
||||||
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
|
local databus = require("supervisor.databus")
|
||||||
|
|
||||||
|
local LISTEN_MODE = types.LISTEN_MODE
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
backplane.nics = _bp.nic_map
|
||||||
|
|
||||||
|
-- initialize the system peripheral backplane
|
||||||
|
---@param config svr_config
|
||||||
|
---@return boolean success
|
||||||
|
function backplane.init(config)
|
||||||
|
-- setup the wired modem, if configured
|
||||||
|
if type(config.WiredModem) == "string" then
|
||||||
|
_bp.lan_iface = config.WiredModem
|
||||||
|
|
||||||
|
local modem = ppm.get_modem(_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
|
||||||
|
|
||||||
|
local nic = network.nic(modem)
|
||||||
|
_bp.wd_nic = nic
|
||||||
|
_bp.nic_map[_bp.lan_iface] = nic
|
||||||
|
|
||||||
|
nic.closeAll()
|
||||||
|
|
||||||
|
if config.PLC_Listen ~= LISTEN_MODE.WIRELESS then nic.open(config.PLC_Channel) end
|
||||||
|
if config.RTU_Listen ~= LISTEN_MODE.WIRELESS then nic.open(config.RTU_Channel) end
|
||||||
|
if config.CRD_Listen ~= LISTEN_MODE.WIRELESS then nic.open(config.CRD_Channel) end
|
||||||
|
|
||||||
|
databus.tx_hw_wd_modem(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- setup the wireless modem, if configured
|
||||||
|
if config.WirelessModem then
|
||||||
|
local modem, iface = ppm.get_wireless_modem()
|
||||||
|
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
|
||||||
|
|
||||||
|
local nic = network.nic(modem)
|
||||||
|
_bp.wl_nic = nic
|
||||||
|
_bp.nic_map[iface] = nic
|
||||||
|
|
||||||
|
nic.closeAll()
|
||||||
|
|
||||||
|
if config.PLC_Listen ~= LISTEN_MODE.WIRED then nic.open(config.PLC_Channel) end
|
||||||
|
if config.RTU_Listen ~= LISTEN_MODE.WIRED then nic.open(config.RTU_Channel) end
|
||||||
|
if config.CRD_Listen ~= LISTEN_MODE.WIRED then nic.open(config.CRD_Channel) end
|
||||||
|
if config.PocketEnabled then nic.open(config.PKT_Channel) end
|
||||||
|
|
||||||
|
databus.tx_hw_wl_modem(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
if not ((type(config.WiredModem) == "string" or config.WirelessModem)) then
|
||||||
|
println("startup> no modems configured")
|
||||||
|
log.fatal("BKPLN: no modems configured")
|
||||||
|
return false
|
||||||
|
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))
|
||||||
|
|
||||||
|
local is_wd = _bp.wd_nic and (_bp.lan_iface == iface)
|
||||||
|
local is_wl = _bp.wl_nic and (not _bp.wl_nic.is_connected()) and m_is_wl
|
||||||
|
|
||||||
|
if is_wd 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 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
|
||||||
|
print_no_fp("standby wireless modem connected")
|
||||||
|
log.info("BKPLN: standby wireless modem connected")
|
||||||
|
else
|
||||||
|
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
|
||||||
|
|
||||||
|
local m_is_wl = device.isWireless()
|
||||||
|
local was_wd = _bp.wd_nic and _bp.wd_nic.is_modem(device)
|
||||||
|
local was_wl = _bp.wl_nic and _bp.wl_nic.is_modem(device)
|
||||||
|
|
||||||
|
log.info(util.c("BKPLN: ", util.trinary(m_is_wl, "WIRELESS", "WIRED"), " PHY_DETACH ", iface))
|
||||||
|
|
||||||
|
_bp.nic_map[iface] = nil
|
||||||
|
|
||||||
|
if _bp.wd_nic and was_wd 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 was_wl 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
|
||||||
|
elseif _bp.wl_nic and m_is_wl 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
|
||||||
@ -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,220 @@ 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="MUST ONLY connect to SCADA computers",fg_bg=cpair(colors.red,colors._INHERIT)}
|
||||||
|
TextBox{parent=net_c_1,x=3,y=7,text="connecting to peripherals will cause problems",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=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}
|
||||||
|
self.pkt_test = Checkbox{parent=net_c_2,label="Enable Pocket Remote System Testing",default=ini_cfg.PocketEnabled,box_fg_bg=cpair(colors.lightBlue,colors.black),disable_fg_bg=g_lg_fg_bg}
|
||||||
|
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 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 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)
|
|
||||||
ct_err.hide(true)
|
if tmp_cfg.WirelessModem then
|
||||||
|
net_pane.set_value(5)
|
||||||
|
ct_err.hide(true)
|
||||||
|
else
|
||||||
|
tmp_cfg.TrustedRange = 0
|
||||||
|
tmp_cfg.AuthKey = ""
|
||||||
|
main_pane.set_value(4)
|
||||||
|
end
|
||||||
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 +296,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 +310,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 +352,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 +489,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 +528,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 +603,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 +690,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 +716,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 +838,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 +870,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 both wired and wireless networking" } }
|
||||||
}
|
}
|
||||||
|
|
||||||
---@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, ---@type boolean
|
||||||
|
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, ---@type boolean
|
||||||
|
PocketTest = true, ---@type boolean
|
||||||
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
|
||||||
|
|||||||
@ -24,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 wireless comms modem connection state
|
||||||
---@param has_modem boolean
|
---@param has_modem boolean
|
||||||
function databus.tx_hw_modem(has_modem)
|
function databus.tx_hw_wl_modem(has_modem)
|
||||||
databus.ps.publish("has_modem", has_modem)
|
databus.ps.publish("has_wl_modem", has_modem)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- transmit hardware status for the wired comms modem connection state
|
||||||
|
---@param has_modem boolean
|
||||||
|
function databus.tx_hw_wd_modem(has_modem)
|
||||||
|
databus.ps.publish("has_wd_modem", has_modem)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- transmit PLC firmware version and session connection state
|
-- transmit PLC firmware version and session connection state
|
||||||
|
|||||||
@ -34,7 +34,8 @@ 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 +54,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,14 +63,23 @@ 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 then
|
||||||
system.line_break()
|
local wl_modem = LED{parent=system,label="WL MODEM",colors=ind_grn}
|
||||||
|
system.line_break()
|
||||||
|
|
||||||
modem.register(databus.ps, "has_modem", modem.update)
|
wl_modem.register(databus.ps, "has_wl_modem", wl_modem.update)
|
||||||
|
end
|
||||||
|
|
||||||
|
if config.WiredModem then
|
||||||
|
local wd_modem = LED{parent=system,label="WD MODEM",colors=ind_grn}
|
||||||
|
system.line_break()
|
||||||
|
|
||||||
|
wd_modem.register(databus.ps, "has_wd_modem", wd_modem.update)
|
||||||
|
end
|
||||||
|
|
||||||
---@diagnostic disable-next-line: undefined-field
|
---@diagnostic disable-next-line: undefined-field
|
||||||
local comp_id = util.sprintf("(%d)", os.getComputerID())
|
local comp_id = util.sprintf("(%d)", os.getComputerID())
|
||||||
TextBox{parent=system,x=9,y=4,width=6,text=comp_id,fg_bg=style.fp.disabled_fg}
|
TextBox{parent=system,x=12,y=4,width=6,text=comp_id,fg_bg=style.fp.disabled_fg}
|
||||||
|
|
||||||
--
|
--
|
||||||
-- about footer
|
-- about footer
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -41,9 +41,8 @@ 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
|
||||||
@ -55,7 +54,6 @@ local self = {
|
|||||||
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
|
||||||
@ -84,7 +82,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
|
||||||
@ -140,12 +138,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
|
||||||
|
|
||||||
@ -159,7 +154,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
|
||||||
|
|
||||||
@ -359,12 +354,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
|
||||||
@ -467,12 +460,13 @@ 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|false session_id
|
||||||
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
|
||||||
---@class plc_session_struct
|
---@class plc_session_struct
|
||||||
local plc_s = {
|
local plc_s = {
|
||||||
@ -480,6 +474,7 @@ function svsessions.establish_plc_session(source_addr, i_seq_num, for_reactor, v
|
|||||||
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(),
|
||||||
@ -517,17 +512,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(),
|
||||||
@ -558,17 +555,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(),
|
||||||
@ -603,16 +602,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(),
|
||||||
|
|||||||
@ -15,6 +15,7 @@ local util = require("scada-common.util")
|
|||||||
|
|
||||||
local core = require("graphics.core")
|
local core = require("graphics.core")
|
||||||
|
|
||||||
|
local backplane = require("supervisor.backplane")
|
||||||
local configure = require("supervisor.configure")
|
local configure = require("supervisor.configure")
|
||||||
local databus = require("supervisor.databus")
|
local databus = require("supervisor.databus")
|
||||||
local facility = require("supervisor.facility")
|
local facility = require("supervisor.facility")
|
||||||
@ -23,7 +24,7 @@ local supervisor = require("supervisor.supervisor")
|
|||||||
|
|
||||||
local svsessions = require("supervisor.session.svsessions")
|
local svsessions = require("supervisor.session.svsessions")
|
||||||
|
|
||||||
local SUPERVISOR_VERSION = "v1.7.1"
|
local SUPERVISOR_VERSION = "v1.8.0"
|
||||||
|
|
||||||
local println = util.println
|
local println = util.println
|
||||||
local println_ts = util.println_ts
|
local println_ts = util.println_ts
|
||||||
@ -125,18 +126,11 @@ local function main()
|
|||||||
network.init_mac(config.AuthKey)
|
network.init_mac(config.AuthKey)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- get modem
|
-- hardware backplane initialization
|
||||||
local modem = ppm.get_wireless_modem()
|
if not backplane.init(config) then return end
|
||||||
if modem == nil then
|
|
||||||
println("startup> wireless modem not found")
|
|
||||||
log.fatal("no wireless modem on startup")
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
databus.tx_hw_modem(true)
|
|
||||||
|
|
||||||
-- start UI
|
-- start UI
|
||||||
local fp_ok, message = renderer.try_start_ui(config.FrontPanelTheme, config.ColorMode)
|
local fp_ok, message = renderer.try_start_ui(config)
|
||||||
|
|
||||||
if not fp_ok then
|
if not fp_ok then
|
||||||
println_ts(util.c("UI error: ", message))
|
println_ts(util.c("UI error: ", message))
|
||||||
@ -150,8 +144,7 @@ local function main()
|
|||||||
local sv_facility = facility.new(config)
|
local sv_facility = facility.new(config)
|
||||||
|
|
||||||
-- create network interface then setup comms
|
-- create network interface then setup comms
|
||||||
local nic = network.nic(modem)
|
local superv_comms = supervisor.comms(SUPERVISOR_VERSION, fp_ok, sv_facility)
|
||||||
local superv_comms = supervisor.comms(SUPERVISOR_VERSION, nic, fp_ok, sv_facility)
|
|
||||||
|
|
||||||
-- base loop clock (6.67Hz, 3 ticks)
|
-- base loop clock (6.67Hz, 3 ticks)
|
||||||
local MAIN_CLOCK = 0.15
|
local MAIN_CLOCK = 0.15
|
||||||
@ -173,49 +166,13 @@ local function main()
|
|||||||
-- 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(param1, type, device, println_ts)
|
||||||
---@cast device Modem
|
|
||||||
-- 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
|
|
||||||
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(param1, type, device, println_ts)
|
||||||
---@cast device Modem
|
|
||||||
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
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
elseif event == "timer" and loop_clock.is_clock(param1) then
|
elseif event == "timer" and loop_clock.is_clock(param1) then
|
||||||
-- main loop tick
|
-- main loop tick
|
||||||
|
|||||||
@ -4,14 +4,17 @@ local util = require("scada-common.util")
|
|||||||
|
|
||||||
local themes = require("graphics.themes")
|
local themes = require("graphics.themes")
|
||||||
|
|
||||||
|
local backplane = require("supervisor.backplane")
|
||||||
|
|
||||||
local svsessions = require("supervisor.session.svsessions")
|
local svsessions = require("supervisor.session.svsessions")
|
||||||
|
|
||||||
local supervisor = {}
|
local supervisor = {}
|
||||||
|
|
||||||
local PROTOCOL = comms.PROTOCOL
|
local PROTOCOL = comms.PROTOCOL
|
||||||
local DEVICE_TYPE = comms.DEVICE_TYPE
|
local DEVICE_TYPE = comms.DEVICE_TYPE
|
||||||
local ESTABLISH_ACK = comms.ESTABLISH_ACK
|
local ESTABLISH_ACK = comms.ESTABLISH_ACK
|
||||||
local MGMT_TYPE = comms.MGMT_TYPE
|
local PROBE_ACK = comms.PROBE_ACK
|
||||||
|
local MGMT_TYPE = comms.MGMT_TYPE
|
||||||
|
|
||||||
---@type svr_config
|
---@type svr_config
|
||||||
---@diagnostic disable-next-line: missing-fields
|
---@diagnostic disable-next-line: missing-fields
|
||||||
@ -58,6 +61,16 @@ function supervisor.load_config()
|
|||||||
config.CRD_Timeout = settings.get("CRD_Timeout")
|
config.CRD_Timeout = settings.get("CRD_Timeout")
|
||||||
config.PKT_Timeout = settings.get("PKT_Timeout")
|
config.PKT_Timeout = settings.get("PKT_Timeout")
|
||||||
|
|
||||||
|
config.WirelessModem = settings.get("WirelessModem")
|
||||||
|
config.WiredModem = settings.get("WiredModem")
|
||||||
|
|
||||||
|
config.PLC_Listen = settings.get("PLC_Listen")
|
||||||
|
config.RTU_Listen = settings.get("RTU_Listen")
|
||||||
|
config.CRD_Listen = settings.get("CRD_Listen")
|
||||||
|
|
||||||
|
config.PocketEnabled = settings.get("PocketEnabled")
|
||||||
|
config.PocketTest = settings.get("PocketTest")
|
||||||
|
|
||||||
config.TrustedRange = settings.get("TrustedRange")
|
config.TrustedRange = settings.get("TrustedRange")
|
||||||
config.AuthKey = settings.get("AuthKey")
|
config.AuthKey = settings.get("AuthKey")
|
||||||
|
|
||||||
@ -99,6 +112,19 @@ function supervisor.load_config()
|
|||||||
cfv.assert_type_num(config.PKT_Timeout)
|
cfv.assert_type_num(config.PKT_Timeout)
|
||||||
cfv.assert_min(config.PKT_Timeout, 2)
|
cfv.assert_min(config.PKT_Timeout, 2)
|
||||||
|
|
||||||
|
cfv.assert_type_bool(config.WirelessModem)
|
||||||
|
cfv.assert((config.WiredModem == false) or (type(config.WiredModem) == "string"))
|
||||||
|
|
||||||
|
cfv.assert_type_num(config.PLC_Listen)
|
||||||
|
cfv.assert_range(config.PLC_Listen, 0, 2)
|
||||||
|
cfv.assert_type_num(config.RTU_Listen)
|
||||||
|
cfv.assert_range(config.RTU_Listen, 0, 2)
|
||||||
|
cfv.assert_type_num(config.CRD_Listen)
|
||||||
|
cfv.assert_range(config.CRD_Listen, 0, 2)
|
||||||
|
|
||||||
|
cfv.assert_type_bool(config.PocketEnabled)
|
||||||
|
cfv.assert_type_bool(config.PocketTest)
|
||||||
|
|
||||||
cfv.assert_type_num(config.TrustedRange)
|
cfv.assert_type_num(config.TrustedRange)
|
||||||
cfv.assert_min(config.TrustedRange, 0)
|
cfv.assert_min(config.TrustedRange, 0)
|
||||||
|
|
||||||
@ -123,36 +149,31 @@ end
|
|||||||
-- supervisory controller communications
|
-- supervisory controller communications
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
---@param _version string supervisor version
|
---@param _version string supervisor version
|
||||||
---@param nic nic network interface device
|
|
||||||
---@param fp_ok boolean if the front panel UI is running
|
---@param fp_ok boolean if the front panel UI is running
|
||||||
---@param facility facility facility instance
|
---@param facility facility facility instance
|
||||||
---@diagnostic disable-next-line: unused-local
|
---@diagnostic disable-next-line: unused-local
|
||||||
function supervisor.comms(_version, nic, fp_ok, facility)
|
function supervisor.comms(_version, fp_ok, facility)
|
||||||
-- print a log message to the terminal as long as the UI isn't running
|
-- print a log message to the terminal as long as the UI isn't running
|
||||||
local function println(message) if not fp_ok then util.println_ts(message) end end
|
local function println(message) if not fp_ok then util.println_ts(message) end end
|
||||||
|
|
||||||
local self = {
|
local self = {
|
||||||
last_est_acks = {}
|
last_est_acks = {} ---@type ESTABLISH_ACK[]
|
||||||
}
|
}
|
||||||
|
|
||||||
comms.set_trusted_range(config.TrustedRange)
|
comms.set_trusted_range(config.TrustedRange)
|
||||||
|
|
||||||
|
-- pass system data and objects to svsessions
|
||||||
|
svsessions.init(fp_ok, config, facility)
|
||||||
|
|
||||||
-- PRIVATE FUNCTIONS --
|
-- PRIVATE FUNCTIONS --
|
||||||
|
|
||||||
-- configure modem channels
|
|
||||||
nic.closeAll()
|
|
||||||
nic.open(config.SVR_Channel)
|
|
||||||
|
|
||||||
-- pass system data and objects to svsessions
|
|
||||||
svsessions.init(nic, fp_ok, config, facility)
|
|
||||||
|
|
||||||
-- send an establish request response
|
-- send an establish request response
|
||||||
|
---@param nic nic
|
||||||
---@param packet scada_packet
|
---@param packet scada_packet
|
||||||
---@param ack ESTABLISH_ACK
|
---@param ack ESTABLISH_ACK
|
||||||
---@param data? any optional data
|
---@param data? any optional data
|
||||||
local function _send_establish(packet, ack, data)
|
local function _send_establish(nic, packet, ack, data)
|
||||||
local s_pkt = comms.scada_packet()
|
local s_pkt, m_pkt = comms.scada_packet(), comms.mgmt_packet()
|
||||||
local m_pkt = comms.mgmt_packet()
|
|
||||||
|
|
||||||
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())
|
||||||
@ -161,6 +182,188 @@ function supervisor.comms(_version, nic, fp_ok, facility)
|
|||||||
self.last_est_acks[packet.src_addr()] = ack
|
self.last_est_acks[packet.src_addr()] = ack
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- send a probe response
|
||||||
|
---@param nic nic
|
||||||
|
---@param packet scada_packet
|
||||||
|
---@param ack PROBE_ACK
|
||||||
|
local function _send_probe(nic, packet, ack)
|
||||||
|
local s_pkt, m_pkt = comms.scada_packet(), comms.mgmt_packet()
|
||||||
|
|
||||||
|
m_pkt.make(MGMT_TYPE.PROBE, { ack })
|
||||||
|
s_pkt.make(packet.src_addr(), packet.seq_num() + 1, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable())
|
||||||
|
|
||||||
|
nic.transmit(packet.remote_channel(), config.SVR_Channel, s_pkt)
|
||||||
|
end
|
||||||
|
|
||||||
|
--#region Establish Handlers
|
||||||
|
|
||||||
|
-- handle a PLC establish
|
||||||
|
---@param nic nic
|
||||||
|
---@param packet mgmt_frame
|
||||||
|
---@param src_addr integer
|
||||||
|
---@param i_seq_num integer
|
||||||
|
---@param last_ack ESTABLISH_ACK
|
||||||
|
local function _establish_plc(nic, packet, src_addr, i_seq_num, last_ack)
|
||||||
|
local comms_v = packet.data[1]
|
||||||
|
local firmware_v = packet.data[2]
|
||||||
|
local dev_type = packet.data[3]
|
||||||
|
|
||||||
|
if comms_v ~= comms.version then
|
||||||
|
if last_ack ~= ESTABLISH_ACK.BAD_VERSION then
|
||||||
|
log.info(util.c("dropping PLC establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")"))
|
||||||
|
end
|
||||||
|
|
||||||
|
_send_establish(nic, packet.scada_frame, ESTABLISH_ACK.BAD_VERSION)
|
||||||
|
elseif dev_type == DEVICE_TYPE.PLC then
|
||||||
|
-- PLC linking request
|
||||||
|
if packet.length == 4 and type(packet.data[4]) == "number" then
|
||||||
|
local reactor_id = packet.data[4]
|
||||||
|
|
||||||
|
-- check ID validity
|
||||||
|
if reactor_id < 1 or reactor_id > config.UnitCount then
|
||||||
|
-- reactor index out of range
|
||||||
|
if last_ack ~= ESTABLISH_ACK.DENY then
|
||||||
|
log.warning(util.c("PLC_ESTABLISH: denied assignment ", reactor_id, " outside of configured unit count ", config.UnitCount))
|
||||||
|
end
|
||||||
|
|
||||||
|
_send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY)
|
||||||
|
else
|
||||||
|
-- try to establish the session
|
||||||
|
local plc_id = svsessions.establish_plc_session(nic, src_addr, i_seq_num, reactor_id, firmware_v)
|
||||||
|
|
||||||
|
if plc_id == false then
|
||||||
|
-- reactor already has a PLC assigned
|
||||||
|
if last_ack ~= ESTABLISH_ACK.COLLISION then
|
||||||
|
log.warning(util.c("PLC_ESTABLISH: assignment collision with reactor ", reactor_id))
|
||||||
|
end
|
||||||
|
|
||||||
|
_send_establish(nic, packet.scada_frame, ESTABLISH_ACK.COLLISION)
|
||||||
|
else
|
||||||
|
-- got an ID; assigned to a reactor successfully
|
||||||
|
println(util.c("PLC (", firmware_v, ") [@", src_addr, "] \xbb reactor ", reactor_id, " connected"))
|
||||||
|
log.info(util.c("PLC_ESTABLISH: PLC (", firmware_v, ") [@", src_addr, "] reactor unit ", reactor_id, " PLC connected with session ID ", plc_id, " on ", nic.phy_name()))
|
||||||
|
_send_establish(nic, packet.scada_frame, ESTABLISH_ACK.ALLOW)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
log.debug("PLC_ESTABLISH: packet length mismatch/bad parameter type")
|
||||||
|
_send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
log.debug(util.c("illegal establish packet for device ", dev_type, " on PLC channel"))
|
||||||
|
_send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- handle an RTU gateway establish
|
||||||
|
---@param nic nic
|
||||||
|
---@param packet mgmt_frame
|
||||||
|
---@param src_addr integer
|
||||||
|
---@param i_seq_num integer
|
||||||
|
---@param last_ack ESTABLISH_ACK
|
||||||
|
local function _establish_rtu_gw(nic, packet, src_addr, i_seq_num, last_ack)
|
||||||
|
local comms_v = packet.data[1]
|
||||||
|
local firmware_v = packet.data[2]
|
||||||
|
local dev_type = packet.data[3]
|
||||||
|
|
||||||
|
if comms_v ~= comms.version then
|
||||||
|
if last_ack ~= ESTABLISH_ACK.BAD_VERSION then
|
||||||
|
log.info(util.c("dropping RTU_GW establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")"))
|
||||||
|
end
|
||||||
|
|
||||||
|
_send_establish(nic, packet.scada_frame, ESTABLISH_ACK.BAD_VERSION)
|
||||||
|
elseif dev_type == DEVICE_TYPE.RTU then
|
||||||
|
if packet.length == 4 then
|
||||||
|
-- this is an RTU advertisement for a new session
|
||||||
|
local rtu_advert = packet.data[4]
|
||||||
|
local s_id = svsessions.establish_rtu_session(nic, src_addr, i_seq_num, rtu_advert, firmware_v)
|
||||||
|
|
||||||
|
println(util.c("RTU (", firmware_v, ") [@", src_addr, "] \xbb connected"))
|
||||||
|
log.info(util.c("RTU_GW_ESTABLISH: RTU_GW (",firmware_v, ") [@", src_addr, "] connected with session ID ", s_id, " on ", nic.phy_name()))
|
||||||
|
_send_establish(nic, packet.scada_frame, ESTABLISH_ACK.ALLOW)
|
||||||
|
else
|
||||||
|
log.debug("RTU_GW_ESTABLISH: packet length mismatch")
|
||||||
|
_send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
log.debug(util.c("illegal establish packet for device ", dev_type, " on RTU channel"))
|
||||||
|
_send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- handle a coordinator establish
|
||||||
|
---@param nic nic
|
||||||
|
---@param packet mgmt_frame
|
||||||
|
---@param src_addr integer
|
||||||
|
---@param i_seq_num integer
|
||||||
|
---@param last_ack ESTABLISH_ACK
|
||||||
|
local function _establish_crd(nic, packet, src_addr, i_seq_num, last_ack)
|
||||||
|
local comms_v = packet.data[1]
|
||||||
|
local firmware_v = packet.data[2]
|
||||||
|
local dev_type = packet.data[3]
|
||||||
|
|
||||||
|
if comms_v ~= comms.version then
|
||||||
|
if last_ack ~= ESTABLISH_ACK.BAD_VERSION then
|
||||||
|
log.info(util.c("dropping coordinator establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")"))
|
||||||
|
end
|
||||||
|
|
||||||
|
_send_establish(nic, packet.scada_frame, ESTABLISH_ACK.BAD_VERSION)
|
||||||
|
elseif dev_type == DEVICE_TYPE.CRD then
|
||||||
|
-- this is an attempt to establish a new coordinator session
|
||||||
|
local s_id = svsessions.establish_crd_session(nic, src_addr, i_seq_num, firmware_v)
|
||||||
|
|
||||||
|
if s_id ~= false then
|
||||||
|
println(util.c("CRD (", firmware_v, ") [@", src_addr, "] \xbb connected"))
|
||||||
|
log.info(util.c("CRD_ESTABLISH: CRD (", firmware_v, ") [@", src_addr, "] connected with session ID ", s_id, " on ", nic.phy_name()))
|
||||||
|
|
||||||
|
_send_establish(nic, packet.scada_frame, ESTABLISH_ACK.ALLOW, { config.UnitCount, facility.get_cooling_conf() })
|
||||||
|
else
|
||||||
|
if last_ack ~= ESTABLISH_ACK.COLLISION then
|
||||||
|
log.info("CRD_ESTABLISH: denied new coordinator [@" .. src_addr .. "] due to already being connected to another coordinator")
|
||||||
|
end
|
||||||
|
|
||||||
|
_send_establish(nic, packet.scada_frame, ESTABLISH_ACK.COLLISION)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
log.debug(util.c("illegal establish packet for device ", dev_type, " on CRD channel"))
|
||||||
|
_send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- handle a pocket debug establish
|
||||||
|
---@param nic nic
|
||||||
|
---@param packet mgmt_frame
|
||||||
|
---@param src_addr integer
|
||||||
|
---@param i_seq_num integer
|
||||||
|
---@param last_ack ESTABLISH_ACK
|
||||||
|
local function _establish_pdg(nic, packet, src_addr, i_seq_num, last_ack)
|
||||||
|
local comms_v = packet.data[1]
|
||||||
|
local firmware_v = packet.data[2]
|
||||||
|
local dev_type = packet.data[3]
|
||||||
|
|
||||||
|
if comms_v ~= comms.version then
|
||||||
|
if last_ack ~= ESTABLISH_ACK.BAD_VERSION then
|
||||||
|
log.info(util.c("dropping PKT establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")"))
|
||||||
|
end
|
||||||
|
|
||||||
|
_send_establish(nic, packet.scada_frame, ESTABLISH_ACK.BAD_VERSION)
|
||||||
|
elseif dev_type == DEVICE_TYPE.PKT then
|
||||||
|
-- this is an attempt to establish a new pocket diagnostic session
|
||||||
|
local s_id = svsessions.establish_pdg_session(nic, src_addr, i_seq_num, firmware_v)
|
||||||
|
|
||||||
|
println(util.c("PKT (", firmware_v, ") [@", src_addr, "] \xbb connected"))
|
||||||
|
log.info(util.c("PDG_ESTABLISH: pocket (", firmware_v, ") [@", src_addr, "] connected with session ID ", s_id, " on ", nic.phy_name()))
|
||||||
|
|
||||||
|
_send_establish(nic, packet.scada_frame, ESTABLISH_ACK.ALLOW)
|
||||||
|
else
|
||||||
|
log.debug(util.c("illegal establish packet for device ", dev_type, " on PKT channel"))
|
||||||
|
_send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
-- PUBLIC FUNCTIONS --
|
-- PUBLIC FUNCTIONS --
|
||||||
|
|
||||||
---@class superv_comms
|
---@class superv_comms
|
||||||
@ -175,36 +378,31 @@ function supervisor.comms(_version, nic, fp_ok, facility)
|
|||||||
---@param distance integer
|
---@param distance integer
|
||||||
---@return modbus_frame|rplc_frame|mgmt_frame|crdn_frame|nil packet
|
---@return modbus_frame|rplc_frame|mgmt_frame|crdn_frame|nil packet
|
||||||
function public.parse_packet(side, sender, reply_to, message, distance)
|
function public.parse_packet(side, sender, reply_to, message, distance)
|
||||||
local s_pkt = nic.receive(side, sender, reply_to, message, distance)
|
local pkt, s_pkt, nic = nil, nil, backplane.nics[side]
|
||||||
local pkt = nil
|
|
||||||
|
if nic then
|
||||||
|
s_pkt = nic.receive(side, sender, reply_to, message, distance)
|
||||||
|
end
|
||||||
|
|
||||||
if s_pkt then
|
if s_pkt then
|
||||||
-- get as MODBUS TCP packet
|
-- get as MODBUS TCP packet
|
||||||
if s_pkt.protocol() == PROTOCOL.MODBUS_TCP then
|
if s_pkt.protocol() == PROTOCOL.MODBUS_TCP then
|
||||||
local m_pkt = comms.modbus_packet()
|
local m_pkt = comms.modbus_packet()
|
||||||
if m_pkt.decode(s_pkt) then
|
if m_pkt.decode(s_pkt) then pkt = m_pkt.get() end
|
||||||
pkt = m_pkt.get()
|
|
||||||
end
|
|
||||||
-- get as RPLC packet
|
-- get as RPLC packet
|
||||||
elseif s_pkt.protocol() == PROTOCOL.RPLC then
|
elseif 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
|
|
||||||
-- get as coordinator packet
|
-- get as coordinator packet
|
||||||
elseif s_pkt.protocol() == PROTOCOL.SCADA_CRDN then
|
elseif s_pkt.protocol() == PROTOCOL.SCADA_CRDN then
|
||||||
local crdn_pkt = comms.crdn_packet()
|
local crdn_pkt = comms.crdn_packet()
|
||||||
if crdn_pkt.decode(s_pkt) then
|
if crdn_pkt.decode(s_pkt) then pkt = crdn_pkt.get() end
|
||||||
pkt = crdn_pkt.get()
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
log.debug("attempted parse of illegal packet type " .. s_pkt.protocol(), true)
|
log.debug("receive[" .. side .. "] attempted parse of illegal packet type " .. s_pkt.protocol(), true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -214,6 +412,7 @@ function supervisor.comms(_version, nic, fp_ok, facility)
|
|||||||
-- handle a packet
|
-- handle a packet
|
||||||
---@param packet modbus_frame|rplc_frame|mgmt_frame|crdn_frame
|
---@param packet modbus_frame|rplc_frame|mgmt_frame|crdn_frame
|
||||||
function public.handle_packet(packet)
|
function public.handle_packet(packet)
|
||||||
|
local nic = backplane.nics[packet.scada_frame.interface()]
|
||||||
local l_chan = packet.scada_frame.local_channel()
|
local l_chan = packet.scada_frame.local_channel()
|
||||||
local r_chan = packet.scada_frame.remote_channel()
|
local r_chan = packet.scada_frame.remote_channel()
|
||||||
local src_addr = packet.scada_frame.src_addr()
|
local src_addr = packet.scada_frame.src_addr()
|
||||||
@ -226,81 +425,39 @@ function supervisor.comms(_version, nic, fp_ok, facility)
|
|||||||
-- look for an associated session
|
-- look for an associated session
|
||||||
local session = svsessions.find_plc_session(src_addr)
|
local session = svsessions.find_plc_session(src_addr)
|
||||||
|
|
||||||
if protocol == PROTOCOL.RPLC then
|
if session then
|
||||||
---@cast packet rplc_frame
|
if nic ~= session.nic then
|
||||||
-- reactor PLC packet
|
-- this is from the same device but on a different interface
|
||||||
if session ~= nil then
|
-- drop unless it is a connection probe
|
||||||
|
if (protocol == PROTOCOL.SCADA_MGMT) and (packet.type == MGMT_TYPE.PROBE) then
|
||||||
|
---@cast packet mgmt_frame
|
||||||
|
log.debug(util.c("PROBE_ACK: conflict with PLC @", src_addr, " on ", session.nic.phy_name(), " probed on ", nic.phy_name()))
|
||||||
|
_send_probe(nic, packet.scada_frame, PROBE_ACK.CONFLICT)
|
||||||
|
else
|
||||||
|
log.debug(util.c("unexpected packet for PLC @ ", src_addr, " received on ", nic.phy_name()))
|
||||||
|
end
|
||||||
|
else
|
||||||
-- pass the packet onto the session handler
|
-- pass the packet onto the session handler
|
||||||
session.in_queue.push_packet(packet)
|
session.in_queue.push_packet(packet)
|
||||||
else
|
|
||||||
-- any other packet should be session related, discard it
|
|
||||||
log.debug("discarding RPLC packet without a known session")
|
|
||||||
end
|
end
|
||||||
|
elseif protocol == PROTOCOL.RPLC then
|
||||||
|
-- reactor PLC packet should be session related, discard it
|
||||||
|
log.debug("discarding RPLC packet without a known session")
|
||||||
elseif protocol == PROTOCOL.SCADA_MGMT then
|
elseif protocol == PROTOCOL.SCADA_MGMT then
|
||||||
---@cast packet mgmt_frame
|
---@cast packet mgmt_frame
|
||||||
-- SCADA management packet
|
-- SCADA management packet
|
||||||
if session ~= nil then
|
if packet.type == MGMT_TYPE.ESTABLISH then
|
||||||
-- pass the packet onto the session handler
|
-- establish a new session: validate packet and continue
|
||||||
session.in_queue.push_packet(packet)
|
|
||||||
elseif packet.type == MGMT_TYPE.ESTABLISH then
|
|
||||||
-- establish a new session
|
|
||||||
local last_ack = self.last_est_acks[src_addr]
|
|
||||||
|
|
||||||
-- validate packet and continue
|
|
||||||
if packet.length >= 3 and type(packet.data[1]) == "string" and type(packet.data[2]) == "string" then
|
if packet.length >= 3 and type(packet.data[1]) == "string" and type(packet.data[2]) == "string" then
|
||||||
local comms_v = packet.data[1]
|
_establish_plc(nic, packet, src_addr, i_seq_num, self.last_est_acks[src_addr])
|
||||||
local firmware_v = packet.data[2]
|
|
||||||
local dev_type = packet.data[3]
|
|
||||||
|
|
||||||
if comms_v ~= comms.version then
|
|
||||||
if last_ack ~= ESTABLISH_ACK.BAD_VERSION then
|
|
||||||
log.info(util.c("dropping PLC establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")"))
|
|
||||||
end
|
|
||||||
|
|
||||||
_send_establish(packet.scada_frame, ESTABLISH_ACK.BAD_VERSION)
|
|
||||||
elseif dev_type == DEVICE_TYPE.PLC then
|
|
||||||
-- PLC linking request
|
|
||||||
if packet.length == 4 and type(packet.data[4]) == "number" then
|
|
||||||
local reactor_id = packet.data[4]
|
|
||||||
|
|
||||||
-- check ID validity
|
|
||||||
if reactor_id < 1 or reactor_id > config.UnitCount then
|
|
||||||
-- reactor index out of range
|
|
||||||
if last_ack ~= ESTABLISH_ACK.DENY then
|
|
||||||
log.warning(util.c("PLC_ESTABLISH: denied assignment ", reactor_id, " outside of configured unit count ", config.UnitCount))
|
|
||||||
end
|
|
||||||
|
|
||||||
_send_establish(packet.scada_frame, ESTABLISH_ACK.DENY)
|
|
||||||
else
|
|
||||||
-- try to establish the session
|
|
||||||
local plc_id = svsessions.establish_plc_session(src_addr, i_seq_num, reactor_id, firmware_v)
|
|
||||||
|
|
||||||
if plc_id == false then
|
|
||||||
-- reactor already has a PLC assigned
|
|
||||||
if last_ack ~= ESTABLISH_ACK.COLLISION then
|
|
||||||
log.warning(util.c("PLC_ESTABLISH: assignment collision with reactor ", reactor_id))
|
|
||||||
end
|
|
||||||
|
|
||||||
_send_establish(packet.scada_frame, ESTABLISH_ACK.COLLISION)
|
|
||||||
else
|
|
||||||
-- got an ID; assigned to a reactor successfully
|
|
||||||
println(util.c("PLC (", firmware_v, ") [@", src_addr, "] \xbb reactor ", reactor_id, " connected"))
|
|
||||||
log.info(util.c("PLC_ESTABLISH: PLC (", firmware_v, ") [@", src_addr, "] reactor unit ", reactor_id, " PLC connected with session ID ", plc_id))
|
|
||||||
_send_establish(packet.scada_frame, ESTABLISH_ACK.ALLOW)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
log.debug("PLC_ESTABLISH: packet length mismatch/bad parameter type")
|
|
||||||
_send_establish(packet.scada_frame, ESTABLISH_ACK.DENY)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
log.debug(util.c("illegal establish packet for device ", dev_type, " on PLC channel"))
|
|
||||||
_send_establish(packet.scada_frame, ESTABLISH_ACK.DENY)
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
log.debug("invalid establish packet (on PLC channel)")
|
log.debug("invalid establish packet (on PLC channel)")
|
||||||
_send_establish(packet.scada_frame, ESTABLISH_ACK.DENY)
|
_send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY)
|
||||||
end
|
end
|
||||||
|
elseif packet.type == MGMT_TYPE.PROBE then
|
||||||
|
-- connection probing
|
||||||
|
log.debug(util.c("PROBE_ACK: reporting open to PLC @", src_addr, " probed on ", nic.phy_name()))
|
||||||
|
_send_probe(nic, packet.scada_frame, PROBE_ACK.OPEN)
|
||||||
else
|
else
|
||||||
-- any other packet should be session related, discard it
|
-- any other packet should be session related, discard it
|
||||||
log.debug(util.c("discarding PLC SCADA_MGMT packet without a known session from computer ", src_addr))
|
log.debug(util.c("discarding PLC SCADA_MGMT packet without a known session from computer ", src_addr))
|
||||||
@ -312,62 +469,43 @@ function supervisor.comms(_version, nic, fp_ok, facility)
|
|||||||
-- look for an associated session
|
-- look for an associated session
|
||||||
local session = svsessions.find_rtu_session(src_addr)
|
local session = svsessions.find_rtu_session(src_addr)
|
||||||
|
|
||||||
if protocol == PROTOCOL.MODBUS_TCP then
|
if session then
|
||||||
---@cast packet modbus_frame
|
if nic ~= session.nic then
|
||||||
-- MODBUS response
|
-- this is from the same device but on a different interface
|
||||||
if session ~= nil then
|
-- drop unless it is a connection probe
|
||||||
|
if (protocol == PROTOCOL.SCADA_MGMT) and (packet.type == MGMT_TYPE.PROBE) then
|
||||||
|
---@cast packet mgmt_frame
|
||||||
|
log.debug(util.c("PROBE_ACK: conflict with RTU_GW @", src_addr, " on ", session.nic.phy_name(), " probed on ", nic.phy_name()))
|
||||||
|
_send_probe(nic, packet.scada_frame, PROBE_ACK.CONFLICT)
|
||||||
|
else
|
||||||
|
log.debug(util.c("unexpected packet for RTU_GW @ ", src_addr, " received on ", nic.phy_name()))
|
||||||
|
end
|
||||||
|
else
|
||||||
-- pass the packet onto the session handler
|
-- pass the packet onto the session handler
|
||||||
session.in_queue.push_packet(packet)
|
session.in_queue.push_packet(packet)
|
||||||
else
|
|
||||||
-- any other packet should be session related, discard it
|
|
||||||
log.debug("discarding MODBUS_TCP packet without a known session")
|
|
||||||
end
|
end
|
||||||
|
elseif protocol == PROTOCOL.MODBUS_TCP then
|
||||||
|
---@cast packet modbus_frame
|
||||||
|
-- MODBUS response, should be session related, discard it
|
||||||
|
log.debug("discarding MODBUS_TCP packet without a known session")
|
||||||
elseif protocol == PROTOCOL.SCADA_MGMT then
|
elseif protocol == PROTOCOL.SCADA_MGMT then
|
||||||
---@cast packet mgmt_frame
|
---@cast packet mgmt_frame
|
||||||
-- SCADA management packet
|
-- SCADA management packet
|
||||||
if session ~= nil then
|
if packet.type == MGMT_TYPE.ESTABLISH then
|
||||||
-- pass the packet onto the session handler
|
-- establish a new session: validate packet and continue
|
||||||
session.in_queue.push_packet(packet)
|
|
||||||
elseif packet.type == MGMT_TYPE.ESTABLISH then
|
|
||||||
-- establish a new session
|
|
||||||
local last_ack = self.last_est_acks[src_addr]
|
|
||||||
|
|
||||||
-- validate packet and continue
|
|
||||||
if packet.length >= 3 and type(packet.data[1]) == "string" and type(packet.data[2]) == "string" then
|
if packet.length >= 3 and type(packet.data[1]) == "string" and type(packet.data[2]) == "string" then
|
||||||
local comms_v = packet.data[1]
|
_establish_rtu_gw(nic, packet, src_addr, i_seq_num, self.last_est_acks[src_addr])
|
||||||
local firmware_v = packet.data[2]
|
|
||||||
local dev_type = packet.data[3]
|
|
||||||
|
|
||||||
if comms_v ~= comms.version then
|
|
||||||
if last_ack ~= ESTABLISH_ACK.BAD_VERSION then
|
|
||||||
log.info(util.c("dropping RTU establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")"))
|
|
||||||
end
|
|
||||||
|
|
||||||
_send_establish(packet.scada_frame, ESTABLISH_ACK.BAD_VERSION)
|
|
||||||
elseif dev_type == DEVICE_TYPE.RTU then
|
|
||||||
if packet.length == 4 then
|
|
||||||
-- this is an RTU advertisement for a new session
|
|
||||||
local rtu_advert = packet.data[4]
|
|
||||||
local s_id = svsessions.establish_rtu_session(src_addr, i_seq_num, rtu_advert, firmware_v)
|
|
||||||
|
|
||||||
println(util.c("RTU (", firmware_v, ") [@", src_addr, "] \xbb connected"))
|
|
||||||
log.info(util.c("RTU_ESTABLISH: RTU (",firmware_v, ") [@", src_addr, "] connected with session ID ", s_id))
|
|
||||||
_send_establish(packet.scada_frame, ESTABLISH_ACK.ALLOW)
|
|
||||||
else
|
|
||||||
log.debug("RTU_ESTABLISH: packet length mismatch")
|
|
||||||
_send_establish(packet.scada_frame, ESTABLISH_ACK.DENY)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
log.debug(util.c("illegal establish packet for device ", dev_type, " on RTU channel"))
|
|
||||||
_send_establish(packet.scada_frame, ESTABLISH_ACK.DENY)
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
log.debug("invalid establish packet (on RTU channel)")
|
log.debug("invalid establish packet (on RTU channel)")
|
||||||
_send_establish(packet.scada_frame, ESTABLISH_ACK.DENY)
|
_send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY)
|
||||||
end
|
end
|
||||||
|
elseif packet.type == MGMT_TYPE.PROBE then
|
||||||
|
-- connection probing
|
||||||
|
log.debug(util.c("PROBE_ACK: reporting open to RTU_GW @", src_addr, " probed on ", nic.phy_name()))
|
||||||
|
_send_probe(nic, packet.scada_frame, PROBE_ACK.OPEN)
|
||||||
else
|
else
|
||||||
-- any other packet should be session related, discard it
|
-- any other packet should be session related, discard it
|
||||||
log.debug(util.c("discarding RTU SCADA_MGMT packet without a known session from computer ", src_addr))
|
log.debug(util.c("discarding RTU gateway SCADA_MGMT packet without a known session from computer ", src_addr))
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
log.debug(util.c("illegal packet type ", protocol, " on RTU channel"))
|
log.debug(util.c("illegal packet type ", protocol, " on RTU channel"))
|
||||||
@ -376,110 +514,64 @@ function supervisor.comms(_version, nic, fp_ok, facility)
|
|||||||
-- look for an associated session
|
-- look for an associated session
|
||||||
local session = svsessions.find_crd_session(src_addr)
|
local session = svsessions.find_crd_session(src_addr)
|
||||||
|
|
||||||
if protocol == PROTOCOL.SCADA_MGMT then
|
if session then
|
||||||
---@cast packet mgmt_frame
|
if nic ~= session.nic then
|
||||||
-- SCADA management packet
|
-- this is from the same device but on a different interface
|
||||||
if session ~= nil then
|
-- drop unless it is a connection probe
|
||||||
|
if (protocol == PROTOCOL.SCADA_MGMT) and (packet.type == MGMT_TYPE.PROBE) then
|
||||||
|
---@cast packet mgmt_frame
|
||||||
|
log.debug(util.c("PROBE_ACK: conflict with CRD @", src_addr, " on ", session.nic.phy_name(), " probed on ", nic.phy_name()))
|
||||||
|
_send_probe(nic, packet.scada_frame, PROBE_ACK.CONFLICT)
|
||||||
|
else
|
||||||
|
log.debug(util.c("unexpected packet for CRD @ ", src_addr, " received on ", nic.phy_name()))
|
||||||
|
end
|
||||||
|
else
|
||||||
-- pass the packet onto the session handler
|
-- pass the packet onto the session handler
|
||||||
session.in_queue.push_packet(packet)
|
session.in_queue.push_packet(packet)
|
||||||
elseif packet.type == MGMT_TYPE.ESTABLISH then
|
end
|
||||||
-- establish a new session
|
elseif protocol == PROTOCOL.SCADA_MGMT then
|
||||||
local last_ack = self.last_est_acks[src_addr]
|
---@cast packet mgmt_frame
|
||||||
|
-- SCADA management packet
|
||||||
-- validate packet and continue
|
if packet.type == MGMT_TYPE.ESTABLISH then
|
||||||
|
-- establish a new session: validate packet and continue
|
||||||
if packet.length >= 3 and type(packet.data[1]) == "string" and type(packet.data[2]) == "string" then
|
if packet.length >= 3 and type(packet.data[1]) == "string" and type(packet.data[2]) == "string" then
|
||||||
local comms_v = packet.data[1]
|
_establish_crd(nic, packet, src_addr, i_seq_num, self.last_est_acks[src_addr])
|
||||||
local firmware_v = packet.data[2]
|
|
||||||
local dev_type = packet.data[3]
|
|
||||||
|
|
||||||
if comms_v ~= comms.version then
|
|
||||||
if last_ack ~= ESTABLISH_ACK.BAD_VERSION then
|
|
||||||
log.info(util.c("dropping coordinator establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")"))
|
|
||||||
end
|
|
||||||
|
|
||||||
_send_establish(packet.scada_frame, ESTABLISH_ACK.BAD_VERSION)
|
|
||||||
elseif dev_type == DEVICE_TYPE.CRD then
|
|
||||||
-- this is an attempt to establish a new coordinator session
|
|
||||||
local s_id = svsessions.establish_crd_session(src_addr, i_seq_num, firmware_v)
|
|
||||||
|
|
||||||
if s_id ~= false then
|
|
||||||
println(util.c("CRD (", firmware_v, ") [@", src_addr, "] \xbb connected"))
|
|
||||||
log.info(util.c("CRD_ESTABLISH: coordinator (", firmware_v, ") [@", src_addr, "] connected with session ID ", s_id))
|
|
||||||
|
|
||||||
_send_establish(packet.scada_frame, ESTABLISH_ACK.ALLOW, { config.UnitCount, facility.get_cooling_conf() })
|
|
||||||
else
|
|
||||||
if last_ack ~= ESTABLISH_ACK.COLLISION then
|
|
||||||
log.info("CRD_ESTABLISH: denied new coordinator [@" .. src_addr .. "] due to already being connected to another coordinator")
|
|
||||||
end
|
|
||||||
|
|
||||||
_send_establish(packet.scada_frame, ESTABLISH_ACK.COLLISION)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
log.debug(util.c("illegal establish packet for device ", dev_type, " on coordinator channel"))
|
|
||||||
_send_establish(packet.scada_frame, ESTABLISH_ACK.DENY)
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
log.debug("CRD_ESTABLISH: establish packet length mismatch")
|
log.debug("CRD_ESTABLISH: establish packet length mismatch")
|
||||||
_send_establish(packet.scada_frame, ESTABLISH_ACK.DENY)
|
_send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY)
|
||||||
end
|
end
|
||||||
|
elseif packet.type == MGMT_TYPE.PROBE then
|
||||||
|
-- connection probing
|
||||||
|
log.debug(util.c("PROBE_ACK: reporting open to CRD @", src_addr, " probed on ", nic.phy_name()))
|
||||||
|
_send_probe(nic, packet.scada_frame, PROBE_ACK.OPEN)
|
||||||
else
|
else
|
||||||
-- any other packet should be session related, discard it
|
-- any other packet should be session related, discard it
|
||||||
log.debug(util.c("discarding coordinator SCADA_MGMT packet without a known session from computer ", src_addr))
|
log.debug(util.c("discarding coordinator SCADA_MGMT packet without a known session from computer ", src_addr))
|
||||||
end
|
end
|
||||||
elseif protocol == PROTOCOL.SCADA_CRDN then
|
elseif protocol == PROTOCOL.SCADA_CRDN then
|
||||||
---@cast packet crdn_frame
|
---@cast packet crdn_frame
|
||||||
-- coordinator packet
|
-- coordinator packet, should be session related, discard it
|
||||||
if session ~= nil then
|
log.debug(util.c("discarding coordinator SCADA_CRDN packet without a known session from computer ", src_addr))
|
||||||
-- pass the packet onto the session handler
|
|
||||||
session.in_queue.push_packet(packet)
|
|
||||||
else
|
|
||||||
-- any other packet should be session related, discard it
|
|
||||||
log.debug(util.c("discarding coordinator SCADA_CRDN packet without a known session from computer ", src_addr))
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
log.debug(util.c("illegal packet type ", protocol, " on coordinator channel"))
|
log.debug(util.c("illegal packet type ", protocol, " on CRD channel"))
|
||||||
end
|
end
|
||||||
elseif r_chan == config.PKT_Channel then
|
elseif r_chan == config.PKT_Channel then
|
||||||
-- look for an associated session
|
-- look for an associated session
|
||||||
local session = svsessions.find_pdg_session(src_addr)
|
local session = svsessions.find_pdg_session(src_addr)
|
||||||
|
|
||||||
if protocol == PROTOCOL.SCADA_MGMT then
|
if session then
|
||||||
|
-- pass the packet onto the session handler
|
||||||
|
session.in_queue.push_packet(packet)
|
||||||
|
elseif protocol == PROTOCOL.SCADA_MGMT then
|
||||||
---@cast packet mgmt_frame
|
---@cast packet mgmt_frame
|
||||||
-- SCADA management packet
|
-- SCADA management packet
|
||||||
if session ~= nil then
|
if packet.type == MGMT_TYPE.ESTABLISH then
|
||||||
-- pass the packet onto the session handler
|
-- establish a new session: validate packet and continue
|
||||||
session.in_queue.push_packet(packet)
|
|
||||||
elseif packet.type == MGMT_TYPE.ESTABLISH then
|
|
||||||
-- establish a new session
|
|
||||||
local last_ack = self.last_est_acks[src_addr]
|
|
||||||
|
|
||||||
-- validate packet and continue
|
|
||||||
if packet.length >= 3 and type(packet.data[1]) == "string" and type(packet.data[2]) == "string" then
|
if packet.length >= 3 and type(packet.data[1]) == "string" and type(packet.data[2]) == "string" then
|
||||||
local comms_v = packet.data[1]
|
_establish_pdg(nic, packet, src_addr, i_seq_num, self.last_est_acks[src_addr])
|
||||||
local firmware_v = packet.data[2]
|
|
||||||
local dev_type = packet.data[3]
|
|
||||||
|
|
||||||
if comms_v ~= comms.version then
|
|
||||||
if last_ack ~= ESTABLISH_ACK.BAD_VERSION then
|
|
||||||
log.info(util.c("dropping PDG establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")"))
|
|
||||||
end
|
|
||||||
|
|
||||||
_send_establish(packet.scada_frame, ESTABLISH_ACK.BAD_VERSION)
|
|
||||||
elseif dev_type == DEVICE_TYPE.PKT then
|
|
||||||
-- this is an attempt to establish a new pocket diagnostic session
|
|
||||||
local s_id = svsessions.establish_pdg_session(src_addr, i_seq_num, firmware_v)
|
|
||||||
|
|
||||||
println(util.c("PKT (", firmware_v, ") [@", src_addr, "] \xbb connected"))
|
|
||||||
log.info(util.c("PDG_ESTABLISH: pocket (", firmware_v, ") [@", src_addr, "] connected with session ID ", s_id))
|
|
||||||
|
|
||||||
_send_establish(packet.scada_frame, ESTABLISH_ACK.ALLOW)
|
|
||||||
else
|
|
||||||
log.debug(util.c("illegal establish packet for device ", dev_type, " on pocket channel"))
|
|
||||||
_send_establish(packet.scada_frame, ESTABLISH_ACK.DENY)
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
log.debug("PDG_ESTABLISH: establish packet length mismatch")
|
log.debug("PDG_ESTABLISH: establish packet length mismatch")
|
||||||
_send_establish(packet.scada_frame, ESTABLISH_ACK.DENY)
|
_send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
-- any other packet should be session related, discard it
|
-- any other packet should be session related, discard it
|
||||||
@ -487,14 +579,8 @@ function supervisor.comms(_version, nic, fp_ok, facility)
|
|||||||
end
|
end
|
||||||
elseif protocol == PROTOCOL.SCADA_CRDN then
|
elseif protocol == PROTOCOL.SCADA_CRDN then
|
||||||
---@cast packet crdn_frame
|
---@cast packet crdn_frame
|
||||||
-- coordinator packet
|
-- coordinator packet, should be session related, discard it
|
||||||
if session ~= nil then
|
log.debug(util.c("discarding pocket SCADA_CRDN packet without a known session from computer ", src_addr))
|
||||||
-- pass the packet onto the session handler
|
|
||||||
session.in_queue.push_packet(packet)
|
|
||||||
else
|
|
||||||
-- any other packet should be session related, discard it
|
|
||||||
log.debug(util.c("discarding pocket SCADA_CRDN packet without a known session from computer ", src_addr))
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
log.debug(util.c("illegal packet type ", protocol, " on pocket channel"))
|
log.debug(util.c("illegal packet type ", protocol, " on pocket channel"))
|
||||||
end
|
end
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user