#528 coordinator configurator cleanup
This commit is contained in:
318
coordinator/config/facility.lua
Normal file
318
coordinator/config/facility.lua
Normal file
@@ -0,0 +1,318 @@
|
||||
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 core = require("graphics.core")
|
||||
|
||||
local Div = require("graphics.elements.Div")
|
||||
local ListBox = require("graphics.elements.ListBox")
|
||||
local MultiPane = require("graphics.elements.MultiPane")
|
||||
local TextBox = require("graphics.elements.TextBox")
|
||||
|
||||
local PushButton = require("graphics.elements.controls.PushButton")
|
||||
|
||||
local NumberField = require("graphics.elements.form.NumberField")
|
||||
|
||||
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,
|
||||
show_sv_cfg = nil, ---@type function
|
||||
|
||||
sv_conn_button = nil, ---@type PushButton
|
||||
sv_conn_status = nil, ---@type TextBox
|
||||
sv_conn_detail = nil, ---@type TextBox
|
||||
sv_next = nil, ---@type PushButton
|
||||
sv_skip = nil, ---@type PushButton
|
||||
|
||||
tool_ctl = nil, ---@type _crd_cfg_tool_ctl
|
||||
tmp_cfg = nil ---@type crd_config
|
||||
}
|
||||
|
||||
-- check if a value is an integer within a range (inclusive)
|
||||
---@param x any
|
||||
---@param min integer
|
||||
---@param max integer
|
||||
local function is_int_min_max(x, min, max) return util.is_int(x) and x >= min and x <= max end
|
||||
|
||||
-- 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.tmp_cfg.SVR_Channel, self.tmp_cfg.CRD_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.tmp_cfg.CRD_Channel then
|
||||
error_msg = "Error: unknown receive channel."
|
||||
elseif packet.scada_frame.remote_channel() == self.tmp_cfg.SVR_Channel and packet.scada_frame.protocol() == PROTOCOL.SCADA_MGMT then
|
||||
if packet.type == MGMT_TYPE.ESTABLISH then
|
||||
if packet.length == 2 then
|
||||
local est_ack = packet.data[1]
|
||||
local config = packet.data[2]
|
||||
|
||||
if est_ack == ESTABLISH_ACK.ALLOW then
|
||||
if type(config) == "table" and #config == 2 then
|
||||
local count_ok = is_int_min_max(config[1], 1, 4)
|
||||
local cool_ok = type(config[2]) == "table" and type(config[2].r_cool) == "table" and #config[2].r_cool == config[1]
|
||||
|
||||
if count_ok and cool_ok then
|
||||
self.tmp_cfg.UnitCount = config[1]
|
||||
self.tool_ctl.sv_cool_conf = {}
|
||||
|
||||
for i = 1, self.tmp_cfg.UnitCount do
|
||||
local num_b = config[2].r_cool[i].BoilerCount
|
||||
local num_t = config[2].r_cool[i].TurbineCount
|
||||
self.tool_ctl.sv_cool_conf[i] = { num_b, num_t }
|
||||
cool_ok = cool_ok and is_int_min_max(num_b, 0, 2) and is_int_min_max(num_t, 1, 3)
|
||||
end
|
||||
end
|
||||
|
||||
if not count_ok then
|
||||
error_msg = "Error: supervisor unit count out of range."
|
||||
elseif not cool_ok then
|
||||
error_msg = "Error: supervisor cooling configuration malformed."
|
||||
self.tool_ctl.sv_cool_conf = nil
|
||||
end
|
||||
|
||||
self.sv_addr = packet.scada_frame.src_addr()
|
||||
send_sv(MGMT_TYPE.CLOSE, {})
|
||||
else
|
||||
error_msg = "Error: invalid cooling configuration supervisor."
|
||||
end
|
||||
else
|
||||
error_msg = "Error: invalid allow reply length from supervisor."
|
||||
end
|
||||
elseif packet.length == 1 then
|
||||
local est_ack = packet.data[1]
|
||||
|
||||
if est_ack == ESTABLISH_ACK.DENY then
|
||||
error_msg = "Error: supervisor connection denied."
|
||||
elseif est_ack == ESTABLISH_ACK.COLLISION then
|
||||
error_msg = "Error: a coordinator is already/still connected. Please try again."
|
||||
elseif est_ack == ESTABLISH_ACK.BAD_VERSION then
|
||||
error_msg = "Error: coordinator comms version does not match supervisor comms version."
|
||||
else
|
||||
error_msg = "Error: invalid reply from supervisor."
|
||||
end
|
||||
else
|
||||
error_msg = "Error: invalid reply length from supervisor."
|
||||
end
|
||||
else
|
||||
error_msg = "Error: didn't get an establish reply from supervisor."
|
||||
end
|
||||
end
|
||||
|
||||
self.net_listen = false
|
||||
|
||||
if error_msg then
|
||||
self.sv_conn_status.set_value("")
|
||||
self.sv_conn_detail.set_value(error_msg)
|
||||
self.sv_conn_button.enable()
|
||||
else
|
||||
self.sv_conn_status.set_value("Connected!")
|
||||
self.sv_conn_detail.set_value("Data received successfully, press 'Next' to continue.")
|
||||
self.sv_skip.hide()
|
||||
self.sv_next.show()
|
||||
end
|
||||
end
|
||||
|
||||
-- handle supervisor connection failure
|
||||
local function handle_timeout()
|
||||
self.net_listen = false
|
||||
self.sv_conn_button.enable()
|
||||
self.sv_conn_status.set_value("Timed out.")
|
||||
self.sv_conn_detail.set_value("Supervisor did not reply. Ensure startup app is running on the supervisor.")
|
||||
end
|
||||
|
||||
-- attempt a connection to the supervisor to get cooling info
|
||||
local function sv_connect()
|
||||
self.sv_conn_button.disable()
|
||||
self.sv_conn_detail.set_value("")
|
||||
|
||||
local modem = ppm.get_wireless_modem()
|
||||
if modem == nil then
|
||||
self.sv_conn_status.set_value("Please connect an ender/wireless modem.")
|
||||
else
|
||||
self.sv_conn_status.set_value("Modem found, connecting...")
|
||||
if self.nic == nil then self.nic = network.nic(modem) end
|
||||
|
||||
self.nic.closeAll()
|
||||
self.nic.open(self.tmp_cfg.CRD_Channel)
|
||||
|
||||
self.sv_addr = comms.BROADCAST
|
||||
self.net_listen = true
|
||||
|
||||
send_sv(MGMT_TYPE.ESTABLISH, { comms.version, "0.0.0", DEVICE_TYPE.CRD })
|
||||
|
||||
tcd.dispatch_unique(8, handle_timeout)
|
||||
end
|
||||
end
|
||||
|
||||
local facility = {}
|
||||
|
||||
-- create the facility configuration view
|
||||
---@param tool_ctl _crd_cfg_tool_ctl
|
||||
---@param main_pane MultiPane
|
||||
---@param cfg_sys [ crd_config, crd_config, crd_config, { [1]: string, [2]: string, [3]: any }[], function ]
|
||||
---@param fac_cfg Div
|
||||
---@param style { [string]: cpair }
|
||||
---@return MultiPane fac_pane
|
||||
function facility.create(tool_ctl, main_pane, cfg_sys, fac_cfg, style)
|
||||
local _, ini_cfg, tmp_cfg, _, _ = cfg_sys[1], cfg_sys[2], cfg_sys[3], cfg_sys[4], cfg_sys[5]
|
||||
|
||||
self.tmp_cfg = tmp_cfg
|
||||
self.tool_ctl = tool_ctl
|
||||
|
||||
local bw_fg_bg = style.bw_fg_bg
|
||||
local g_lg_fg_bg = style.g_lg_fg_bg
|
||||
local nav_fg_bg = style.nav_fg_bg
|
||||
local btn_act_fg_bg = style.btn_act_fg_bg
|
||||
local btn_dis_fg_bg = style.btn_dis_fg_bg
|
||||
|
||||
--#region Facility
|
||||
|
||||
local fac_c_1 = Div{parent=fac_cfg,x=2,y=4,width=49}
|
||||
local fac_c_2 = Div{parent=fac_cfg,x=2,y=4,width=49}
|
||||
local fac_c_3 = Div{parent=fac_cfg,x=2,y=4,width=49}
|
||||
|
||||
local fac_pane = MultiPane{parent=fac_cfg,x=1,y=4,panes={fac_c_1,fac_c_2,fac_c_3}}
|
||||
|
||||
TextBox{parent=fac_cfg,x=1,y=2,text=" Facility Configuration",fg_bg=cpair(colors.black,colors.yellow)}
|
||||
|
||||
TextBox{parent=fac_c_1,x=1,y=1,height=4,text="This tool can attempt to connect to your supervisor computer. This would load facility information in order to get the unit count and aid monitor setup."}
|
||||
TextBox{parent=fac_c_1,x=1,y=6,height=2,text="The supervisor startup app must be running and fully configured on your supervisor computer."}
|
||||
|
||||
tool_ctl.sv_conn_status = TextBox{parent=fac_c_1,x=11,y=9,text=""}
|
||||
tool_ctl.sv_conn_detail = TextBox{parent=fac_c_1,x=1,y=11,height=2,text=""}
|
||||
|
||||
tool_ctl.sv_conn_button = PushButton{parent=fac_c_1,x=1,y=9,text="Connect",min_width=9,callback=function()sv_connect()end,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
||||
|
||||
local function sv_skip()
|
||||
tcd.abort(handle_timeout)
|
||||
tool_ctl.sv_cool_conf = nil
|
||||
self.net_listen = false
|
||||
fac_pane.set_value(2)
|
||||
end
|
||||
|
||||
local function sv_next()
|
||||
self.show_sv_cfg()
|
||||
tool_ctl.update_mon_reqs()
|
||||
fac_pane.set_value(3)
|
||||
end
|
||||
|
||||
PushButton{parent=fac_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}
|
||||
tool_ctl.sv_skip = PushButton{parent=fac_c_1,x=44,y=14,text="Skip \x1a",callback=sv_skip,fg_bg=cpair(colors.black,colors.red),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
||||
tool_ctl.sv_next = PushButton{parent=fac_c_1,x=44,y=14,text="Next \x1a",callback=sv_next,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg,hidden=true}
|
||||
|
||||
TextBox{parent=fac_c_2,x=1,y=1,height=3,text="Please enter the number of reactors you have, also referred to as reactor units or 'units' for short. A maximum of 4 is currently supported."}
|
||||
tool_ctl.num_units = NumberField{parent=fac_c_2,x=1,y=5,width=5,max_chars=2,default=ini_cfg.UnitCount,min=1,max=4,fg_bg=bw_fg_bg}
|
||||
TextBox{parent=fac_c_2,x=7,y=5,text="reactors"}
|
||||
TextBox{parent=fac_c_2,x=1,y=7,height=3,text="This will decide how many monitors you need. If this does not match the supervisor's number of reactor units, the coordinator will not connect.",fg_bg=g_lg_fg_bg}
|
||||
TextBox{parent=fac_c_2,x=1,y=10,height=3,text="Since you skipped supervisor sync, the main monitor minimum height can't be determined precisely. It is marked with * on the next page.",fg_bg=g_lg_fg_bg}
|
||||
|
||||
local nu_error = TextBox{parent=fac_c_2,x=8,y=14,width=35,text="Please set the number of reactors.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||
|
||||
local function submit_num_units()
|
||||
local count = tonumber(tool_ctl.num_units.get_value())
|
||||
if count ~= nil and count > 0 and count < 5 then
|
||||
nu_error.hide(true)
|
||||
tmp_cfg.UnitCount = count
|
||||
tool_ctl.update_mon_reqs()
|
||||
main_pane.set_value(4)
|
||||
else nu_error.show() end
|
||||
end
|
||||
|
||||
PushButton{parent=fac_c_2,x=1,y=14,text="\x1b Back",callback=function()fac_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=fac_c_2,x=44,y=14,text="Next \x1a",callback=submit_num_units,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
TextBox{parent=fac_c_3,x=1,y=1,height=2,text="The following facility configuration was fetched from your supervisor computer."}
|
||||
|
||||
local fac_config_list = ListBox{parent=fac_c_3,x=1,y=4,height=9,width=49,scroll_height=100,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
|
||||
|
||||
PushButton{parent=fac_c_3,x=1,y=14,text="\x1b Back",callback=function()fac_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=fac_c_3,x=44,y=14,text="Next \x1a",callback=function()main_pane.set_value(4)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
--#endregion
|
||||
|
||||
--#region Tool and Helper Functions
|
||||
|
||||
tool_ctl.is_int_min_max = is_int_min_max
|
||||
|
||||
-- reset the connection display for a new attempt
|
||||
function tool_ctl.init_sv_connect_ui()
|
||||
self.sv_next.hide()
|
||||
self.sv_skip.disable()
|
||||
self.sv_skip.show()
|
||||
self.sv_conn_button.enable()
|
||||
self.sv_conn_status.set_value("")
|
||||
self.sv_conn_detail.set_value("")
|
||||
|
||||
-- the user needs to wait a few seconds, encouraging the to connect
|
||||
tcd.dispatch_unique(2, function () self.sv_skip.enable() end)
|
||||
end
|
||||
|
||||
-- show the facility's unit count and cooling configuration data
|
||||
function self.show_sv_cfg()
|
||||
local conf = tool_ctl.sv_cool_conf
|
||||
fac_config_list.remove_all()
|
||||
|
||||
local str = util.sprintf("Facility has %d reactor unit%s:", #conf, tri(#conf==1,"","s"))
|
||||
TextBox{parent=fac_config_list,text=str,fg_bg=cpair(colors.gray,colors.white)}
|
||||
|
||||
for i = 1, #conf do
|
||||
local num_b, num_t = conf[i][1], conf[i][2]
|
||||
str = util.sprintf("\x07 Unit %d has %d boiler%s and %d turbine%s", i, num_b, tri(num_b == 1, "", "s"), num_t, tri(num_t == 1, "", "s"))
|
||||
TextBox{parent=fac_config_list,text=str,fg_bg=cpair(colors.gray,colors.white)}
|
||||
end
|
||||
end
|
||||
|
||||
--#endregion
|
||||
|
||||
return fac_pane
|
||||
end
|
||||
|
||||
-- handle incoming modem messages
|
||||
---@param side string
|
||||
---@param sender integer
|
||||
---@param reply_to integer
|
||||
---@param message any
|
||||
---@param distance integer
|
||||
function facility.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 facility
|
||||
Reference in New Issue
Block a user