mirror of
https://github.com/MikaylaFischler/cc-mek-scada.git
synced 2024-08-30 18:22:34 +00:00
#306 RTU integration with new settings
This commit is contained in:
parent
9e13a3a467
commit
1b5e8cb69c
@ -156,6 +156,7 @@ local tool_ctl = {
|
|||||||
p_assign_end = nil, ---@type graphics_element
|
p_assign_end = nil, ---@type graphics_element
|
||||||
p_desc = nil, ---@type graphics_element
|
p_desc = nil, ---@type graphics_element
|
||||||
p_desc_ext = nil, ---@type graphics_element
|
p_desc_ext = nil, ---@type graphics_element
|
||||||
|
p_err = nil, ---@type graphics_element
|
||||||
|
|
||||||
rs_cfg_selection = nil, ---@type graphics_element
|
rs_cfg_selection = nil, ---@type graphics_element
|
||||||
rs_cfg_unit_l = nil, ---@type graphics_element
|
rs_cfg_unit_l = nil, ---@type graphics_element
|
||||||
@ -200,7 +201,6 @@ local side_options = { "Top", "Bottom", "Left", "Right", "Front", "Back" }
|
|||||||
local side_options_map = { "top", "bottom", "left", "right", "front", "back" }
|
local side_options_map = { "top", "bottom", "left", "right", "front", "back" }
|
||||||
local color_options = { "Red", "Orange", "Yellow", "Lime", "Green", "Cyan", "Light Blue", "Blue", "Purple", "Magenta", "Pink", "White", "Light Gray", "Gray", "Black", "Brown" }
|
local color_options = { "Red", "Orange", "Yellow", "Lime", "Green", "Cyan", "Light Blue", "Blue", "Purple", "Magenta", "Pink", "White", "Light Gray", "Gray", "Black", "Brown" }
|
||||||
local color_options_map = { colors.red, colors.orange, colors.yellow, colors.lime, colors.green, colors.cyan, colors.lightBlue, colors.blue, colors.purple, colors.magenta, colors.pink, colors.white, colors.lightGray, colors.gray, colors.black, colors.brown }
|
local color_options_map = { colors.red, colors.orange, colors.yellow, colors.lime, colors.green, colors.cyan, colors.lightBlue, colors.blue, colors.purple, colors.magenta, colors.pink, colors.white, colors.lightGray, colors.gray, colors.black, colors.brown }
|
||||||
local color_name_map = { [colors.red] = "red", [colors.orange] = "orange", [colors.yellow] = "yellow", [colors.lime] = "lime", [colors.green] = "green", [colors.cyan] = "cyan", [colors.lightBlue] = "lightBlue", [colors.blue] = "blue", [colors.purple] = "purple", [colors.magenta] = "magenta", [colors.pink] = "pink", [colors.white] = "white", [colors.lightGray] = "lightGray", [colors.gray] = "gray", [colors.black] = "black", [colors.brown] = "brown" }
|
|
||||||
|
|
||||||
-- convert text representation to index
|
-- convert text representation to index
|
||||||
---@param side string
|
---@param side string
|
||||||
@ -236,7 +236,7 @@ end
|
|||||||
---@param target rtu_config
|
---@param target rtu_config
|
||||||
---@param raw boolean? true to not use default values
|
---@param raw boolean? true to not use default values
|
||||||
local function load_settings(target, raw)
|
local function load_settings(target, raw)
|
||||||
for _, v in pairs(fields) do settings.unset(v[1]) end
|
for k, _ in pairs(tmp_cfg) do settings.unset(k) end
|
||||||
|
|
||||||
local loaded = settings.load("/rtu.settings")
|
local loaded = settings.load("/rtu.settings")
|
||||||
|
|
||||||
@ -271,13 +271,14 @@ local function config_view(display)
|
|||||||
|
|
||||||
--#region MAIN PAGE
|
--#region MAIN PAGE
|
||||||
|
|
||||||
local y_start = 5
|
local y_start = 2
|
||||||
|
|
||||||
TextBox{parent=main_page,x=2,y=2,height=2,text_align=CENTER,text="Welcome to the RTU gateway configurator! Please select one of the following options."}
|
|
||||||
|
|
||||||
if tool_ctl.ask_config then
|
if tool_ctl.ask_config then
|
||||||
TextBox{parent=main_page,x=2,y=y_start,height=4,width=49,text_align=CENTER,text="Notice: This device has no valid config so the configurator has been automatically started. If you previously had a valid config, you may want to check the Change Log to see what changed.",fg_bg=cpair(colors.red,colors.lightGray)}
|
TextBox{parent=main_page,x=2,y=y_start,height=4,width=49,text_align=CENTER,text="Notice: This device has no valid config so the configurator has been automatically started. If you previously had a valid config, you may want to check the Change Log to see what changed.",fg_bg=cpair(colors.red,colors.lightGray)}
|
||||||
y_start = y_start + 5
|
y_start = y_start + 5
|
||||||
|
else
|
||||||
|
TextBox{parent=main_page,x=2,y=2,height=2,text_align=CENTER,text="Welcome to the RTU gateway configurator! Please select one of the following options."}
|
||||||
|
y_start = y_start + 3
|
||||||
end
|
end
|
||||||
|
|
||||||
local function view_config()
|
local function view_config()
|
||||||
@ -520,8 +521,11 @@ local function config_view(display)
|
|||||||
if data ~= nil then element.set_value(data) end
|
if data ~= nil then element.set_value(data) end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function save_and_continue()
|
---@param exclude_conns boolean? true to exclude saving peripheral/redstone connections
|
||||||
for k, v in pairs(tmp_cfg) do settings.set(k, v) end
|
local function save_and_continue(exclude_conns)
|
||||||
|
for k, v in pairs(tmp_cfg) do
|
||||||
|
if not (exclude_conns and (k == "Peripherals" or k == "Redstone")) then settings.set(k, v) end
|
||||||
|
end
|
||||||
|
|
||||||
if settings.save("rtu.settings") then
|
if settings.save("rtu.settings") then
|
||||||
load_settings(ini_cfg)
|
load_settings(ini_cfg)
|
||||||
@ -537,8 +541,16 @@ local function config_view(display)
|
|||||||
try_set(path, ini_cfg.LogPath)
|
try_set(path, ini_cfg.LogPath)
|
||||||
try_set(en_dbg, ini_cfg.LogDebug)
|
try_set(en_dbg, ini_cfg.LogDebug)
|
||||||
|
|
||||||
|
if not exclude_conns then
|
||||||
|
tmp_cfg.Peripherals = deep_copy_peri(ini_cfg.Peripherals)
|
||||||
|
tmp_cfg.Redstone = deep_copy_rs(ini_cfg.Redstone)
|
||||||
|
|
||||||
|
tool_ctl.update_peri_list()
|
||||||
|
end
|
||||||
|
|
||||||
tool_ctl.dev_cfg.enable()
|
tool_ctl.dev_cfg.enable()
|
||||||
tool_ctl.rs_cfg.enable()
|
tool_ctl.rs_cfg.enable()
|
||||||
|
tool_ctl.view_gw_cfg.enable()
|
||||||
|
|
||||||
if tool_ctl.importing_legacy then
|
if tool_ctl.importing_legacy then
|
||||||
tool_ctl.importing_legacy = false
|
tool_ctl.importing_legacy = false
|
||||||
@ -549,7 +561,7 @@ local function config_view(display)
|
|||||||
|
|
||||||
PushButton{parent=sum_c_1,x=1,y=14,min_width=6,text="\x1b Back",callback=back_from_settings,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=sum_c_1,x=1,y=14,min_width=6,text="\x1b Back",callback=back_from_settings,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
tool_ctl.show_key_btn = PushButton{parent=sum_c_1,x=8,y=14,min_width=17,text="Unhide Auth Key",callback=function()tool_ctl.show_auth_key()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)}
|
tool_ctl.show_key_btn = PushButton{parent=sum_c_1,x=8,y=14,min_width=17,text="Unhide Auth Key",callback=function()tool_ctl.show_auth_key()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)}
|
||||||
tool_ctl.settings_apply = PushButton{parent=sum_c_1,x=43,y=14,min_width=7,text="Apply",callback=save_and_continue,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg}
|
tool_ctl.settings_apply = PushButton{parent=sum_c_1,x=43,y=14,min_width=7,text="Apply",callback=function()save_and_continue(true)end,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg}
|
||||||
tool_ctl.settings_confirm = PushButton{parent=sum_c_1,x=41,y=14,min_width=9,text="Confirm",callback=function()sum_pane.set_value(2)end,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg}
|
tool_ctl.settings_confirm = PushButton{parent=sum_c_1,x=41,y=14,min_width=9,text="Confirm",callback=function()sum_pane.set_value(2)end,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg}
|
||||||
tool_ctl.settings_confirm.hide()
|
tool_ctl.settings_confirm.hide()
|
||||||
|
|
||||||
@ -648,8 +660,10 @@ local function config_view(display)
|
|||||||
|
|
||||||
tool_ctl.ppm_devs = ListBox{parent=peri_c_2,x=1,y=3,height=10,width=51,scroll_height=1000,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
|
tool_ctl.ppm_devs = ListBox{parent=peri_c_2,x=1,y=3,height=10,width=51,scroll_height=1000,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
|
||||||
|
|
||||||
|
PushButton{parent=peri_c_2,x=1,y=14,min_width=6,text="\x1b Back",callback=function()peri_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
PushButton{parent=peri_c_2,x=8,y=14,min_width=10,text="Manual +",callback=function()peri_pane.set_value(3)end,fg_bg=cpair(colors.black,colors.orange),active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=peri_c_2,x=8,y=14,min_width=10,text="Manual +",callback=function()peri_pane.set_value(3)end,fg_bg=cpair(colors.black,colors.orange),active_fg_bg=btn_act_fg_bg}
|
||||||
PushButton{parent=peri_c_2,x=26,y=14,min_width=24,text="I don't see my device!",callback=function()peri_pane.set_value(7)end,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=peri_c_2,x=26,y=14,min_width=24,text="I don't see my device!",callback=function()peri_pane.set_value(7)end,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=peri_c_7,x=1,y=1,height=10,text="Make sure your device is either touching the RTU, or connected via wired modems. There should be a wired modem on a side of the RTU then one on the device, connected by a cable. The modem on the device needs to be right clicked to connect it (which will turn its border red), at which point the peripheral name will be shown in the chat."}
|
TextBox{parent=peri_c_7,x=1,y=1,height=10,text="Make sure your device is either touching the RTU, or connected via wired modems. There should be a wired modem on a side of the RTU then one on the device, connected by a cable. The modem on the device needs to be right clicked to connect it (which will turn its border red), at which point the peripheral name will be shown in the chat."}
|
||||||
TextBox{parent=peri_c_7,x=1,y=9,height=4,text="If it still does not show, it may not be compatible. Currently only Boilers, Turbines, Dynamic Tanks, SNAs, SPSs, Induction Matricies, and Environment Detectors are supported."}
|
TextBox{parent=peri_c_7,x=1,y=9,height=4,text="If it still does not show, it may not be compatible. Currently only Boilers, Turbines, Dynamic Tanks, SNAs, SPSs, Induction Matricies, and Environment Detectors are supported."}
|
||||||
PushButton{parent=peri_c_7,x=1,y=14,min_width=6,text="\x1b Back",callback=function()peri_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=peri_c_7,x=1,y=14,min_width=6,text="\x1b Back",callback=function()peri_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
@ -659,6 +673,7 @@ local function config_view(display)
|
|||||||
new_peri_attrs = { name, type }
|
new_peri_attrs = { name, type }
|
||||||
tool_ctl.peri_cfg_editing = false
|
tool_ctl.peri_cfg_editing = false
|
||||||
|
|
||||||
|
tool_ctl.p_err.hide(true)
|
||||||
tool_ctl.p_name_msg.set_value("Configuring peripheral on '" .. name .. "':")
|
tool_ctl.p_name_msg.set_value("Configuring peripheral on '" .. name .. "':")
|
||||||
tool_ctl.p_desc_ext.set_value("")
|
tool_ctl.p_desc_ext.set_value("")
|
||||||
|
|
||||||
@ -769,8 +784,6 @@ local function config_view(display)
|
|||||||
|
|
||||||
tool_ctl.update_peri_list()
|
tool_ctl.update_peri_list()
|
||||||
|
|
||||||
PushButton{parent=peri_c_2,x=1,y=14,min_width=6,text="\x1b Back",callback=function()peri_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
|
||||||
|
|
||||||
TextBox{parent=peri_c_3,x=1,y=1,height=4,text_align=CENTER,text="This feature is intended for advanced users. If you are clicking this just because your device is not shown, follow the connection instructions in 'I don't see my device!'."}
|
TextBox{parent=peri_c_3,x=1,y=1,height=4,text_align=CENTER,text="This feature is intended for advanced users. If you are clicking this just because your device is not shown, follow the connection instructions in 'I don't see my device!'."}
|
||||||
TextBox{parent=peri_c_3,x=1,y=6,height=4,text_align=CENTER,text="Peripheral Name"}
|
TextBox{parent=peri_c_3,x=1,y=6,height=4,text_align=CENTER,text="Peripheral Name"}
|
||||||
local p_name = TextField{parent=peri_c_3,x=1,y=7,width=49,height=1,max_len=128,fg_bg=bw_fg_bg}
|
local p_name = TextField{parent=peri_c_3,x=1,y=7,width=49,height=1,max_len=128,fg_bg=bw_fg_bg}
|
||||||
@ -813,8 +826,8 @@ local function config_view(display)
|
|||||||
tool_ctl.p_desc = TextBox{parent=peri_c_4,x=1,y=7,height=6,text_align=LEFT,text="",fg_bg=g_lg_fg_bg}
|
tool_ctl.p_desc = TextBox{parent=peri_c_4,x=1,y=7,height=6,text_align=LEFT,text="",fg_bg=g_lg_fg_bg}
|
||||||
tool_ctl.p_desc_ext = TextBox{parent=peri_c_4,x=1,y=6,height=7,text_align=LEFT,text="",fg_bg=g_lg_fg_bg}
|
tool_ctl.p_desc_ext = TextBox{parent=peri_c_4,x=1,y=6,height=7,text_align=LEFT,text="",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
local p_err = TextBox{parent=peri_c_4,x=8,y=14,height=1,width=35,text_align=LEFT,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
tool_ctl.p_err = TextBox{parent=peri_c_4,x=8,y=14,height=1,width=35,text_align=LEFT,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||||
p_err.hide(true)
|
tool_ctl.p_err.hide(true)
|
||||||
|
|
||||||
local function back_from_peri_opts()
|
local function back_from_peri_opts()
|
||||||
if tool_ctl.peri_cfg_editing ~= false then
|
if tool_ctl.peri_cfg_editing ~= false then
|
||||||
@ -842,33 +855,33 @@ local function config_view(display)
|
|||||||
if (peri_type == "dynamicValve" or peri_type == "environmentDetector") and for_facility then
|
if (peri_type == "dynamicValve" or peri_type == "environmentDetector") and for_facility then
|
||||||
-- skip
|
-- skip
|
||||||
elseif not (util.is_int(u) and u > 0 and u < 5) then
|
elseif not (util.is_int(u) and u > 0 and u < 5) then
|
||||||
p_err.set_value("Unit ID must be within 1 through 4.")
|
tool_ctl.p_err.set_value("Unit ID must be within 1 through 4.")
|
||||||
p_err.show()
|
tool_ctl.p_err.show()
|
||||||
return
|
return
|
||||||
else unit = u end
|
else unit = u end
|
||||||
end
|
end
|
||||||
|
|
||||||
if peri_type == "boilerValve" then
|
if peri_type == "boilerValve" then
|
||||||
if not (idx == 1 or idx == 2) then
|
if not (idx == 1 or idx == 2) then
|
||||||
p_err.set_value("Index must be 1 or 2.")
|
tool_ctl.p_err.set_value("Index must be 1 or 2.")
|
||||||
p_err.show()
|
tool_ctl.p_err.show()
|
||||||
return
|
return
|
||||||
else index = idx end
|
else index = idx end
|
||||||
elseif peri_type == "turbineValve" then
|
elseif peri_type == "turbineValve" then
|
||||||
if not (idx == 1 or idx == 2 or idx == 3) then
|
if not (idx == 1 or idx == 2 or idx == 3) then
|
||||||
p_err.set_value("Index must be 1, 2, or 3.")
|
tool_ctl.p_err.set_value("Index must be 1, 2, or 3.")
|
||||||
p_err.show()
|
tool_ctl.p_err.show()
|
||||||
return
|
return
|
||||||
else index = idx end
|
else index = idx end
|
||||||
elseif peri_type == "dynamicValve" and not for_facility then
|
elseif peri_type == "dynamicValve" and for_facility then
|
||||||
if not (util.is_int(idx) and idx > 0 and idx < 5) then
|
if not (util.is_int(idx) and idx > 0 and idx < 5) then
|
||||||
p_err.set_value("Index must be within 1 through 4.")
|
tool_ctl.p_err.set_value("Index must be within 1 through 4.")
|
||||||
p_err.show()
|
tool_ctl.p_err.show()
|
||||||
return
|
return
|
||||||
else index = idx end
|
else index = idx end
|
||||||
end
|
end
|
||||||
|
|
||||||
p_err.hide(true)
|
tool_ctl.p_err.hide(true)
|
||||||
|
|
||||||
---@type rtu_peri_definition
|
---@type rtu_peri_definition
|
||||||
local def = { name = peri_name, unit = unit, index = index }
|
local def = { name = peri_name, unit = unit, index = index }
|
||||||
@ -1124,7 +1137,7 @@ local function config_view(display)
|
|||||||
local unit = "facility"
|
local unit = "facility"
|
||||||
|
|
||||||
if def.unit then unit = "unit " .. def.unit end
|
if def.unit then unit = "unit " .. def.unit end
|
||||||
if def.color ~= nil then conn = def.side .. "/" .. color_name_map[def.color] end
|
if def.color ~= nil then conn = def.side .. "/" .. rsio.color_name(def.color) end
|
||||||
|
|
||||||
local line = Div{parent=rs_import_list,height=1}
|
local line = Div{parent=rs_import_list,height=1}
|
||||||
TextBox{parent=line,x=1,y=1,width=1,height=1,text=io_dir,fg_bg=cpair(colors.lightGray,colors.white)}
|
TextBox{parent=line,x=1,y=1,width=1,height=1,text=io_dir,fg_bg=cpair(colors.lightGray,colors.white)}
|
||||||
@ -1202,6 +1215,67 @@ local function config_view(display)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param def rtu_peri_definition
|
||||||
|
---@param idx integer
|
||||||
|
---@param type string
|
||||||
|
local function edit_peri_entry(idx, def, type)
|
||||||
|
-- set inputs BEFORE calling new_peri()
|
||||||
|
if def.index ~= nil then tool_ctl.p_idx.set_value(def.index) end
|
||||||
|
if def.unit == nil then
|
||||||
|
tool_ctl.p_assign_btn.set_value(1)
|
||||||
|
else
|
||||||
|
tool_ctl.p_unit.set_value(def.unit)
|
||||||
|
tool_ctl.p_assign_btn.set_value(2)
|
||||||
|
end
|
||||||
|
|
||||||
|
new_peri(def.name, type)
|
||||||
|
|
||||||
|
-- set editing mode AFTER new_peri()
|
||||||
|
tool_ctl.peri_cfg_editing = idx
|
||||||
|
end
|
||||||
|
|
||||||
|
local function delete_peri_entry(idx)
|
||||||
|
table.remove(tmp_cfg.Peripherals, idx)
|
||||||
|
tool_ctl.gen_peri_summary(tmp_cfg)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- generate the peripherals summary list
|
||||||
|
---@param cfg rtu_config
|
||||||
|
function tool_ctl.gen_peri_summary(cfg)
|
||||||
|
peri_list.remove_all()
|
||||||
|
|
||||||
|
for i = 1, #cfg.Peripherals do
|
||||||
|
local def = cfg.Peripherals[i] ---@type rtu_peri_definition
|
||||||
|
|
||||||
|
local t = ppm.get_type(def.name)
|
||||||
|
local t_str = "<disconnected> (connect to edit)"
|
||||||
|
local disconnected = t == nil
|
||||||
|
|
||||||
|
if not disconnected then t_str = "[" .. t .. "]" end
|
||||||
|
|
||||||
|
local desc = " \x1a "
|
||||||
|
|
||||||
|
if type(def.index) == "number" then
|
||||||
|
desc = desc .. "#" .. def.index .. " "
|
||||||
|
end
|
||||||
|
|
||||||
|
if type(def.unit) == "number" then
|
||||||
|
desc = desc .. "for unit " .. def.unit
|
||||||
|
else
|
||||||
|
desc = desc .. "for the facility"
|
||||||
|
end
|
||||||
|
|
||||||
|
local entry = Div{parent=peri_list,height=3}
|
||||||
|
TextBox{parent=entry,x=1,y=1,height=1,text="@ "..def.name,fg_bg=cpair(colors.black,colors.white)}
|
||||||
|
TextBox{parent=entry,x=1,y=2,height=1,text=" \x1a "..t_str,fg_bg=cpair(colors.gray,colors.white)}
|
||||||
|
TextBox{parent=entry,x=1,y=3,height=1,text=desc,fg_bg=cpair(colors.gray,colors.white)}
|
||||||
|
local edit_btn = PushButton{parent=entry,x=41,y=2,min_width=8,height=1,text="EDIT",callback=function()edit_peri_entry(i,def,t or "")end,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)}
|
||||||
|
PushButton{parent=entry,x=41,y=3,min_width=8,height=1,text="DELETE",callback=function()delete_peri_entry(i)end,fg_bg=cpair(colors.black,colors.red),active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
|
if disconnected then edit_btn.disable() end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
local function edit_rs_entry(idx)
|
local function edit_rs_entry(idx)
|
||||||
local def = tmp_cfg.Redstone[idx] ---@type rtu_rs_definition
|
local def = tmp_cfg.Redstone[idx] ---@type rtu_rs_definition
|
||||||
|
|
||||||
@ -1252,7 +1326,7 @@ local function config_view(display)
|
|||||||
local conn = def.side
|
local conn = def.side
|
||||||
local unit = util.strval(def.unit or "F")
|
local unit = util.strval(def.unit or "F")
|
||||||
|
|
||||||
if def.color ~= nil then conn = def.side .. "/" .. color_name_map[def.color] end
|
if def.color ~= nil then conn = def.side .. "/" .. rsio.color_name(def.color) end
|
||||||
|
|
||||||
local entry = Div{parent=rs_list,height=1}
|
local entry = Div{parent=rs_list,height=1}
|
||||||
TextBox{parent=entry,x=1,y=1,width=1,height=1,text=io_dir,fg_bg=cpair(colors.lightGray,colors.white)}
|
TextBox{parent=entry,x=1,y=1,width=1,height=1,text=io_dir,fg_bg=cpair(colors.lightGray,colors.white)}
|
||||||
@ -1263,53 +1337,6 @@ local function config_view(display)
|
|||||||
PushButton{parent=entry,x=41,y=1,min_width=8,height=1,text="DELETE",callback=function()delete_rs_entry(i)end,fg_bg=cpair(colors.black,colors.red),active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=entry,x=41,y=1,min_width=8,height=1,text="DELETE",callback=function()delete_rs_entry(i)end,fg_bg=cpair(colors.black,colors.red),active_fg_bg=btn_act_fg_bg}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function edit_peri_entry(idx, name, type)
|
|
||||||
new_peri(name, type)
|
|
||||||
tool_ctl.peri_cfg_editing = idx -- must be after new_peri
|
|
||||||
end
|
|
||||||
|
|
||||||
local function delete_peri_entry(idx)
|
|
||||||
table.remove(tmp_cfg.Peripherals, idx)
|
|
||||||
tool_ctl.gen_peri_summary(tmp_cfg)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- generate the peripherals summary list
|
|
||||||
---@param cfg rtu_config
|
|
||||||
function tool_ctl.gen_peri_summary(cfg)
|
|
||||||
peri_list.remove_all()
|
|
||||||
|
|
||||||
for i = 1, #cfg.Peripherals do
|
|
||||||
local def = cfg.Peripherals[i] ---@type rtu_peri_definition
|
|
||||||
|
|
||||||
local t = ppm.get_type(def.name)
|
|
||||||
local t_str = "<disconnected> (connect to edit)"
|
|
||||||
local disconnected = t == nil
|
|
||||||
|
|
||||||
if not disconnected then t_str = "[" .. t .. "]" end
|
|
||||||
|
|
||||||
local desc = " \x1a "
|
|
||||||
|
|
||||||
if type(def.index) == "number" then
|
|
||||||
desc = desc .. "#" .. def.index .. " "
|
|
||||||
end
|
|
||||||
|
|
||||||
if type(def.unit) == "number" then
|
|
||||||
desc = desc .. "for unit " .. def.unit
|
|
||||||
else
|
|
||||||
desc = desc .. "for the facility"
|
|
||||||
end
|
|
||||||
|
|
||||||
local entry = Div{parent=peri_list,height=3}
|
|
||||||
TextBox{parent=entry,x=1,y=1,height=1,text="@ "..def.name,fg_bg=cpair(colors.black,colors.white)}
|
|
||||||
TextBox{parent=entry,x=1,y=2,height=1,text=" \x1a "..t_str,fg_bg=cpair(colors.gray,colors.white)}
|
|
||||||
TextBox{parent=entry,x=1,y=3,height=1,text=desc,fg_bg=cpair(colors.gray,colors.white)}
|
|
||||||
local edit_btn = PushButton{parent=entry,x=41,y=2,min_width=8,height=1,text="EDIT",callback=function()edit_peri_entry(i,def.name,t)end,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)}
|
|
||||||
PushButton{parent=entry,x=41,y=3,min_width=8,height=1,text="DELETE",callback=function()delete_peri_entry(i)end,fg_bg=cpair(colors.black,colors.red),active_fg_bg=btn_act_fg_bg}
|
|
||||||
|
|
||||||
if disconnected then edit_btn.disable() end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- reset terminal screen
|
-- reset terminal screen
|
||||||
|
@ -109,12 +109,10 @@ local function init(panel, units)
|
|||||||
unit_hw.register(databus.ps, "unit_hw_" .. i, unit_hw.update)
|
unit_hw.register(databus.ps, "unit_hw_" .. i, unit_hw.update)
|
||||||
|
|
||||||
-- unit name identifier (type + index)
|
-- unit name identifier (type + index)
|
||||||
local name = util.c(UNIT_TYPE_LABELS[unit.type + 1], " ", unit.index)
|
local function get_name(t) return util.c(UNIT_TYPE_LABELS[t + 1], " ", util.trinary(util.is_int(unit.index), unit.index, "")) end
|
||||||
local name_box = TextBox{parent=unit_hw_statuses,y=i,x=3,text=name,height=1}
|
local name_box = TextBox{parent=unit_hw_statuses,y=i,x=3,text=get_name(unit.type),height=1}
|
||||||
|
|
||||||
name_box.register(databus.ps, "unit_type_" .. i, function (t)
|
name_box.register(databus.ps, "unit_type_" .. i, function (t) name_box.set_value(get_name(t)) end)
|
||||||
name_box.set_value(util.c(UNIT_TYPE_LABELS[t + 1], " ", unit.index))
|
|
||||||
end)
|
|
||||||
|
|
||||||
-- assignment (unit # or facility)
|
-- assignment (unit # or facility)
|
||||||
local for_unit = util.trinary(unit.reactor == 0, "\x1a FACIL ", "\x1a UNIT " .. unit.reactor)
|
local for_unit = util.trinary(unit.reactor == 0, "\x1a FACIL ", "\x1a UNIT " .. unit.reactor)
|
||||||
|
63
rtu/rtu.lua
63
rtu/rtu.lua
@ -5,7 +5,6 @@ local log = require("scada-common.log")
|
|||||||
local types = require("scada-common.types")
|
local types = require("scada-common.types")
|
||||||
local util = require("scada-common.util")
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
local config = require("rtu.config")
|
|
||||||
local databus = require("rtu.databus")
|
local databus = require("rtu.databus")
|
||||||
local modbus = require("rtu.modbus")
|
local modbus = require("rtu.modbus")
|
||||||
|
|
||||||
@ -17,6 +16,51 @@ local ESTABLISH_ACK = comms.ESTABLISH_ACK
|
|||||||
local MGMT_TYPE = comms.MGMT_TYPE
|
local MGMT_TYPE = comms.MGMT_TYPE
|
||||||
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
|
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
|
||||||
|
|
||||||
|
---@type rtu_config
|
||||||
|
local config = {}
|
||||||
|
|
||||||
|
rtu.config = config
|
||||||
|
|
||||||
|
-- load the RTU configuration
|
||||||
|
function rtu.load_config()
|
||||||
|
if not settings.load("/rtu.settings") then return false end
|
||||||
|
|
||||||
|
config.Peripherals = settings.get("Peripherals")
|
||||||
|
config.Redstone = settings.get("Redstone")
|
||||||
|
|
||||||
|
config.SpeakerVolume = settings.get("SpeakerVolume")
|
||||||
|
config.SVR_Channel = settings.get("SVR_Channel")
|
||||||
|
config.RTU_Channel = settings.get("RTU_Channel")
|
||||||
|
config.ConnTimeout = settings.get("ConnTimeout")
|
||||||
|
config.TrustedRange = settings.get("TrustedRange")
|
||||||
|
config.AuthKey = settings.get("AuthKey")
|
||||||
|
config.LogMode = settings.get("LogMode")
|
||||||
|
config.LogPath = settings.get("LogPath")
|
||||||
|
config.LogDebug = settings.get("LogDebug")
|
||||||
|
|
||||||
|
local cfv = util.new_validator()
|
||||||
|
|
||||||
|
cfv.assert_type_num(config.SpeakerVolume)
|
||||||
|
cfv.assert_channel(config.SVR_Channel)
|
||||||
|
cfv.assert_channel(config.RTU_Channel)
|
||||||
|
cfv.assert_type_int(config.ConnTimeout)
|
||||||
|
cfv.assert_min(config.ConnTimeout, 2)
|
||||||
|
cfv.assert_type_num(config.TrustedRange)
|
||||||
|
cfv.assert_min(config.TrustedRange, 0)
|
||||||
|
cfv.assert_type_str(config.AuthKey)
|
||||||
|
|
||||||
|
if type(config.AuthKey) == "string" then
|
||||||
|
local len = string.len(config.AuthKey)
|
||||||
|
cfv.assert_eq(len == 0 or len >= 8, true)
|
||||||
|
end
|
||||||
|
|
||||||
|
cfv.assert_type_int(config.LogMode)
|
||||||
|
cfv.assert_type_str(config.LogPath)
|
||||||
|
cfv.assert_type_bool(config.LogDebug)
|
||||||
|
|
||||||
|
return cfv.valid()
|
||||||
|
end
|
||||||
|
|
||||||
-- create a new RTU unit
|
-- create a new RTU unit
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
function rtu.init_unit()
|
function rtu.init_unit()
|
||||||
@ -175,7 +219,7 @@ function rtu.init_sounder(speaker)
|
|||||||
function spkr_ctl.continue()
|
function spkr_ctl.continue()
|
||||||
if spkr_ctl.playing then
|
if spkr_ctl.playing then
|
||||||
if spkr_ctl.speaker ~= nil and spkr_ctl.stream.has_next_block() then
|
if spkr_ctl.speaker ~= nil and spkr_ctl.stream.has_next_block() then
|
||||||
local success = spkr_ctl.speaker.playAudio(spkr_ctl.stream.get_next_block(), config.SOUNDER_VOLUME)
|
local success = spkr_ctl.speaker.playAudio(spkr_ctl.stream.get_next_block(), config.SpeakerVolume)
|
||||||
if not success then log.error(util.c("rtu_sounder(", spkr_ctl.name, "): error playing audio")) end
|
if not success then log.error(util.c("rtu_sounder(", spkr_ctl.name, "): error playing audio")) end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -203,11 +247,8 @@ end
|
|||||||
---@nodiscard
|
---@nodiscard
|
||||||
---@param version string RTU version
|
---@param version string RTU version
|
||||||
---@param nic nic network interface device
|
---@param nic nic network interface device
|
||||||
---@param rtu_channel integer PLC comms channel
|
|
||||||
---@param svr_channel integer supervisor server channel
|
|
||||||
---@param range integer trusted device connection range
|
|
||||||
---@param conn_watchdog watchdog watchdog reference
|
---@param conn_watchdog watchdog watchdog reference
|
||||||
function rtu.comms(version, nic, rtu_channel, svr_channel, range, conn_watchdog)
|
function rtu.comms(version, nic, conn_watchdog)
|
||||||
local self = {
|
local self = {
|
||||||
sv_addr = comms.BROADCAST,
|
sv_addr = comms.BROADCAST,
|
||||||
seq_num = 0,
|
seq_num = 0,
|
||||||
@ -218,13 +259,13 @@ function rtu.comms(version, nic, rtu_channel, svr_channel, range, conn_watchdog)
|
|||||||
|
|
||||||
local insert = table.insert
|
local insert = table.insert
|
||||||
|
|
||||||
comms.set_trusted_range(range)
|
comms.set_trusted_range(config.TrustedRange)
|
||||||
|
|
||||||
-- PRIVATE FUNCTIONS --
|
-- PRIVATE FUNCTIONS --
|
||||||
|
|
||||||
-- configure modem channels
|
-- configure modem channels
|
||||||
nic.closeAll()
|
nic.closeAll()
|
||||||
nic.open(rtu_channel)
|
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
|
||||||
@ -236,7 +277,7 @@ function rtu.comms(version, nic, rtu_channel, svr_channel, range, conn_watchdog)
|
|||||||
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())
|
||||||
|
|
||||||
nic.transmit(svr_channel, 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
|
||||||
|
|
||||||
@ -280,7 +321,7 @@ function rtu.comms(version, nic, rtu_channel, svr_channel, range, conn_watchdog)
|
|||||||
function public.send_modbus(m_pkt)
|
function public.send_modbus(m_pkt)
|
||||||
local s_pkt = comms.scada_packet()
|
local s_pkt = comms.scada_packet()
|
||||||
s_pkt.make(self.sv_addr, self.seq_num, PROTOCOL.MODBUS_TCP, m_pkt.raw_sendable())
|
s_pkt.make(self.sv_addr, self.seq_num, PROTOCOL.MODBUS_TCP, m_pkt.raw_sendable())
|
||||||
nic.transmit(svr_channel, 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
|
||||||
|
|
||||||
@ -365,7 +406,7 @@ function rtu.comms(version, nic, rtu_channel, svr_channel, range, conn_watchdog)
|
|||||||
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()
|
||||||
|
|
||||||
if l_chan == rtu_channel then
|
if l_chan == config.RTU_Channel then
|
||||||
-- check sequence number
|
-- check sequence number
|
||||||
if self.r_seq_num == nil then
|
if self.r_seq_num == nil then
|
||||||
self.r_seq_num = packet.scada_frame.seq_num()
|
self.r_seq_num = packet.scada_frame.seq_num()
|
||||||
|
231
rtu/startup.lua
231
rtu/startup.lua
@ -15,7 +15,7 @@ local rsio = require("scada-common.rsio")
|
|||||||
local types = require("scada-common.types")
|
local types = require("scada-common.types")
|
||||||
local util = require("scada-common.util")
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
local config = require("rtu.config")
|
local configure = require("rtu.configure")
|
||||||
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")
|
||||||
@ -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.6.6"
|
local RTU_VERSION = "v1.7.0"
|
||||||
|
|
||||||
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
|
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
|
||||||
local RTU_UNIT_HW_STATE = databus.RTU_UNIT_HW_STATE
|
local RTU_UNIT_HW_STATE = databus.RTU_UNIT_HW_STATE
|
||||||
@ -40,27 +40,26 @@ local println = util.println
|
|||||||
local println_ts = util.println_ts
|
local println_ts = util.println_ts
|
||||||
|
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
-- config validation
|
-- get configuration
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
|
|
||||||
local cfv = util.new_validator()
|
if not rtu.load_config() then
|
||||||
|
-- try to reconfigure (user action)
|
||||||
|
local success, error = configure.configure(true)
|
||||||
|
if success then
|
||||||
|
assert(rtu.load_config(), "failed to load valid RTU configuration")
|
||||||
|
else
|
||||||
|
assert(success, "RTU configuration error: " .. error)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
cfv.assert_channel(config.SVR_CHANNEL)
|
local config = rtu.config
|
||||||
cfv.assert_channel(config.RTU_CHANNEL)
|
|
||||||
cfv.assert_type_int(config.TRUSTED_RANGE)
|
|
||||||
cfv.assert_type_num(config.COMMS_TIMEOUT)
|
|
||||||
cfv.assert_min(config.COMMS_TIMEOUT, 2)
|
|
||||||
cfv.assert_type_str(config.LOG_PATH)
|
|
||||||
cfv.assert_type_int(config.LOG_MODE)
|
|
||||||
cfv.assert_type_table(config.RTU_DEVICES)
|
|
||||||
cfv.assert_type_table(config.RTU_REDSTONE)
|
|
||||||
assert(cfv.valid(), "bad config file: missing/invalid fields")
|
|
||||||
|
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
-- log init
|
-- log init
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
|
|
||||||
log.init(config.LOG_PATH, config.LOG_MODE, config.LOG_DEBUG == true)
|
log.init(config.LogPath, config.LogMode, config.LogDebug)
|
||||||
|
|
||||||
log.info("========================================")
|
log.info("========================================")
|
||||||
log.info("BOOTING rtu.startup " .. RTU_VERSION)
|
log.info("BOOTING rtu.startup " .. RTU_VERSION)
|
||||||
@ -85,8 +84,8 @@ local function main()
|
|||||||
ppm.mount_all()
|
ppm.mount_all()
|
||||||
|
|
||||||
-- message authentication init
|
-- message authentication init
|
||||||
if type(config.AUTH_KEY) == "string" then
|
if type(config.AuthKey) == "string" then
|
||||||
network.init_mac(config.AUTH_KEY)
|
network.init_mac(config.AuthKey)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- get modem
|
-- get modem
|
||||||
@ -139,126 +138,109 @@ local function main()
|
|||||||
|
|
||||||
local units = __shared_memory.rtu_sys.units
|
local units = __shared_memory.rtu_sys.units
|
||||||
|
|
||||||
local rtu_redstone = config.RTU_REDSTONE
|
local rtu_redstone = config.Redstone
|
||||||
local rtu_devices = config.RTU_DEVICES
|
local rtu_devices = config.Peripherals
|
||||||
|
|
||||||
-- configure RTU gateway based on config file definitions
|
-- configure RTU gateway based on settings file definitions
|
||||||
local function configure()
|
local function sys_config()
|
||||||
-- redstone interfaces
|
-- redstone interfaces
|
||||||
|
local rs_rtus = {}
|
||||||
|
|
||||||
|
-- go through redstone definitions list
|
||||||
for entry_idx = 1, #rtu_redstone do
|
for entry_idx = 1, #rtu_redstone do
|
||||||
local rs_rtu = redstone_rtu.new()
|
local entry = rtu_redstone[entry_idx] ---@type rtu_rs_definition
|
||||||
local io_table = rtu_redstone[entry_idx].io ---@type table
|
local assignment = ""
|
||||||
local io_reactor = rtu_redstone[entry_idx].for_reactor ---@type integer
|
local for_reactor = entry.unit
|
||||||
|
local iface_name = util.trinary(entry.color ~= nil, util.c(entry.side, "/", rsio.color_name(entry.color)), entry.side)
|
||||||
|
|
||||||
-- CHECK: reactor ID must be >= to 1
|
if util.is_int(entry.unit) and entry.unit > 0 and entry.unit < 5 then
|
||||||
if (not util.is_int(io_reactor)) or (io_reactor < 0) then
|
---@cast for_reactor integer
|
||||||
local message = util.c("configure> redstone entry #", entry_idx, " : ", io_reactor, " isn't an integer >= 0")
|
assignment = "reactor unit " .. entry.unit
|
||||||
|
if rs_rtus[for_reactor] == nil then
|
||||||
|
log.debug(util.c("configure> allocated redstone RTU for reactor unit ", entry.unit))
|
||||||
|
rs_rtus[for_reactor] = { rtu = redstone_rtu.new(), capabilities = {} }
|
||||||
|
end
|
||||||
|
elseif entry.unit == nil then
|
||||||
|
assignment = "facility"
|
||||||
|
for_reactor = 0
|
||||||
|
if rs_rtus[for_reactor] == nil then
|
||||||
|
log.debug(util.c("configure> allocated redstone RTU for the facility"))
|
||||||
|
rs_rtus[for_reactor] = { rtu = redstone_rtu.new(), capabilities = {} }
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local message = util.c("configure> invalid unit assignment at block index #", entry_idx)
|
||||||
println(message)
|
println(message)
|
||||||
log.fatal(message)
|
log.fatal(message)
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
-- CHECK: io table exists
|
|
||||||
if type(io_table) ~= "table" then
|
|
||||||
local message = util.c("configure> redstone entry #", entry_idx, " no IO table found")
|
|
||||||
println(message)
|
|
||||||
log.fatal(message)
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
local capabilities = {}
|
|
||||||
|
|
||||||
log.debug(util.c("configure> starting redstone RTU I/O linking for reactor ", io_reactor, "..."))
|
|
||||||
|
|
||||||
local continue = true
|
|
||||||
|
|
||||||
-- CHECK: no duplicate entries
|
|
||||||
for i = 1, #units do
|
|
||||||
local unit = units[i] ---@type rtu_unit_registry_entry
|
|
||||||
if unit.reactor == io_reactor and unit.type == RTU_UNIT_TYPE.REDSTONE then
|
|
||||||
-- duplicate entry
|
|
||||||
local message = util.c("configure> skipping definition block #", entry_idx, " for reactor ", io_reactor,
|
|
||||||
" with already defined redstone I/O")
|
|
||||||
println(message)
|
|
||||||
log.warning(message)
|
|
||||||
continue = false
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- not a duplicate
|
|
||||||
if continue then
|
|
||||||
for i = 1, #io_table do
|
|
||||||
local valid = false
|
|
||||||
local conf = io_table[i]
|
|
||||||
|
|
||||||
-- verify configuration
|
-- verify configuration
|
||||||
if rsio.is_valid_port(conf.port) and rsio.is_valid_side(conf.side) then
|
local valid = false
|
||||||
if conf.bundled_color then
|
if rsio.is_valid_port(entry.port) and rsio.is_valid_side(entry.side) then
|
||||||
valid = rsio.is_color(conf.bundled_color)
|
valid = util.trinary(entry.color == nil, true, rsio.is_color(entry.color))
|
||||||
else
|
|
||||||
valid = true
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local rs_rtu = rs_rtus[for_reactor].rtu
|
||||||
|
local capabilities = rs_rtus[for_reactor].capabilities
|
||||||
|
|
||||||
if not valid then
|
if not valid then
|
||||||
local message = util.c("configure> invalid redstone definition at index ", i, " in definition block #", entry_idx,
|
local message = util.c("configure> invalid redstone definition at block index #", entry_idx)
|
||||||
" (for reactor ", io_reactor, ")")
|
|
||||||
println(message)
|
println(message)
|
||||||
log.fatal(message)
|
log.fatal(message)
|
||||||
return false
|
return false
|
||||||
else
|
else
|
||||||
-- link redstone in RTU
|
-- link redstone in RTU
|
||||||
local mode = rsio.get_io_mode(conf.port)
|
local mode = rsio.get_io_mode(entry.port)
|
||||||
if mode == rsio.IO_MODE.DIGITAL_IN then
|
if mode == rsio.IO_MODE.DIGITAL_IN then
|
||||||
-- can't have duplicate inputs
|
-- can't have duplicate inputs
|
||||||
if util.table_contains(capabilities, conf.port) then
|
if util.table_contains(capabilities, entry.port) then
|
||||||
local message = util.c("configure> skipping duplicate input for port ", rsio.to_string(conf.port), " on side ", conf.side)
|
local message = util.c("configure> skipping duplicate input for port ", rsio.to_string(entry.port), " on side ", iface_name)
|
||||||
println(message)
|
println(message)
|
||||||
log.warning(message)
|
log.warning(message)
|
||||||
else
|
else
|
||||||
rs_rtu.link_di(conf.side, conf.bundled_color)
|
rs_rtu.link_di(entry.side, entry.color)
|
||||||
end
|
end
|
||||||
elseif mode == rsio.IO_MODE.DIGITAL_OUT then
|
elseif mode == rsio.IO_MODE.DIGITAL_OUT then
|
||||||
rs_rtu.link_do(conf.side, conf.bundled_color)
|
rs_rtu.link_do(entry.side, entry.color)
|
||||||
elseif mode == rsio.IO_MODE.ANALOG_IN then
|
elseif mode == rsio.IO_MODE.ANALOG_IN then
|
||||||
-- can't have duplicate inputs
|
-- can't have duplicate inputs
|
||||||
if util.table_contains(capabilities, conf.port) then
|
if util.table_contains(capabilities, entry.port) then
|
||||||
local message = util.c("configure> skipping duplicate input for port ", rsio.to_string(conf.port), " on side ", conf.side)
|
local message = util.c("configure> skipping duplicate input for port ", rsio.to_string(entry.port), " on side ", iface_name)
|
||||||
println(message)
|
println(message)
|
||||||
log.warning(message)
|
log.warning(message)
|
||||||
else
|
else
|
||||||
rs_rtu.link_ai(conf.side)
|
rs_rtu.link_ai(entry.side)
|
||||||
end
|
end
|
||||||
elseif mode == rsio.IO_MODE.ANALOG_OUT then
|
elseif mode == rsio.IO_MODE.ANALOG_OUT then
|
||||||
rs_rtu.link_ao(conf.side)
|
rs_rtu.link_ao(entry.side)
|
||||||
else
|
else
|
||||||
-- should be unreachable code, we already validated ports
|
-- should be unreachable code, we already validated ports
|
||||||
log.error("configure> fell through if chain attempting to identify IO mode", true)
|
log.error("configure> fell through if chain attempting to identify IO mode at block index #" .. entry_idx, true)
|
||||||
println("configure> encountered a software error, check logs")
|
println("configure> encountered a software error, check logs")
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
table.insert(capabilities, conf.port)
|
table.insert(capabilities, entry.port)
|
||||||
|
|
||||||
log.debug(util.c("configure> linked redstone ", #capabilities, ": ", rsio.to_string(conf.port),
|
log.debug(util.c("configure> linked redstone ", #capabilities, ": ", rsio.to_string(entry.port), " (", iface_name, ") for ", assignment))
|
||||||
" (", conf.side, ") for reactor ", io_reactor))
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- create unit entries for redstone RTUs
|
||||||
|
for for_reactor, def in pairs(rs_rtus) do
|
||||||
---@class rtu_unit_registry_entry
|
---@class rtu_unit_registry_entry
|
||||||
local unit = {
|
local unit = {
|
||||||
uid = 0, ---@type integer
|
uid = 0, ---@type integer
|
||||||
name = "redstone_io", ---@type string
|
name = "redstone_io", ---@type string
|
||||||
type = RTU_UNIT_TYPE.REDSTONE, ---@type RTU_UNIT_TYPE
|
type = RTU_UNIT_TYPE.REDSTONE, ---@type RTU_UNIT_TYPE
|
||||||
index = entry_idx, ---@type integer
|
index = false, ---@type integer|false
|
||||||
reactor = io_reactor, ---@type integer
|
reactor = for_reactor, ---@type integer
|
||||||
device = capabilities, ---@type table use device field for redstone ports
|
device = def.capabilities, ---@type table use device field for redstone ports
|
||||||
is_multiblock = false, ---@type boolean
|
is_multiblock = false, ---@type boolean
|
||||||
formed = nil, ---@type boolean|nil
|
formed = nil, ---@type boolean|nil
|
||||||
hw_state = RTU_UNIT_HW_STATE.OK, ---@type RTU_UNIT_HW_STATE
|
hw_state = RTU_UNIT_HW_STATE.OK, ---@type RTU_UNIT_HW_STATE
|
||||||
rtu = rs_rtu, ---@type rtu_device|rtu_rs_device
|
rtu = def.rtu, ---@type rtu_device|rtu_rs_device
|
||||||
modbus_io = modbus.new(rs_rtu, false),
|
modbus_io = modbus.new(def.rtu, false),
|
||||||
pkt_queue = nil, ---@type mqueue|nil
|
pkt_queue = nil, ---@type mqueue|nil
|
||||||
thread = nil ---@type parallel_thread|nil
|
thread = nil ---@type parallel_thread|nil
|
||||||
}
|
}
|
||||||
@ -266,8 +248,8 @@ local function main()
|
|||||||
table.insert(units, unit)
|
table.insert(units, unit)
|
||||||
|
|
||||||
local for_message = "facility"
|
local for_message = "facility"
|
||||||
if io_reactor > 0 then
|
if util.is_int(for_reactor) then
|
||||||
for_message = util.c("reactor ", io_reactor)
|
for_message = util.c("reactor unit ", for_reactor)
|
||||||
end
|
end
|
||||||
|
|
||||||
log.info(util.c("configure> initialized RTU unit #", #units, ": redstone_io (redstone) [1] for ", for_message))
|
log.info(util.c("configure> initialized RTU unit #", #units, ": redstone_io (redstone) [1] for ", for_message))
|
||||||
@ -276,13 +258,13 @@ local function main()
|
|||||||
|
|
||||||
databus.tx_unit_hw_status(unit.uid, unit.hw_state)
|
databus.tx_unit_hw_status(unit.uid, unit.hw_state)
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
-- mounted peripherals
|
-- mounted peripherals
|
||||||
for i = 1, #rtu_devices do
|
for i = 1, #rtu_devices do
|
||||||
local name = rtu_devices[i].name
|
local entry = rtu_devices[i] ---@type rtu_peri_definition
|
||||||
local index = rtu_devices[i].index
|
local name = entry.name
|
||||||
local for_reactor = rtu_devices[i].for_reactor
|
local index = entry.index
|
||||||
|
local for_reactor = util.trinary(entry.unit == nil, 0, entry.unit)
|
||||||
|
|
||||||
-- CHECK: name is a string
|
-- CHECK: name is a string
|
||||||
if type(name) ~= "string" then
|
if type(name) ~= "string" then
|
||||||
@ -292,20 +274,37 @@ local function main()
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
-- CHECK: index is an integer >= 1
|
-- CHECK: index type
|
||||||
if (not util.is_int(index)) or (index <= 0) then
|
if (index ~= nil) and (not util.is_int(index)) then
|
||||||
local message = util.c("configure> device entry #", i, ": index ", index, " isn't an integer >= 1")
|
local message = util.c("configure> device entry #", i, ": index ", index, " isn't valid")
|
||||||
println(message)
|
println(message)
|
||||||
log.fatal(message)
|
log.fatal(message)
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
-- CHECK: reactor is an integer >= 0
|
-- CHECK: index range
|
||||||
if (not util.is_int(for_reactor)) or (for_reactor < 0) then
|
local function validate_index(min, max)
|
||||||
local message = util.c("configure> device entry #", i, ": reactor ", for_reactor, " isn't an integer >= 0")
|
if (util.is_int(index) and index < min) and (max == nil or index > max) then
|
||||||
|
local message = util.c("configure> device entry #", i, ": index ", index, " isn't >= ", min, " and <= ", max)
|
||||||
println(message)
|
println(message)
|
||||||
log.fatal(message)
|
log.fatal(message)
|
||||||
return false
|
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("configure> 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("configure> device entry #", i, ": unit assignment ", for_reactor, " isn't vaild")
|
||||||
|
println(message)
|
||||||
|
log.fatal(message)
|
||||||
|
return false
|
||||||
|
else return true end
|
||||||
end
|
end
|
||||||
|
|
||||||
local device = ppm.get_periph(name)
|
local device = ppm.get_periph(name)
|
||||||
@ -330,6 +329,9 @@ local function main()
|
|||||||
|
|
||||||
if type == "boilerValve" then
|
if type == "boilerValve" then
|
||||||
-- boiler multiblock
|
-- 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_type = RTU_UNIT_TYPE.BOILER_VALVE
|
||||||
rtu_iface, faulted = boilerv_rtu.new(device)
|
rtu_iface, faulted = boilerv_rtu.new(device)
|
||||||
is_multiblock = true
|
is_multiblock = true
|
||||||
@ -342,6 +344,9 @@ local function main()
|
|||||||
end
|
end
|
||||||
elseif type == "turbineValve" then
|
elseif type == "turbineValve" then
|
||||||
-- turbine multiblock
|
-- 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_type = RTU_UNIT_TYPE.TURBINE_VALVE
|
||||||
rtu_iface, faulted = turbinev_rtu.new(device)
|
rtu_iface, faulted = turbinev_rtu.new(device)
|
||||||
is_multiblock = true
|
is_multiblock = true
|
||||||
@ -354,6 +359,14 @@ local function main()
|
|||||||
end
|
end
|
||||||
elseif type == "dynamicValve" then
|
elseif type == "dynamicValve" then
|
||||||
-- dynamic tank multiblock
|
-- 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_type = RTU_UNIT_TYPE.DYNAMIC_VALVE
|
||||||
rtu_iface, faulted = dynamicv_rtu.new(device)
|
rtu_iface, faulted = dynamicv_rtu.new(device)
|
||||||
is_multiblock = true
|
is_multiblock = true
|
||||||
@ -366,6 +379,8 @@ local function main()
|
|||||||
end
|
end
|
||||||
elseif type == "inductionPort" then
|
elseif type == "inductionPort" then
|
||||||
-- induction matrix multiblock
|
-- induction matrix multiblock
|
||||||
|
if not validate_assign(true) then return false end
|
||||||
|
|
||||||
rtu_type = RTU_UNIT_TYPE.IMATRIX
|
rtu_type = RTU_UNIT_TYPE.IMATRIX
|
||||||
rtu_iface, faulted = imatrix_rtu.new(device)
|
rtu_iface, faulted = imatrix_rtu.new(device)
|
||||||
is_multiblock = true
|
is_multiblock = true
|
||||||
@ -378,6 +393,8 @@ local function main()
|
|||||||
end
|
end
|
||||||
elseif type == "spsPort" then
|
elseif type == "spsPort" then
|
||||||
-- SPS multiblock
|
-- SPS multiblock
|
||||||
|
if not validate_assign(true) then return false end
|
||||||
|
|
||||||
rtu_type = RTU_UNIT_TYPE.SPS
|
rtu_type = RTU_UNIT_TYPE.SPS
|
||||||
rtu_iface, faulted = sps_rtu.new(device)
|
rtu_iface, faulted = sps_rtu.new(device)
|
||||||
is_multiblock = true
|
is_multiblock = true
|
||||||
@ -390,10 +407,14 @@ local function main()
|
|||||||
end
|
end
|
||||||
elseif type == "solarNeutronActivator" then
|
elseif type == "solarNeutronActivator" then
|
||||||
-- SNA
|
-- SNA
|
||||||
|
if not validate_assign() then return false end
|
||||||
|
|
||||||
rtu_type = RTU_UNIT_TYPE.SNA
|
rtu_type = RTU_UNIT_TYPE.SNA
|
||||||
rtu_iface, faulted = sna_rtu.new(device)
|
rtu_iface, faulted = sna_rtu.new(device)
|
||||||
elseif type == "environmentDetector" then
|
elseif type == "environmentDetector" then
|
||||||
-- advanced peripherals environment detector
|
-- advanced peripherals environment detector
|
||||||
|
if not validate_assign(entry.unit == nil) then return false end
|
||||||
|
|
||||||
rtu_type = RTU_UNIT_TYPE.ENV_DETECTOR
|
rtu_type = RTU_UNIT_TYPE.ENV_DETECTOR
|
||||||
rtu_iface, faulted = envd_rtu.new(device)
|
rtu_iface, faulted = envd_rtu.new(device)
|
||||||
elseif type == ppm.VIRTUAL_DEVICE_TYPE then
|
elseif type == ppm.VIRTUAL_DEVICE_TYPE then
|
||||||
@ -423,7 +444,7 @@ local function main()
|
|||||||
uid = 0, ---@type integer
|
uid = 0, ---@type integer
|
||||||
name = name, ---@type string
|
name = name, ---@type string
|
||||||
type = rtu_type, ---@type RTU_UNIT_TYPE
|
type = rtu_type, ---@type RTU_UNIT_TYPE
|
||||||
index = index, ---@type integer
|
index = index or false, ---@type integer|false
|
||||||
reactor = for_reactor, ---@type integer
|
reactor = for_reactor, ---@type integer
|
||||||
device = device, ---@type table
|
device = device, ---@type table
|
||||||
is_multiblock = is_multiblock, ---@type boolean
|
is_multiblock = is_multiblock, ---@type boolean
|
||||||
@ -465,7 +486,6 @@ local function main()
|
|||||||
databus.tx_unit_hw_status(rtu_unit.uid, rtu_unit.hw_state)
|
databus.tx_unit_hw_status(rtu_unit.uid, rtu_unit.hw_state)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- we made it through all that trusting-user-to-write-a-config-file chaos
|
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -475,9 +495,9 @@ local function main()
|
|||||||
|
|
||||||
local rtu_state = __shared_memory.rtu_state
|
local rtu_state = __shared_memory.rtu_state
|
||||||
|
|
||||||
log.debug("boot> running configure()")
|
log.debug("boot> running sys_config()")
|
||||||
|
|
||||||
if configure() then
|
if sys_config() then
|
||||||
-- start UI
|
-- start UI
|
||||||
local message
|
local message
|
||||||
rtu_state.fp_ok, message = renderer.try_start_ui(units)
|
rtu_state.fp_ok, message = renderer.try_start_ui(units)
|
||||||
@ -502,12 +522,11 @@ local function main()
|
|||||||
databus.tx_hw_spkr_count(#__shared_memory.rtu_dev.sounders)
|
databus.tx_hw_spkr_count(#__shared_memory.rtu_dev.sounders)
|
||||||
|
|
||||||
-- start connection watchdog
|
-- start connection watchdog
|
||||||
smem_sys.conn_watchdog = util.new_watchdog(config.COMMS_TIMEOUT)
|
smem_sys.conn_watchdog = util.new_watchdog(config.ConnTimeout)
|
||||||
log.debug("startup> conn watchdog started")
|
log.debug("startup> conn watchdog started")
|
||||||
|
|
||||||
-- setup comms
|
-- setup comms
|
||||||
smem_sys.rtu_comms = rtu.comms(RTU_VERSION, smem_sys.nic, config.RTU_CHANNEL, config.SVR_CHANNEL,
|
smem_sys.rtu_comms = rtu.comms(RTU_VERSION, smem_sys.nic, smem_sys.conn_watchdog)
|
||||||
config.TRUSTED_RANGE, smem_sys.conn_watchdog)
|
|
||||||
log.debug("startup> comms init")
|
log.debug("startup> comms init")
|
||||||
|
|
||||||
-- init threads
|
-- init threads
|
||||||
|
265
rtu/threads.lua
265
rtu/threads.lua
@ -28,6 +28,174 @@ local UNIT_HW_STATE = databus.RTU_UNIT_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 println_ts function
|
||||||
|
---@param iface string
|
||||||
|
---@param type string
|
||||||
|
---@param device table
|
||||||
|
---@param unit rtu_unit_registry_entry
|
||||||
|
local function handle_unit_mount(smem, println_ts, iface, type, device, unit)
|
||||||
|
local sys = smem.rtu_sys
|
||||||
|
|
||||||
|
-- find disconnected device to reconnect
|
||||||
|
-- note: cannot check isFormed as that would yield this coroutine and consume events
|
||||||
|
if unit.name == iface then
|
||||||
|
local resend_advert = false
|
||||||
|
local faulted = false
|
||||||
|
local unknown = false
|
||||||
|
local invalid = false
|
||||||
|
|
||||||
|
-- found, re-link
|
||||||
|
unit.device = device
|
||||||
|
|
||||||
|
if unit.type == RTU_UNIT_TYPE.VIRTUAL then
|
||||||
|
resend_advert = true
|
||||||
|
if type == "boilerValve" then
|
||||||
|
-- boiler multiblock
|
||||||
|
if unit.reactor < 1 or unit.reactor > 4 then
|
||||||
|
invalid = true
|
||||||
|
log.error(util.c("boiler '", unit.name, "' cannot init, not assigned to a valid unit in config"))
|
||||||
|
end
|
||||||
|
|
||||||
|
if (unit.index == false) or unit.index < 1 or unit.index > 2 then
|
||||||
|
invalid = true
|
||||||
|
log.error(util.c("boiler '", unit.name, "' cannot init, invalid index provided in config"))
|
||||||
|
end
|
||||||
|
|
||||||
|
unit.type = RTU_UNIT_TYPE.BOILER_VALVE
|
||||||
|
elseif type == "turbineValve" then
|
||||||
|
-- turbine multiblock
|
||||||
|
if unit.reactor < 1 or unit.reactor > 4 then
|
||||||
|
invalid = true
|
||||||
|
log.error(util.c("turbine '", unit.name, "' cannot init, not assigned to a valid unit in config"))
|
||||||
|
end
|
||||||
|
|
||||||
|
if (unit.index == false) or unit.index < 1 or unit.index > 3 then
|
||||||
|
invalid = true
|
||||||
|
log.error(util.c("turbine '", unit.name, "' cannot init, invalid index provided in config"))
|
||||||
|
end
|
||||||
|
|
||||||
|
unit.type = RTU_UNIT_TYPE.TURBINE_VALVE
|
||||||
|
elseif type == "dynamicValve" then
|
||||||
|
-- dynamic tank multiblock
|
||||||
|
if unit.reactor < 0 or unit.reactor > 4 then
|
||||||
|
invalid = true
|
||||||
|
log.error(util.c("dynamic tank '", unit.name, "' cannot init, no valid assignment provided in config"))
|
||||||
|
end
|
||||||
|
|
||||||
|
if (unit.reactor == 0 and ((unit.index == false) or unit.index < 1 or unit.index > 4)) or
|
||||||
|
(unit.reactor > 0 and unit.index ~= 1) then
|
||||||
|
invalid = true
|
||||||
|
log.error(util.c("dynamic tank '", unit.name, "' cannot init, invalid index provided in config"))
|
||||||
|
end
|
||||||
|
|
||||||
|
unit.type = RTU_UNIT_TYPE.DYNAMIC_VALVE
|
||||||
|
elseif type == "inductionPort" then
|
||||||
|
-- induction matrix multiblock
|
||||||
|
if unit.reactor ~= 0 then
|
||||||
|
invalid = true
|
||||||
|
log.error(util.c("induction matrix '", unit.name, "' cannot init, not assigned to facility in config"))
|
||||||
|
end
|
||||||
|
|
||||||
|
unit.type = RTU_UNIT_TYPE.IMATRIX
|
||||||
|
elseif type == "spsPort" then
|
||||||
|
-- SPS multiblock
|
||||||
|
if unit.reactor ~= 0 then
|
||||||
|
invalid = true
|
||||||
|
log.error(util.c("SPS '", unit.name, "' cannot init, not assigned to facility in config"))
|
||||||
|
end
|
||||||
|
|
||||||
|
unit.type = RTU_UNIT_TYPE.SPS
|
||||||
|
elseif type == "solarNeutronActivator" then
|
||||||
|
-- SNA
|
||||||
|
if unit.reactor < 1 or unit.reactor > 4 then
|
||||||
|
invalid = true
|
||||||
|
log.error(util.c("SNA '", unit.name, "' cannot init, not assigned to a valid unit in config"))
|
||||||
|
end
|
||||||
|
|
||||||
|
unit.type = RTU_UNIT_TYPE.SNA
|
||||||
|
elseif type == "environmentDetector" then
|
||||||
|
-- advanced peripherals environment detector
|
||||||
|
if unit.reactor < 0 or unit.reactor > 4 then
|
||||||
|
invalid = true
|
||||||
|
log.error(util.c("environment detector '", unit.name, "' cannot init, no valid assignment provided in config"))
|
||||||
|
end
|
||||||
|
|
||||||
|
unit.type = RTU_UNIT_TYPE.ENV_DETECTOR
|
||||||
|
else
|
||||||
|
resend_advert = false
|
||||||
|
log.error(util.c("virtual device '", unit.name, "' cannot init to an unknown type (", type, ")"))
|
||||||
|
end
|
||||||
|
|
||||||
|
databus.tx_unit_hw_type(unit.uid, unit.type)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- if disconnected on startup, config wouldn't have been validated
|
||||||
|
-- checking now that it has connected; the config isn't valid, so don't connect it
|
||||||
|
if invalid then
|
||||||
|
unit.hw_state = UNIT_HW_STATE.OFFLINE
|
||||||
|
databus.tx_unit_hw_status(unit.uid, unit.hw_state)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- note for multiblock structures: if not formed, indexing the multiblock functions results in a PPM fault
|
||||||
|
|
||||||
|
if unit.type == RTU_UNIT_TYPE.BOILER_VALVE then
|
||||||
|
unit.rtu, faulted = boilerv_rtu.new(device)
|
||||||
|
unit.formed = util.trinary(faulted, false, nil)
|
||||||
|
elseif unit.type == RTU_UNIT_TYPE.TURBINE_VALVE then
|
||||||
|
unit.rtu, faulted = turbinev_rtu.new(device)
|
||||||
|
unit.formed = util.trinary(faulted, false, nil)
|
||||||
|
elseif unit.type == RTU_UNIT_TYPE.DYNAMIC_VALVE then
|
||||||
|
unit.rtu, faulted = dynamicv_rtu.new(device)
|
||||||
|
unit.formed = util.trinary(faulted, false, nil)
|
||||||
|
elseif unit.type == RTU_UNIT_TYPE.IMATRIX then
|
||||||
|
unit.rtu, faulted = imatrix_rtu.new(device)
|
||||||
|
unit.formed = util.trinary(faulted, false, nil)
|
||||||
|
elseif unit.type == RTU_UNIT_TYPE.SPS then
|
||||||
|
unit.rtu, faulted = sps_rtu.new(device)
|
||||||
|
unit.formed = util.trinary(faulted, false, nil)
|
||||||
|
elseif unit.type == RTU_UNIT_TYPE.SNA then
|
||||||
|
unit.rtu, faulted = sna_rtu.new(device)
|
||||||
|
elseif unit.type == RTU_UNIT_TYPE.ENV_DETECTOR then
|
||||||
|
unit.rtu, faulted = envd_rtu.new(device)
|
||||||
|
else
|
||||||
|
unknown = true
|
||||||
|
log.error(util.c("failed to identify reconnected RTU unit type (", unit.name, ")"), true)
|
||||||
|
end
|
||||||
|
|
||||||
|
if unit.is_multiblock then
|
||||||
|
unit.hw_state = UNIT_HW_STATE.UNFORMED
|
||||||
|
if unit.formed == false then
|
||||||
|
log.info(util.c("assuming ", unit.name, " is not formed due to PPM faults while initializing"))
|
||||||
|
end
|
||||||
|
elseif faulted then
|
||||||
|
unit.hw_state = UNIT_HW_STATE.FAULTED
|
||||||
|
elseif not unknown then
|
||||||
|
unit.hw_state = UNIT_HW_STATE.OK
|
||||||
|
else
|
||||||
|
unit.hw_state = UNIT_HW_STATE.OFFLINE
|
||||||
|
end
|
||||||
|
|
||||||
|
databus.tx_unit_hw_status(unit.uid, unit.hw_state)
|
||||||
|
|
||||||
|
if not unknown then
|
||||||
|
unit.modbus_io = modbus.new(unit.rtu, true)
|
||||||
|
|
||||||
|
local type_name = types.rtu_type_to_string(unit.type)
|
||||||
|
local message = util.c("reconnected the ", type_name, " on interface ", unit.name)
|
||||||
|
println_ts(message)
|
||||||
|
log.info(message)
|
||||||
|
|
||||||
|
if resend_advert then
|
||||||
|
sys.rtu_comms.send_advertisement(sys.units)
|
||||||
|
else
|
||||||
|
sys.rtu_comms.send_remounted(unit.uid)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- main thread
|
-- main thread
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
---@param smem rtu_shared_memory
|
---@param smem rtu_shared_memory
|
||||||
@ -180,102 +348,7 @@ function threads.thread__main(smem)
|
|||||||
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
|
||||||
local unit = units[i] ---@type rtu_unit_registry_entry
|
handle_unit_mount(smem, println_ts, param1, type, device, units[i])
|
||||||
|
|
||||||
-- find disconnected device to reconnect
|
|
||||||
-- note: cannot check isFormed as that would yield this coroutine and consume events
|
|
||||||
if unit.name == param1 then
|
|
||||||
local resend_advert = false
|
|
||||||
local faulted = false
|
|
||||||
local unknown = false
|
|
||||||
|
|
||||||
-- found, re-link
|
|
||||||
unit.device = device
|
|
||||||
|
|
||||||
if unit.type == RTU_UNIT_TYPE.VIRTUAL then
|
|
||||||
resend_advert = true
|
|
||||||
if type == "boilerValve" then
|
|
||||||
-- boiler multiblock
|
|
||||||
unit.type = RTU_UNIT_TYPE.BOILER_VALVE
|
|
||||||
elseif type == "turbineValve" then
|
|
||||||
-- turbine multiblock
|
|
||||||
unit.type = RTU_UNIT_TYPE.TURBINE_VALVE
|
|
||||||
elseif type == "inductionPort" then
|
|
||||||
-- induction matrix multiblock
|
|
||||||
unit.type = RTU_UNIT_TYPE.IMATRIX
|
|
||||||
elseif type == "spsPort" then
|
|
||||||
-- SPS multiblock
|
|
||||||
unit.type = RTU_UNIT_TYPE.SPS
|
|
||||||
elseif type == "solarNeutronActivator" then
|
|
||||||
-- SNA
|
|
||||||
unit.type = RTU_UNIT_TYPE.SNA
|
|
||||||
elseif type == "environmentDetector" then
|
|
||||||
-- advanced peripherals environment detector
|
|
||||||
unit.type = RTU_UNIT_TYPE.ENV_DETECTOR
|
|
||||||
else
|
|
||||||
resend_advert = false
|
|
||||||
log.error(util.c("virtual device '", unit.name, "' cannot init to an unknown type (", type, ")"))
|
|
||||||
end
|
|
||||||
|
|
||||||
databus.tx_unit_hw_type(unit.uid, unit.type)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- note for multiblock structures: if not formed, indexing the multiblock functions results in a PPM fault
|
|
||||||
|
|
||||||
if unit.type == RTU_UNIT_TYPE.BOILER_VALVE then
|
|
||||||
unit.rtu, faulted = boilerv_rtu.new(device)
|
|
||||||
unit.formed = util.trinary(faulted, false, nil)
|
|
||||||
elseif unit.type == RTU_UNIT_TYPE.TURBINE_VALVE then
|
|
||||||
unit.rtu, faulted = turbinev_rtu.new(device)
|
|
||||||
unit.formed = util.trinary(faulted, false, nil)
|
|
||||||
elseif unit.type == RTU_UNIT_TYPE.DYNAMIC_VALVE then
|
|
||||||
unit.rtu, faulted = dynamicv_rtu.new(device)
|
|
||||||
unit.formed = util.trinary(faulted, false, nil)
|
|
||||||
elseif unit.type == RTU_UNIT_TYPE.IMATRIX then
|
|
||||||
unit.rtu, faulted = imatrix_rtu.new(device)
|
|
||||||
unit.formed = util.trinary(faulted, false, nil)
|
|
||||||
elseif unit.type == RTU_UNIT_TYPE.SPS then
|
|
||||||
unit.rtu, faulted = sps_rtu.new(device)
|
|
||||||
unit.formed = util.trinary(faulted, false, nil)
|
|
||||||
elseif unit.type == RTU_UNIT_TYPE.SNA then
|
|
||||||
unit.rtu, faulted = sna_rtu.new(device)
|
|
||||||
elseif unit.type == RTU_UNIT_TYPE.ENV_DETECTOR then
|
|
||||||
unit.rtu, faulted = envd_rtu.new(device)
|
|
||||||
else
|
|
||||||
unknown = true
|
|
||||||
log.error(util.c("failed to identify reconnected RTU unit type (", unit.name, ")"), true)
|
|
||||||
end
|
|
||||||
|
|
||||||
if unit.is_multiblock then
|
|
||||||
unit.hw_state = UNIT_HW_STATE.UNFORMED
|
|
||||||
if unit.formed == false then
|
|
||||||
log.info(util.c("assuming ", unit.name, " is not formed due to PPM faults while initializing"))
|
|
||||||
end
|
|
||||||
elseif faulted then
|
|
||||||
unit.hw_state = UNIT_HW_STATE.FAULTED
|
|
||||||
elseif not unknown then
|
|
||||||
unit.hw_state = UNIT_HW_STATE.OK
|
|
||||||
else
|
|
||||||
unit.hw_state = UNIT_HW_STATE.OFFLINE
|
|
||||||
end
|
|
||||||
|
|
||||||
databus.tx_unit_hw_status(unit.uid, unit.hw_state)
|
|
||||||
|
|
||||||
if not unknown then
|
|
||||||
unit.modbus_io = modbus.new(unit.rtu, true)
|
|
||||||
|
|
||||||
local type_name = types.rtu_type_to_string(unit.type)
|
|
||||||
local message = util.c("reconnected the ", type_name, " on interface ", unit.name)
|
|
||||||
println_ts(message)
|
|
||||||
log.info(message)
|
|
||||||
|
|
||||||
if resend_advert then
|
|
||||||
rtu_comms.send_advertisement(units)
|
|
||||||
else
|
|
||||||
rtu_comms.send_remounted(unit.uid)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -285,6 +285,18 @@ function rsio.is_color(color)
|
|||||||
return util.is_int(color) and (color > 0) and (_B_AND(color, (color - 1)) == 0)
|
return util.is_int(color) and (color > 0) and (_B_AND(color, (color - 1)) == 0)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- color to string
|
||||||
|
---@nodiscard
|
||||||
|
---@param color color
|
||||||
|
---@return string
|
||||||
|
function rsio.color_name(color)
|
||||||
|
local color_name_map = { [colors.red] = "red", [colors.orange] = "orange", [colors.yellow] = "yellow", [colors.lime] = "lime", [colors.green] = "green", [colors.cyan] = "cyan", [colors.lightBlue] = "lightBlue", [colors.blue] = "blue", [colors.purple] = "purple", [colors.magenta] = "magenta", [colors.pink] = "pink", [colors.white] = "white", [colors.lightGray] = "lightGray", [colors.gray] = "gray", [colors.black] = "black", [colors.brown] = "brown" }
|
||||||
|
|
||||||
|
if rsio.is_color(color) then
|
||||||
|
return color_name_map[color]
|
||||||
|
else return "unknown" end
|
||||||
|
end
|
||||||
|
|
||||||
--#endregion
|
--#endregion
|
||||||
|
|
||||||
--#region Digital I/O
|
--#region Digital I/O
|
||||||
|
Loading…
Reference in New Issue
Block a user