#364 base RTU gateway self checks
This commit is contained in:
parent
d374967cb7
commit
c6a7de2669
232
rtu/config/check.lua
Normal file
232
rtu/config/check.lua
Normal file
@ -0,0 +1,232 @@
|
|||||||
|
local comms = require("scada-common.comms")
|
||||||
|
local network = require("scada-common.network")
|
||||||
|
local ppm = require("scada-common.ppm")
|
||||||
|
local tcd = require("scada-common.tcd")
|
||||||
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
|
local rtu = require("rtu.rtu")
|
||||||
|
|
||||||
|
local core = require("graphics.core")
|
||||||
|
|
||||||
|
local Div = require("graphics.elements.Div")
|
||||||
|
local ListBox = require("graphics.elements.ListBox")
|
||||||
|
local TextBox = require("graphics.elements.TextBox")
|
||||||
|
|
||||||
|
local PushButton = require("graphics.elements.controls.PushButton")
|
||||||
|
|
||||||
|
local tri = util.trinary
|
||||||
|
|
||||||
|
local cpair = core.cpair
|
||||||
|
|
||||||
|
local PROTOCOL = comms.PROTOCOL
|
||||||
|
local DEVICE_TYPE = comms.DEVICE_TYPE
|
||||||
|
local ESTABLISH_ACK = comms.ESTABLISH_ACK
|
||||||
|
local MGMT_TYPE = comms.MGMT_TYPE
|
||||||
|
|
||||||
|
local self = {
|
||||||
|
nic = nil, ---@type nic
|
||||||
|
net_listen = false,
|
||||||
|
sv_addr = comms.BROADCAST,
|
||||||
|
sv_seq_num = util.time_ms() * 10,
|
||||||
|
|
||||||
|
self_check_pass = true,
|
||||||
|
|
||||||
|
settings = nil, ---@type rtu_config
|
||||||
|
|
||||||
|
run_test_btn = nil, ---@type PushButton
|
||||||
|
sc_log = nil, ---@type ListBox
|
||||||
|
self_check_msg = nil ---@type function
|
||||||
|
}
|
||||||
|
|
||||||
|
-- report successful completion of the check
|
||||||
|
local function check_complete()
|
||||||
|
TextBox{parent=self.sc_log,text="> all tests passed!",fg_bg=cpair(colors.blue,colors._INHERIT)}
|
||||||
|
TextBox{parent=self.sc_log,text=""}
|
||||||
|
local more = Div{parent=self.sc_log,height=3,fg_bg=cpair(colors.gray,colors._INHERIT)}
|
||||||
|
TextBox{parent=more,text="if you still have a problem:"}
|
||||||
|
TextBox{parent=more,text="- check the wiki on GitHub"}
|
||||||
|
TextBox{parent=more,text="- ask for help on GitHub discussions or Discord"}
|
||||||
|
end
|
||||||
|
|
||||||
|
-- send a management packet to the supervisor
|
||||||
|
---@param msg_type MGMT_TYPE
|
||||||
|
---@param msg table
|
||||||
|
local function send_sv(msg_type, msg)
|
||||||
|
local s_pkt = comms.scada_packet()
|
||||||
|
local pkt = comms.mgmt_packet()
|
||||||
|
|
||||||
|
pkt.make(msg_type, msg)
|
||||||
|
s_pkt.make(self.sv_addr, self.sv_seq_num, PROTOCOL.SCADA_MGMT, pkt.raw_sendable())
|
||||||
|
|
||||||
|
self.nic.transmit(self.settings.SVR_Channel, self.settings.RTU_Channel, s_pkt)
|
||||||
|
self.sv_seq_num = self.sv_seq_num + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
-- handle an establish message from the supervisor
|
||||||
|
---@param packet mgmt_frame
|
||||||
|
local function handle_packet(packet)
|
||||||
|
local error_msg = nil
|
||||||
|
|
||||||
|
if packet.scada_frame.local_channel() ~= self.settings.RTU_Channel then
|
||||||
|
error_msg = "error: unknown receive channel"
|
||||||
|
elseif packet.scada_frame.remote_channel() == self.settings.SVR_Channel and packet.scada_frame.protocol() == PROTOCOL.SCADA_MGMT then
|
||||||
|
if packet.type == MGMT_TYPE.ESTABLISH then
|
||||||
|
if packet.length == 1 then
|
||||||
|
local est_ack = packet.data[1]
|
||||||
|
|
||||||
|
if est_ack== ESTABLISH_ACK.ALLOW then
|
||||||
|
self.self_check_msg(nil, true, "")
|
||||||
|
self.sv_addr = packet.scada_frame.src_addr()
|
||||||
|
send_sv(MGMT_TYPE.CLOSE, {})
|
||||||
|
if self.self_check_pass then check_complete() end
|
||||||
|
elseif est_ack == ESTABLISH_ACK.DENY then
|
||||||
|
error_msg = "error: supervisor connection denied"
|
||||||
|
elseif est_ack == ESTABLISH_ACK.BAD_VERSION then
|
||||||
|
error_msg = "RTU gateway comms version does not match supervisor comms version, make sure both devices are up-to-date (ccmsi update)"
|
||||||
|
else
|
||||||
|
error_msg = "error: invalid reply from supervisor"
|
||||||
|
end
|
||||||
|
else
|
||||||
|
error_msg = "error: invalid reply length from supervisor"
|
||||||
|
end
|
||||||
|
else
|
||||||
|
error_msg = "error: didn't get an establish reply from supervisor"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
self.net_listen = false
|
||||||
|
self.run_test_btn.enable()
|
||||||
|
|
||||||
|
if error_msg then
|
||||||
|
self.self_check_msg(nil, false, error_msg)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- handle supervisor connection failure
|
||||||
|
local function handle_timeout()
|
||||||
|
self.net_listen = false
|
||||||
|
self.run_test_btn.enable()
|
||||||
|
self.self_check_msg(nil, false, "make sure your supervisor is running, your channels are correct, trusted ranges are set properly (if enabled), facility keys match (if set), and if you are using wireless modems rather than ender modems, that your devices are close together in the same dimension")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- execute the self-check
|
||||||
|
local function self_check()
|
||||||
|
self.run_test_btn.disable()
|
||||||
|
|
||||||
|
self.sc_log.remove_all()
|
||||||
|
ppm.mount_all()
|
||||||
|
|
||||||
|
self.self_check_pass = true
|
||||||
|
|
||||||
|
local modem = ppm.get_wireless_modem()
|
||||||
|
local valid_cfg = rtu.validate_config(self.settings)
|
||||||
|
|
||||||
|
self.self_check_msg("> check wireless/ender modem connected...", modem ~= nil, "you must connect an ender or wireless modem to the RTU gateway")
|
||||||
|
|
||||||
|
self.self_check_msg("> check configuration...", valid_cfg, "go through Configure System and apply settings to set any missing settings and repair any corrupted ones")
|
||||||
|
|
||||||
|
if valid_cfg and modem then
|
||||||
|
self.self_check_msg("> check supervisor connection...")
|
||||||
|
|
||||||
|
-- init mac as needed
|
||||||
|
if self.settings.AuthKey and string.len(self.settings.AuthKey) >= 8 then
|
||||||
|
network.init_mac(self.settings.AuthKey)
|
||||||
|
else
|
||||||
|
network.deinit_mac()
|
||||||
|
end
|
||||||
|
|
||||||
|
self.nic = network.nic(modem)
|
||||||
|
|
||||||
|
self.nic.closeAll()
|
||||||
|
self.nic.open(self.settings.RTU_Channel)
|
||||||
|
|
||||||
|
self.sv_addr = comms.BROADCAST
|
||||||
|
self.net_listen = true
|
||||||
|
|
||||||
|
send_sv(MGMT_TYPE.ESTABLISH, { comms.version, "0.0.0", DEVICE_TYPE.RTU, {} })
|
||||||
|
|
||||||
|
tcd.dispatch_unique(8, handle_timeout)
|
||||||
|
else
|
||||||
|
if self.self_check_pass then check_complete() end
|
||||||
|
self.run_test_btn.enable()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- exit self check back home
|
||||||
|
---@param main_pane MultiPane
|
||||||
|
local function exit_self_check(main_pane)
|
||||||
|
tcd.abort(handle_timeout)
|
||||||
|
self.net_listen = false
|
||||||
|
self.run_test_btn.enable()
|
||||||
|
self.sc_log.remove_all()
|
||||||
|
main_pane.set_value(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
local check = {}
|
||||||
|
|
||||||
|
-- create the self-check view
|
||||||
|
---@param main_pane MultiPane
|
||||||
|
---@param settings_cfg rtu_config
|
||||||
|
---@param check_sys Div
|
||||||
|
---@param style { [string]: cpair }
|
||||||
|
function check.create(main_pane, settings_cfg, check_sys, style)
|
||||||
|
local bw_fg_bg = style.bw_fg_bg
|
||||||
|
local g_lg_fg_bg = style.g_lg_fg_bg
|
||||||
|
local nav_fg_bg = style.nav_fg_bg
|
||||||
|
local btn_act_fg_bg = style.btn_act_fg_bg
|
||||||
|
local btn_dis_fg_bg = style.btn_dis_fg_bg
|
||||||
|
|
||||||
|
self.settings = settings_cfg
|
||||||
|
|
||||||
|
local sc = Div{parent=check_sys,x=2,y=4,width=49}
|
||||||
|
|
||||||
|
TextBox{parent=check_sys,x=1,y=2,text=" RTU Gateway Self-Check",fg_bg=bw_fg_bg}
|
||||||
|
|
||||||
|
self.sc_log = ListBox{parent=sc,x=1,y=1,height=12,width=49,scroll_height=100,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
|
||||||
|
|
||||||
|
local last_check = { nil, nil }
|
||||||
|
|
||||||
|
function self.self_check_msg(msg, success, fail_msg)
|
||||||
|
if type(msg) == "string" then
|
||||||
|
last_check[1] = Div{parent=self.sc_log,height=1}
|
||||||
|
local e = TextBox{parent=last_check[1],text=msg,fg_bg=bw_fg_bg}
|
||||||
|
last_check[2] = e.get_x()+string.len(msg)
|
||||||
|
end
|
||||||
|
|
||||||
|
if type(fail_msg) == "string" then
|
||||||
|
TextBox{parent=last_check[1],x=last_check[2],y=1,text=tri(success,"PASS","FAIL"),fg_bg=tri(success,cpair(colors.green,colors._INHERIT),cpair(colors.red,colors._INHERIT))}
|
||||||
|
|
||||||
|
if not success then
|
||||||
|
local fail = Div{parent=self.sc_log,height=#util.strwrap(fail_msg, 46)}
|
||||||
|
TextBox{parent=fail,x=3,text=fail_msg,fg_bg=cpair(colors.gray,colors.white)}
|
||||||
|
end
|
||||||
|
|
||||||
|
self.self_check_pass = self.self_check_pass and success
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
PushButton{parent=sc,x=1,y=14,text="\x1b Back",callback=function()exit_self_check(main_pane)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
self.run_test_btn = PushButton{parent=sc,x=40,y=14,min_width=10,text="Run Test",callback=function()self_check()end,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
||||||
|
end
|
||||||
|
|
||||||
|
-- handle incoming modem messages
|
||||||
|
---@param side string
|
||||||
|
---@param sender integer
|
||||||
|
---@param reply_to integer
|
||||||
|
---@param message any
|
||||||
|
---@param distance integer
|
||||||
|
function check.receive_sv(side, sender, reply_to, message, distance)
|
||||||
|
if self.nic ~= nil and self.net_listen then
|
||||||
|
local s_pkt = self.nic.receive(side, sender, reply_to, message, distance)
|
||||||
|
|
||||||
|
if s_pkt and s_pkt.protocol() == PROTOCOL.SCADA_MGMT then
|
||||||
|
local mgmt_pkt = comms.mgmt_packet()
|
||||||
|
if mgmt_pkt.decode(s_pkt) then
|
||||||
|
tcd.abort(handle_timeout)
|
||||||
|
handle_packet(mgmt_pkt.get())
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return check
|
||||||
@ -7,6 +7,7 @@ local ppm = require("scada-common.ppm")
|
|||||||
local tcd = require("scada-common.tcd")
|
local tcd = require("scada-common.tcd")
|
||||||
local util = require("scada-common.util")
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
|
local check = require("rtu.config.check")
|
||||||
local peripherals = require("rtu.config.peripherals")
|
local peripherals = require("rtu.config.peripherals")
|
||||||
local redstone = require("rtu.config.redstone")
|
local redstone = require("rtu.config.redstone")
|
||||||
local system = require("rtu.config.system")
|
local system = require("rtu.config.system")
|
||||||
@ -169,8 +170,9 @@ local function config_view(display)
|
|||||||
local changelog = Div{parent=root_pane_div,x=1,y=1}
|
local changelog = Div{parent=root_pane_div,x=1,y=1}
|
||||||
local peri_cfg = Div{parent=root_pane_div,x=1,y=1}
|
local peri_cfg = Div{parent=root_pane_div,x=1,y=1}
|
||||||
local rs_cfg = Div{parent=root_pane_div,x=1,y=1}
|
local rs_cfg = Div{parent=root_pane_div,x=1,y=1}
|
||||||
|
local check_sys = Div{parent=root_pane_div,x=1,y=1}
|
||||||
|
|
||||||
local main_pane = MultiPane{parent=root_pane_div,x=1,y=1,panes={main_page,spkr_cfg,net_cfg,log_cfg,clr_cfg,summary,changelog,peri_cfg,rs_cfg}}
|
local main_pane = MultiPane{parent=root_pane_div,x=1,y=1,panes={main_page,spkr_cfg,net_cfg,log_cfg,clr_cfg,summary,changelog,peri_cfg,rs_cfg,check_sys}}
|
||||||
|
|
||||||
--#region Main Page
|
--#region Main Page
|
||||||
|
|
||||||
@ -226,8 +228,9 @@ local function config_view(display)
|
|||||||
|
|
||||||
PushButton{parent=main_page,x=2,y=17,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=main_page,x=2,y=17,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=btn_act_fg_bg}
|
||||||
local start_btn = PushButton{parent=main_page,x=42,y=17,min_width=9,text="Startup",callback=startup,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
local start_btn = PushButton{parent=main_page,x=42,y=17,min_width=9,text="Startup",callback=startup,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
||||||
tool_ctl.color_cfg = PushButton{parent=main_page,x=36,y=y_start,min_width=15,text="Color Options",callback=jump_color,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
PushButton{parent=main_page,x=39,y=y_start,min_width=12,text="Self-Check",callback=function()main_pane.set_value(10)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
||||||
PushButton{parent=main_page,x=39,y=y_start+2,min_width=12,text="Change Log",callback=function()main_pane.set_value(7)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
tool_ctl.color_cfg = PushButton{parent=main_page,x=36,y=y_start+2,min_width=15,text="Color Options",callback=jump_color,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
||||||
|
PushButton{parent=main_page,x=39,y=y_start+4,min_width=12,text="Change Log",callback=function()main_pane.set_value(7)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
if tool_ctl.ask_config then start_btn.disable() end
|
if tool_ctl.ask_config then start_btn.disable() end
|
||||||
|
|
||||||
@ -283,6 +286,12 @@ local function config_view(display)
|
|||||||
PushButton{parent=cl,x=1,y=14,text="\x1b Back",callback=function()main_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=cl,x=1,y=14,text="\x1b Back",callback=function()main_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
--#endregion
|
--#endregion
|
||||||
|
|
||||||
|
--#region Self-Check
|
||||||
|
|
||||||
|
check.create(main_pane, settings_cfg, check_sys, style)
|
||||||
|
|
||||||
|
--#endregion
|
||||||
end
|
end
|
||||||
|
|
||||||
-- reset terminal screen
|
-- reset terminal screen
|
||||||
@ -317,7 +326,7 @@ function configurator.configure(ask_config)
|
|||||||
config_view(display)
|
config_view(display)
|
||||||
|
|
||||||
while true do
|
while true do
|
||||||
local event, param1, param2, param3 = util.pull_event()
|
local event, param1, param2, param3, param4, param5 = util.pull_event()
|
||||||
|
|
||||||
-- handle event
|
-- handle event
|
||||||
if event == "timer" then
|
if event == "timer" then
|
||||||
@ -330,6 +339,8 @@ function configurator.configure(ask_config)
|
|||||||
if k_e then display.handle_key(k_e) end
|
if k_e then display.handle_key(k_e) end
|
||||||
elseif event == "paste" then
|
elseif event == "paste" then
|
||||||
display.handle_paste(param1)
|
display.handle_paste(param1)
|
||||||
|
elseif event == "modem_message" then
|
||||||
|
check.receive_sv(param1, param2, param3, param4, param5)
|
||||||
elseif event == "peripheral_detach" then
|
elseif event == "peripheral_detach" then
|
||||||
---@diagnostic disable-next-line: discard-returns
|
---@diagnostic disable-next-line: discard-returns
|
||||||
ppm.handle_unmount(param1)
|
ppm.handle_unmount(param1)
|
||||||
|
|||||||
48
rtu/rtu.lua
48
rtu/rtu.lua
@ -46,36 +46,42 @@ function rtu.load_config()
|
|||||||
config.FrontPanelTheme = settings.get("FrontPanelTheme")
|
config.FrontPanelTheme = settings.get("FrontPanelTheme")
|
||||||
config.ColorMode = settings.get("ColorMode")
|
config.ColorMode = settings.get("ColorMode")
|
||||||
|
|
||||||
|
return rtu.validate_config(config)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- validate a RTU gateway configuration
|
||||||
|
---@param cfg rtu_config
|
||||||
|
function rtu.validate_config(cfg)
|
||||||
local cfv = util.new_validator()
|
local cfv = util.new_validator()
|
||||||
|
|
||||||
cfv.assert_type_num(config.SpeakerVolume)
|
cfv.assert_type_num(cfg.SpeakerVolume)
|
||||||
cfv.assert_range(config.SpeakerVolume, 0, 3)
|
cfv.assert_range(cfg.SpeakerVolume, 0, 3)
|
||||||
|
|
||||||
cfv.assert_channel(config.SVR_Channel)
|
cfv.assert_channel(cfg.SVR_Channel)
|
||||||
cfv.assert_channel(config.RTU_Channel)
|
cfv.assert_channel(cfg.RTU_Channel)
|
||||||
cfv.assert_type_num(config.ConnTimeout)
|
cfv.assert_type_num(cfg.ConnTimeout)
|
||||||
cfv.assert_min(config.ConnTimeout, 2)
|
cfv.assert_min(cfg.ConnTimeout, 2)
|
||||||
cfv.assert_type_num(config.TrustedRange)
|
cfv.assert_type_num(cfg.TrustedRange)
|
||||||
cfv.assert_min(config.TrustedRange, 0)
|
cfv.assert_min(cfg.TrustedRange, 0)
|
||||||
cfv.assert_type_str(config.AuthKey)
|
cfv.assert_type_str(cfg.AuthKey)
|
||||||
|
|
||||||
if type(config.AuthKey) == "string" then
|
if type(cfg.AuthKey) == "string" then
|
||||||
local len = string.len(config.AuthKey)
|
local len = string.len(cfg.AuthKey)
|
||||||
cfv.assert(len == 0 or len >= 8)
|
cfv.assert(len == 0 or len >= 8)
|
||||||
end
|
end
|
||||||
|
|
||||||
cfv.assert_type_int(config.LogMode)
|
cfv.assert_type_int(cfg.LogMode)
|
||||||
cfv.assert_range(config.LogMode, 0, 1)
|
cfv.assert_range(cfg.LogMode, 0, 1)
|
||||||
cfv.assert_type_str(config.LogPath)
|
cfv.assert_type_str(cfg.LogPath)
|
||||||
cfv.assert_type_bool(config.LogDebug)
|
cfv.assert_type_bool(cfg.LogDebug)
|
||||||
|
|
||||||
cfv.assert_type_int(config.FrontPanelTheme)
|
cfv.assert_type_int(cfg.FrontPanelTheme)
|
||||||
cfv.assert_range(config.FrontPanelTheme, 1, 2)
|
cfv.assert_range(cfg.FrontPanelTheme, 1, 2)
|
||||||
cfv.assert_type_int(config.ColorMode)
|
cfv.assert_type_int(cfg.ColorMode)
|
||||||
cfv.assert_range(config.ColorMode, 1, themes.COLOR_MODE.NUM_MODES)
|
cfv.assert_range(cfg.ColorMode, 1, themes.COLOR_MODE.NUM_MODES)
|
||||||
|
|
||||||
cfv.assert_type_table(config.Peripherals)
|
cfv.assert_type_table(cfg.Peripherals)
|
||||||
cfv.assert_type_table(config.Redstone)
|
cfv.assert_type_table(cfg.Redstone)
|
||||||
|
|
||||||
return cfv.valid()
|
return cfv.valid()
|
||||||
end
|
end
|
||||||
|
|||||||
@ -31,7 +31,7 @@ local sna_rtu = require("rtu.dev.sna_rtu")
|
|||||||
local sps_rtu = require("rtu.dev.sps_rtu")
|
local sps_rtu = require("rtu.dev.sps_rtu")
|
||||||
local turbinev_rtu = require("rtu.dev.turbinev_rtu")
|
local turbinev_rtu = require("rtu.dev.turbinev_rtu")
|
||||||
|
|
||||||
local RTU_VERSION = "v1.11.6"
|
local RTU_VERSION = "v1.11.7"
|
||||||
|
|
||||||
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
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user