Merge pull request #619 from MikaylaFischler/rtu-redstone-enhancements
604 redstone relay integration
This commit is contained in:
commit
1a2ecd0599
@ -136,22 +136,45 @@ local function self_check()
|
|||||||
self.self_check_msg("> check gateway configuration...", valid_cfg, "go through Configure Gateway and apply settings to set any missing settings and repair any corrupted ones")
|
self.self_check_msg("> check gateway configuration...", valid_cfg, "go through Configure Gateway and apply settings to set any missing settings and repair any corrupted ones")
|
||||||
|
|
||||||
-- check redstone configurations
|
-- check redstone configurations
|
||||||
local ifaces = {}
|
|
||||||
local bundled_sides = {}
|
local phys = {} ---@type rtu_rs_definition[][]
|
||||||
|
local inputs = { [0] = {}, {}, {}, {}, {} }
|
||||||
|
|
||||||
for i = 1, #cfg.Redstone do
|
for i = 1, #cfg.Redstone do
|
||||||
local entry = cfg.Redstone[i]
|
local entry = cfg.Redstone[i]
|
||||||
local ident = entry.side .. tri(entry.color, ":" .. rsio.color_name(entry.color), "")
|
local name = entry.relay or "local"
|
||||||
local dupe = util.table_contains(ifaces, ident)
|
|
||||||
local mixed = (bundled_sides[entry.side] and (entry.color == nil)) or (bundled_sides[entry.side] == false and (entry.color ~= nil))
|
|
||||||
|
|
||||||
local mixed_msg = util.trinary(bundled_sides[entry.side], "bundled entry(s) but this entry is not", "non-bundled entry(s) but this entry is")
|
if phys[name] == nil then phys[name] = {} end
|
||||||
|
table.insert(phys[entry.relay or "local"], entry)
|
||||||
|
end
|
||||||
|
|
||||||
self.self_check_msg("> check redstone " .. ident .. " unique...", not dupe, "only one port should be set to a side/color combination")
|
for name, entries in pairs(phys) do
|
||||||
self.self_check_msg("> check redstone " .. ident .. " bundle...", not mixed, "this side has " .. mixed_msg .. " bundled, which will not work")
|
TextBox{parent=self.sc_log,text="> checking redstone @ "..name.."...",fg_bg=cpair(colors.blue,colors.white)}
|
||||||
self.self_check_msg("> check redstone " .. ident .. " valid...", redstone.validate(entry), "configuration invalid, please re-configure redstone entry")
|
|
||||||
|
|
||||||
bundled_sides[entry.side] = bundled_sides[entry.side] or entry.color ~= nil
|
local ifaces = {}
|
||||||
table.insert(ifaces, ident)
|
local bundled_sides = {}
|
||||||
|
|
||||||
|
for i = 1, #entries do
|
||||||
|
local entry = entries[i]
|
||||||
|
local ident = entry.side .. tri(entry.color, ":" .. rsio.color_name(entry.color), "")
|
||||||
|
|
||||||
|
local sc_dupe = util.table_contains(ifaces, ident)
|
||||||
|
local mixed = (bundled_sides[entry.side] and (entry.color == nil)) or (bundled_sides[entry.side] == false and (entry.color ~= nil))
|
||||||
|
|
||||||
|
local mixed_msg = util.trinary(bundled_sides[entry.side], "bundled entry(s) but this entry is not", "non-bundled entry(s) but this entry is")
|
||||||
|
|
||||||
|
self.self_check_msg("> check redstone " .. ident .. " unique...", not sc_dupe, "only one port should be set to a side/color combination")
|
||||||
|
self.self_check_msg("> check redstone " .. ident .. " bundle...", not mixed, "this side has " .. mixed_msg .. " bundled, which will not work")
|
||||||
|
self.self_check_msg("> check redstone " .. ident .. " valid...", redstone.validate(entry), "configuration invalid, please re-configure redstone entry")
|
||||||
|
|
||||||
|
if rsio.get_io_dir(entry.port) == rsio.IO_DIR.IN then
|
||||||
|
local in_dupe = util.table_contains(inputs[entry.unit or 0], entry.port)
|
||||||
|
self.self_check_msg("> check redstone " .. ident .. " input...", not in_dupe, "you cannot have multiple of the same input for a given unit or the facility ("..rsio.to_string(entry.port)..")")
|
||||||
|
end
|
||||||
|
|
||||||
|
bundled_sides[entry.side] = bundled_sides[entry.side] or entry.color ~= nil
|
||||||
|
table.insert(ifaces, ident)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- check peripheral configurations
|
-- check peripheral configurations
|
||||||
@ -245,7 +268,7 @@ function check.create(main_pane, settings_cfg, check_sys, style)
|
|||||||
|
|
||||||
TextBox{parent=check_sys,x=1,y=2,text=" RTU Gateway Self-Check",fg_bg=bw_fg_bg}
|
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=500,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
|
self.sc_log = ListBox{parent=sc,x=1,y=1,height=12,width=49,scroll_height=1000,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
|
||||||
|
|
||||||
local last_check = { nil, nil }
|
local last_check = { nil, nil }
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
local constants = require("scada-common.constants")
|
local constants = require("scada-common.constants")
|
||||||
|
local ppm = require("scada-common.ppm")
|
||||||
local rsio = require("scada-common.rsio")
|
local rsio = require("scada-common.rsio")
|
||||||
local util = require("scada-common.util")
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
@ -18,6 +19,7 @@ local NumberField = require("graphics.elements.form.NumberField")
|
|||||||
---@class rtu_rs_definition
|
---@class rtu_rs_definition
|
||||||
---@field unit integer|nil
|
---@field unit integer|nil
|
||||||
---@field port IO_PORT
|
---@field port IO_PORT
|
||||||
|
---@field relay string|nil
|
||||||
---@field side side
|
---@field side side
|
||||||
---@field color color|nil
|
---@field color color|nil
|
||||||
---@field invert true|nil
|
---@field invert true|nil
|
||||||
@ -33,6 +35,7 @@ local IO_MODE = rsio.IO_MODE
|
|||||||
local LEFT = core.ALIGN.LEFT
|
local LEFT = core.ALIGN.LEFT
|
||||||
|
|
||||||
local self = {
|
local self = {
|
||||||
|
rs_cfg_phy = false, ---@type string|nil|false
|
||||||
rs_cfg_port = 1, ---@type IO_PORT
|
rs_cfg_port = 1, ---@type IO_PORT
|
||||||
rs_cfg_editing = false, ---@type integer|false
|
rs_cfg_editing = false, ---@type integer|false
|
||||||
|
|
||||||
@ -108,6 +111,23 @@ local function color_to_idx(color)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- select the subset of redstone entries assigned to the given phy
|
||||||
|
---@param cfg rtu_rs_definition[] the full redstone entry list
|
||||||
|
---@param phy string|nil which phy to get redstone entries for
|
||||||
|
---@param invert boolean? true to get all except this phy
|
||||||
|
---@return rtu_rs_definition[]
|
||||||
|
local function redstone_subset(cfg, phy, invert)
|
||||||
|
local subset = {}
|
||||||
|
|
||||||
|
for i = 1, #cfg do
|
||||||
|
if ((not invert) and cfg[i].relay == phy) or (invert and cfg[i].relay ~= phy) then
|
||||||
|
table.insert(subset, cfg[i])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return subset
|
||||||
|
end
|
||||||
|
|
||||||
local redstone = {}
|
local redstone = {}
|
||||||
|
|
||||||
-- validate a redstone entry
|
-- validate a redstone entry
|
||||||
@ -137,21 +157,89 @@ function redstone.create(tool_ctl, main_pane, cfg_sys, rs_cfg, style)
|
|||||||
|
|
||||||
--#region Redstone
|
--#region Redstone
|
||||||
|
|
||||||
local rs_c_1 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
local rs_c_1 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
||||||
local rs_c_2 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
local rs_c_2 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
||||||
local rs_c_3 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
local rs_c_3 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
||||||
local rs_c_4 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
local rs_c_4 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
||||||
local rs_c_5 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
local rs_c_5 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
||||||
local rs_c_6 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
local rs_c_6 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
||||||
local rs_c_7 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
local rs_c_7 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
||||||
local rs_c_8 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
local rs_c_8 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
||||||
|
local rs_c_9 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
||||||
|
local rs_c_10 = Div{parent=rs_cfg,x=2,y=4,width=49}
|
||||||
|
|
||||||
local rs_pane = MultiPane{parent=rs_cfg,x=1,y=4,panes={rs_c_1,rs_c_2,rs_c_3,rs_c_4,rs_c_5,rs_c_6,rs_c_7,rs_c_8}}
|
local rs_pane = MultiPane{parent=rs_cfg,x=1,y=4,panes={rs_c_1,rs_c_2,rs_c_3,rs_c_4,rs_c_5,rs_c_6,rs_c_7,rs_c_8,rs_c_9,rs_c_10}}
|
||||||
|
|
||||||
TextBox{parent=rs_cfg,x=1,y=2,text=" Redstone Connections",fg_bg=cpair(colors.black,colors.red)}
|
local header = TextBox{parent=rs_cfg,x=1,y=2,text=" Redstone Connections",fg_bg=cpair(colors.black,colors.red)}
|
||||||
|
|
||||||
TextBox{parent=rs_c_1,x=1,y=1,text=" port side/color unit/facility",fg_bg=g_lg_fg_bg}
|
--#region Interface Selection
|
||||||
local rs_list = ListBox{parent=rs_c_1,x=1,y=2,height=11,width=49,scroll_height=200,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
|
|
||||||
|
TextBox{parent=rs_c_1,x=1,y=1,text="Configure this computer or a redstone relay."}
|
||||||
|
local iface_list = ListBox{parent=rs_c_1,x=1,y=3,height=10,width=49,scroll_height=1000,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
|
||||||
|
|
||||||
|
-- update relay interface list
|
||||||
|
function tool_ctl.update_relay_list()
|
||||||
|
local mounts = ppm.list_mounts()
|
||||||
|
|
||||||
|
iface_list.remove_all()
|
||||||
|
|
||||||
|
-- assemble list of configured relays
|
||||||
|
local relays = {}
|
||||||
|
for i = 1, #tmp_cfg.Redstone do
|
||||||
|
local def = tmp_cfg.Redstone[i]
|
||||||
|
if def.relay and not util.table_contains(relays, def.relay) then
|
||||||
|
table.insert(relays, def.relay)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- add unconfigured connected relays
|
||||||
|
for name, entry in pairs(mounts) do
|
||||||
|
if entry.type == "redstone_relay" and not util.table_contains(relays, name) then
|
||||||
|
table.insert(relays, name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function config_rs(name)
|
||||||
|
header.set_value(" Redstone Connections (" .. name .. ")")
|
||||||
|
|
||||||
|
self.rs_cfg_phy = tri(name == "local", nil, name)
|
||||||
|
|
||||||
|
tool_ctl.gen_rs_summary()
|
||||||
|
rs_pane.set_value(2)
|
||||||
|
end
|
||||||
|
|
||||||
|
local line = Div{parent=iface_list,height=2,fg_bg=cpair(colors.black,colors.white)}
|
||||||
|
TextBox{parent=line,x=1,y=1,text="@ local",fg_bg=cpair(colors.black,colors.white)}
|
||||||
|
TextBox{parent=line,x=3,y=2,text="This Computer",fg_bg=cpair(colors.gray,colors.white)}
|
||||||
|
local count = #redstone_subset(ini_cfg.Redstone, nil)
|
||||||
|
TextBox{parent=line,x=33,y=2,width=16,alignment=core.ALIGN.RIGHT,text=count.." connections",fg_bg=cpair(colors.gray,colors.white)}
|
||||||
|
|
||||||
|
PushButton{parent=line,x=41,y=1,min_width=8,height=1,text="CONFIG",callback=function()config_rs("local")end,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
||||||
|
|
||||||
|
for i = 1, #relays do
|
||||||
|
local name = relays[i]
|
||||||
|
|
||||||
|
line = Div{parent=iface_list,height=2,fg_bg=cpair(colors.black,colors.white)}
|
||||||
|
TextBox{parent=line,x=1,y=1,text="@ "..name,fg_bg=cpair(colors.black,colors.white)}
|
||||||
|
TextBox{parent=line,x=3,y=2,text="Redstone Relay",fg_bg=cpair(colors.gray,colors.white)}
|
||||||
|
TextBox{parent=line,x=18,y=2,text=tri(mounts[name],"ONLINE","OFFLINE"),fg_bg=cpair(tri(mounts[name],colors.green,colors.red),colors.white)}
|
||||||
|
count = #redstone_subset(ini_cfg.Redstone, name)
|
||||||
|
TextBox{parent=line,x=33,y=2,width=16,alignment=core.ALIGN.RIGHT,text=count.." connections",fg_bg=cpair(colors.gray,colors.white)}
|
||||||
|
|
||||||
|
PushButton{parent=line,x=41,y=1,min_width=8,height=1,text="CONFIG",callback=function()config_rs(name)end,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
tool_ctl.update_relay_list()
|
||||||
|
|
||||||
|
PushButton{parent=rs_c_1,x=1,y=14,text="\x1b Back",callback=function()main_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
PushButton{parent=rs_c_1,x=27,y=14,min_width=23,text="I don't see my relay!",callback=function()rs_pane.set_value(10)end,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
--#region Configuration List
|
||||||
|
|
||||||
|
TextBox{parent=rs_c_2,x=1,y=1,text=" port side/color unit/facility",fg_bg=g_lg_fg_bg}
|
||||||
|
local rs_list = ListBox{parent=rs_c_2,x=1,y=2,height=11,width=49,scroll_height=200,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
|
||||||
|
|
||||||
local function rs_revert()
|
local function rs_revert()
|
||||||
tmp_cfg.Redstone = tool_ctl.deep_copy_rs(ini_cfg.Redstone)
|
tmp_cfg.Redstone = tool_ctl.deep_copy_rs(ini_cfg.Redstone)
|
||||||
@ -159,40 +247,47 @@ function redstone.create(tool_ctl, main_pane, cfg_sys, rs_cfg, style)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function rs_apply()
|
local function rs_apply()
|
||||||
settings.set("Redstone", tmp_cfg.Redstone)
|
-- add the changed data to the existing saved data
|
||||||
|
local new_data = redstone_subset(tmp_cfg.Redstone, self.rs_cfg_phy)
|
||||||
|
local new_save = redstone_subset(ini_cfg.Redstone, self.rs_cfg_phy, true)
|
||||||
|
for i = 1, #new_data do table.insert(new_save, new_data[i]) end
|
||||||
|
|
||||||
|
settings.set("Redstone", new_save)
|
||||||
|
|
||||||
if settings.save("/rtu.settings") then
|
if settings.save("/rtu.settings") then
|
||||||
load_settings(settings_cfg, true)
|
load_settings(settings_cfg, true)
|
||||||
load_settings(ini_cfg)
|
load_settings(ini_cfg)
|
||||||
rs_pane.set_value(4)
|
rs_pane.set_value(5)
|
||||||
|
|
||||||
-- for return to list from saved screen
|
-- for return to list from saved screen
|
||||||
|
-- this will delete unsaved changes for other phy's, which is acceptable
|
||||||
tmp_cfg.Redstone = tool_ctl.deep_copy_rs(ini_cfg.Redstone)
|
tmp_cfg.Redstone = tool_ctl.deep_copy_rs(ini_cfg.Redstone)
|
||||||
tool_ctl.gen_rs_summary()
|
tool_ctl.gen_rs_summary()
|
||||||
|
tool_ctl.update_relay_list()
|
||||||
else
|
else
|
||||||
rs_pane.set_value(5)
|
rs_pane.set_value(6)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
PushButton{parent=rs_c_1,x=1,y=14,text="\x1b Back",callback=function()main_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
local function rs_back()
|
||||||
local rs_revert_btn = PushButton{parent=rs_c_1,x=8,y=14,min_width=16,text="Revert Changes",callback=rs_revert,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
self.rs_cfg_phy = false
|
||||||
PushButton{parent=rs_c_1,x=35,y=14,min_width=7,text="New +",callback=function()rs_pane.set_value(2)end,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg}
|
rs_pane.set_value(1)
|
||||||
local rs_apply_btn = PushButton{parent=rs_c_1,x=43,y=14,min_width=7,text="Apply",callback=rs_apply,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
header.set_value(" Redstone Connections")
|
||||||
|
end
|
||||||
|
|
||||||
TextBox{parent=rs_c_2,x=1,y=1,text="Select one of the below ports to use."}
|
PushButton{parent=rs_c_2,x=1,y=14,text="\x1b Back",callback=rs_back,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
local rs_revert_btn = PushButton{parent=rs_c_2,x=8,y=14,min_width=16,text="Revert Changes",callback=rs_revert,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
||||||
|
PushButton{parent=rs_c_2,x=35,y=14,min_width=7,text="New +",callback=function()rs_pane.set_value(3)end,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg}
|
||||||
|
local rs_apply_btn = PushButton{parent=rs_c_2,x=43,y=14,min_width=7,text="Apply",callback=rs_apply,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
||||||
|
|
||||||
local rs_ports = ListBox{parent=rs_c_2,x=1,y=3,height=10,width=49,scroll_height=200,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
|
--#endregion
|
||||||
|
--#region Port Selection
|
||||||
|
|
||||||
|
TextBox{parent=rs_c_3,x=1,y=1,text="Select one of the below ports to use."}
|
||||||
|
|
||||||
|
local rs_ports = ListBox{parent=rs_c_3,x=1,y=3,height=10,width=49,scroll_height=200,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
|
||||||
|
|
||||||
local function new_rs(port)
|
local function new_rs(port)
|
||||||
if (rsio.get_io_dir(port) == rsio.IO_DIR.IN) then
|
|
||||||
for i = 1, #tmp_cfg.Redstone do
|
|
||||||
if tmp_cfg.Redstone[i].port == port then
|
|
||||||
rs_pane.set_value(6)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
self.rs_cfg_editing = false
|
self.rs_cfg_editing = false
|
||||||
|
|
||||||
local text
|
local text
|
||||||
@ -249,7 +344,7 @@ function redstone.create(tool_ctl, main_pane, cfg_sys, rs_cfg, style)
|
|||||||
|
|
||||||
self.rs_cfg_selection.set_value(text)
|
self.rs_cfg_selection.set_value(text)
|
||||||
self.rs_cfg_port = port
|
self.rs_cfg_port = port
|
||||||
rs_pane.set_value(3)
|
rs_pane.set_value(4)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- add entries to redstone option list
|
-- add entries to redstone option list
|
||||||
@ -270,38 +365,43 @@ function redstone.create(tool_ctl, main_pane, cfg_sys, rs_cfg, style)
|
|||||||
TextBox{parent=entry,x=22,y=1,text=PORT_DESC_MAP[i][2],fg_bg=cpair(colors.gray,colors.white)}
|
TextBox{parent=entry,x=22,y=1,text=PORT_DESC_MAP[i][2],fg_bg=cpair(colors.gray,colors.white)}
|
||||||
end
|
end
|
||||||
|
|
||||||
PushButton{parent=rs_c_2,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=rs_c_3,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
self.rs_cfg_selection = TextBox{parent=rs_c_3,x=1,y=1,height=2,text=""}
|
--#endregion
|
||||||
|
--#region Port Configuration
|
||||||
|
|
||||||
PushButton{parent=rs_c_3,x=36,y=3,text="What's that?",min_width=14,callback=function()rs_pane.set_value(7)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
self.rs_cfg_selection = TextBox{parent=rs_c_4,x=1,y=1,height=2,text=""}
|
||||||
|
|
||||||
self.rs_cfg_side_l = TextBox{parent=rs_c_3,x=1,y=4,width=11,text="Output Side"}
|
PushButton{parent=rs_c_4,x=36,y=3,text="What's that?",min_width=14,callback=function()rs_pane.set_value(8)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
local side = Radio2D{parent=rs_c_3,x=1,y=5,rows=1,columns=6,default=1,options=side_options,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.red}
|
|
||||||
|
|
||||||
self.rs_cfg_unit_l = TextBox{parent=rs_c_3,x=25,y=7,width=7,text="Unit ID"}
|
self.rs_cfg_side_l = TextBox{parent=rs_c_4,x=1,y=4,width=11,text="Output Side"}
|
||||||
self.rs_cfg_unit = NumberField{parent=rs_c_3,x=33,y=7,width=10,max_chars=2,min=1,max=4,fg_bg=bw_fg_bg}
|
local side = Radio2D{parent=rs_c_4,x=1,y=5,rows=1,columns=6,default=1,options=side_options,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.red}
|
||||||
|
|
||||||
|
self.rs_cfg_unit_l = TextBox{parent=rs_c_4,x=25,y=7,width=7,text="Unit ID"}
|
||||||
|
self.rs_cfg_unit = NumberField{parent=rs_c_4,x=33,y=7,width=10,max_chars=2,min=1,max=4,fg_bg=bw_fg_bg}
|
||||||
|
|
||||||
local function set_bundled(bundled)
|
local function set_bundled(bundled)
|
||||||
if bundled then self.rs_cfg_color.enable() else self.rs_cfg_color.disable() end
|
if bundled then self.rs_cfg_color.enable() else self.rs_cfg_color.disable() end
|
||||||
end
|
end
|
||||||
|
|
||||||
self.rs_cfg_shortcut = TextBox{parent=rs_c_3,x=1,y=9,height=4,text="This shortcut will add entries for each of the 4 waste outputs. If you select bundled, 4 colors will be assigned to the selected side. Otherwise, 4 default sides will be used."}
|
self.rs_cfg_shortcut = TextBox{parent=rs_c_4,x=1,y=9,height=4,text="This shortcut will add entries for each of the 4 waste outputs. If you select bundled, 4 colors will be assigned to the selected side. Otherwise, 4 default sides will be used."}
|
||||||
self.rs_cfg_shortcut.hide(true)
|
self.rs_cfg_shortcut.hide(true)
|
||||||
|
|
||||||
self.rs_cfg_bundled = Checkbox{parent=rs_c_3,x=1,y=7,label="Is Bundled?",default=false,box_fg_bg=cpair(colors.red,colors.black),callback=set_bundled,disable_fg_bg=g_lg_fg_bg}
|
self.rs_cfg_bundled = Checkbox{parent=rs_c_4,x=1,y=7,label="Is Bundled?",default=false,box_fg_bg=cpair(colors.red,colors.black),callback=set_bundled,disable_fg_bg=g_lg_fg_bg}
|
||||||
self.rs_cfg_color = Radio2D{parent=rs_c_3,x=1,y=9,rows=4,columns=4,default=1,options=color_options,radio_colors=cpair(colors.lightGray,colors.black),color_map=color_options_map,disable_color=colors.gray,disable_fg_bg=g_lg_fg_bg}
|
self.rs_cfg_color = Radio2D{parent=rs_c_4,x=1,y=9,rows=4,columns=4,default=1,options=color_options,radio_colors=cpair(colors.lightGray,colors.black),color_map=color_options_map,disable_color=colors.gray,disable_fg_bg=g_lg_fg_bg}
|
||||||
self.rs_cfg_color.disable()
|
self.rs_cfg_color.disable()
|
||||||
|
|
||||||
local rs_err = TextBox{parent=rs_c_3,x=8,y=14,width=30,text="Unit ID must be within 1 to 4.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
local rs_err = TextBox{parent=rs_c_4,x=8,y=14,width=30,text="Unit ID invalid.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||||
rs_err.hide(true)
|
rs_err.hide(true)
|
||||||
|
|
||||||
local function back_from_rs_opts()
|
local function back_from_rs_opts()
|
||||||
rs_err.hide(true)
|
rs_err.hide(true)
|
||||||
if self.rs_cfg_editing ~= false then rs_pane.set_value(1) else rs_pane.set_value(2) end
|
if self.rs_cfg_editing ~= false then rs_pane.set_value(2) else rs_pane.set_value(3) end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function save_rs_entry()
|
local function save_rs_entry()
|
||||||
|
assert(self.rs_cfg_phy ~= false, "tried to save a redstone entry without a phy")
|
||||||
|
|
||||||
local port = self.rs_cfg_port
|
local port = self.rs_cfg_port
|
||||||
local u = tonumber(self.rs_cfg_unit.get_value())
|
local u = tonumber(self.rs_cfg_unit.get_value())
|
||||||
|
|
||||||
@ -313,12 +413,23 @@ function redstone.create(tool_ctl, main_pane, cfg_sys, rs_cfg, style)
|
|||||||
local def = {
|
local def = {
|
||||||
unit = tri(PORT_DSGN[port] == 1, u, nil),
|
unit = tri(PORT_DSGN[port] == 1, u, nil),
|
||||||
port = port,
|
port = port,
|
||||||
|
relay = self.rs_cfg_phy,
|
||||||
side = side_options_map[side.get_value()],
|
side = side_options_map[side.get_value()],
|
||||||
color = tri(self.rs_cfg_bundled.get_value() and rsio.is_digital(port), color_options_map[self.rs_cfg_color.get_value()], nil),
|
color = tri(self.rs_cfg_bundled.get_value() and rsio.is_digital(port), color_options_map[self.rs_cfg_color.get_value()], nil),
|
||||||
invert = self.rs_cfg_inverted.get_value() or nil
|
invert = self.rs_cfg_inverted.get_value() or nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.rs_cfg_editing == false then
|
if self.rs_cfg_editing == false then
|
||||||
|
-- check for duplicate inputs for this unit/facility
|
||||||
|
if (rsio.get_io_dir(port) == rsio.IO_DIR.IN) then
|
||||||
|
for i = 1, #tmp_cfg.Redstone do
|
||||||
|
if tmp_cfg.Redstone[i].port == port and tmp_cfg.Redstone[i].unit == def.unit then
|
||||||
|
rs_pane.set_value(7)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
table.insert(tmp_cfg.Redstone, def)
|
table.insert(tmp_cfg.Redstone, def)
|
||||||
else
|
else
|
||||||
def.port = tmp_cfg.Redstone[self.rs_cfg_editing].port
|
def.port = tmp_cfg.Redstone[self.rs_cfg_editing].port
|
||||||
@ -331,13 +442,14 @@ function redstone.create(tool_ctl, main_pane, cfg_sys, rs_cfg, style)
|
|||||||
table.insert(tmp_cfg.Redstone, {
|
table.insert(tmp_cfg.Redstone, {
|
||||||
unit = tri(PORT_DSGN[IO.WASTE_PU + i] == 1, u, nil),
|
unit = tri(PORT_DSGN[IO.WASTE_PU + i] == 1, u, nil),
|
||||||
port = IO.WASTE_PU + i,
|
port = IO.WASTE_PU + i,
|
||||||
|
relay = self.rs_cfg_phy,
|
||||||
side = tri(self.rs_cfg_bundled.get_value(), side_options_map[side.get_value()], default_sides[i + 1]),
|
side = tri(self.rs_cfg_bundled.get_value(), side_options_map[side.get_value()], default_sides[i + 1]),
|
||||||
color = tri(self.rs_cfg_bundled.get_value(), default_colors[i + 1], nil)
|
color = tri(self.rs_cfg_bundled.get_value(), default_colors[i + 1], nil)
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
rs_pane.set_value(1)
|
rs_pane.set_value(2)
|
||||||
tool_ctl.gen_rs_summary()
|
tool_ctl.gen_rs_summary()
|
||||||
|
|
||||||
side.set_value(1)
|
side.set_value(1)
|
||||||
@ -349,30 +461,35 @@ function redstone.create(tool_ctl, main_pane, cfg_sys, rs_cfg, style)
|
|||||||
else rs_err.show() end
|
else rs_err.show() end
|
||||||
end
|
end
|
||||||
|
|
||||||
PushButton{parent=rs_c_3,x=1,y=14,text="\x1b Back",callback=back_from_rs_opts,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=rs_c_4,x=1,y=14,text="\x1b Back",callback=back_from_rs_opts,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
self.rs_cfg_advanced = PushButton{parent=rs_c_3,x=30,y=14,min_width=10,text="Advanced",callback=function()rs_pane.set_value(8)end,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
self.rs_cfg_advanced = PushButton{parent=rs_c_4,x=30,y=14,min_width=10,text="Advanced",callback=function()rs_pane.set_value(9)end,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg}
|
||||||
PushButton{parent=rs_c_3,x=41,y=14,min_width=9,text="Confirm",callback=save_rs_entry,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=rs_c_4,x=41,y=14,min_width=9,text="Confirm",callback=save_rs_entry,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=rs_c_4,x=1,y=1,text="Settings saved!"}
|
--#endregion
|
||||||
PushButton{parent=rs_c_4,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
|
||||||
PushButton{parent=rs_c_4,x=44,y=14,min_width=6,text="Home",callback=function()tool_ctl.go_home()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
|
||||||
|
|
||||||
TextBox{parent=rs_c_5,x=1,y=1,height=5,text="Failed to save the settings file.\n\nThere may not be enough space for the modification or server file permissions may be denying writes."}
|
TextBox{parent=rs_c_5,x=1,y=1,text="Settings saved!"}
|
||||||
PushButton{parent=rs_c_5,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=rs_c_5,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
PushButton{parent=rs_c_5,x=44,y=14,min_width=6,text="Home",callback=function()tool_ctl.go_home()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=rs_c_5,x=44,y=14,min_width=6,text="Home",callback=function()tool_ctl.go_home()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=rs_c_6,x=1,y=1,height=5,text="You already configured this input. There can only be one entry for each input.\n\nPlease select a different port."}
|
TextBox{parent=rs_c_6,x=1,y=1,height=5,text="Failed to save the settings file.\n\nThere may not be enough space for the modification or server file permissions may be denying writes."}
|
||||||
PushButton{parent=rs_c_6,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=rs_c_6,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
PushButton{parent=rs_c_6,x=44,y=14,min_width=6,text="Home",callback=function()tool_ctl.go_home()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=rs_c_7,x=1,y=1,height=4,text="(Normal) Digital Input: On if there is a redstone signal, off otherwise\nInverted Digital Input: On without a redstone signal, off otherwise"}
|
TextBox{parent=rs_c_7,x=1,y=1,height=6,text="You already configured this input for this facility/unit assignment. There can only be one entry for each input per each unit or the facility (for facility inputs).\n\nPlease select a different port."}
|
||||||
TextBox{parent=rs_c_7,x=1,y=6,height=4,text="(Normal) Digital Output: Redstone signal to 'turn it on', none to 'turn it off'\nInverted Digital Output: No redstone signal to 'turn it on', redstone signal to 'turn it off'"}
|
|
||||||
TextBox{parent=rs_c_7,x=1,y=11,height=2,text="Analog Input: 0-15 redstone power level input\nAnalog Output: 0-15 scaled redstone power level output"}
|
|
||||||
PushButton{parent=rs_c_7,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(3)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=rs_c_7,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(3)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=rs_c_8,x=1,y=1,height=5,text="Advanced Options"}
|
TextBox{parent=rs_c_8,x=1,y=1,height=4,text="(Normal) Digital Input: On if there is a redstone signal, off otherwise\nInverted Digital Input: On without a redstone signal, off otherwise"}
|
||||||
self.rs_cfg_inverted = Checkbox{parent=rs_c_8,x=1,y=3,label="Invert",default=false,box_fg_bg=cpair(colors.red,colors.black),callback=function()end,disable_fg_bg=g_lg_fg_bg}
|
TextBox{parent=rs_c_8,x=1,y=6,height=4,text="(Normal) Digital Output: Redstone signal to 'turn it on', none to 'turn it off'\nInverted Digital Output: No redstone signal to 'turn it on', redstone signal to 'turn it off'"}
|
||||||
TextBox{parent=rs_c_8,x=3,y=4,height=4,text="Digital I/O is already inverted (or not) based on intended use. If you have a non-standard setup, you can use this option to avoid needing a redstone inverter.",fg_bg=cpair(colors.gray,colors.lightGray)}
|
TextBox{parent=rs_c_8,x=1,y=11,height=2,text="Analog Input: 0-15 redstone power level input\nAnalog Output: 0-15 scaled redstone power level output"}
|
||||||
PushButton{parent=rs_c_8,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(3)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=rs_c_8,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(4)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
|
TextBox{parent=rs_c_9,x=1,y=1,height=5,text="Advanced Options"}
|
||||||
|
self.rs_cfg_inverted = Checkbox{parent=rs_c_9,x=1,y=3,label="Invert",default=false,box_fg_bg=cpair(colors.red,colors.black),callback=function()end,disable_fg_bg=g_lg_fg_bg}
|
||||||
|
TextBox{parent=rs_c_9,x=3,y=4,height=4,text="Digital I/O is already inverted (or not) based on intended use. If you have a non-standard setup, you can use this option to avoid needing a redstone inverter.",fg_bg=cpair(colors.gray,colors.lightGray)}
|
||||||
|
PushButton{parent=rs_c_9,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(4)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
|
TextBox{parent=rs_c_10,x=1,y=1,height=10,text="Make sure your relay is either touching the RTU gateway or connected via wired modems. There should be a wired modem on a side of the RTU gateway then one on the device, connected by a cable. The modem on the device needs to be right clicked to connect it (which will turn its border red), at which point the peripheral name will be shown in the chat."}
|
||||||
|
PushButton{parent=rs_c_10,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
--#endregion
|
--#endregion
|
||||||
|
|
||||||
@ -422,7 +539,7 @@ function redstone.create(tool_ctl, main_pane, cfg_sys, rs_cfg, style)
|
|||||||
side.set_value(side_to_idx(def.side))
|
side.set_value(side_to_idx(def.side))
|
||||||
self.rs_cfg_color.set_value(value)
|
self.rs_cfg_color.set_value(value)
|
||||||
self.rs_cfg_inverted.set_value(def.invert or false)
|
self.rs_cfg_inverted.set_value(def.invert or false)
|
||||||
rs_pane.set_value(3)
|
rs_pane.set_value(4)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function delete_rs_entry(idx)
|
local function delete_rs_entry(idx)
|
||||||
@ -432,34 +549,41 @@ function redstone.create(tool_ctl, main_pane, cfg_sys, rs_cfg, style)
|
|||||||
|
|
||||||
-- generate the redstone summary list
|
-- generate the redstone summary list
|
||||||
function tool_ctl.gen_rs_summary()
|
function tool_ctl.gen_rs_summary()
|
||||||
|
assert(self.rs_cfg_phy ~= false, "tried to generate a summary without a phy set")
|
||||||
|
|
||||||
rs_list.remove_all()
|
rs_list.remove_all()
|
||||||
|
|
||||||
local modified = #ini_cfg.Redstone ~= #tmp_cfg.Redstone
|
local ini = redstone_subset(ini_cfg.Redstone, self.rs_cfg_phy)
|
||||||
|
local tmp = redstone_subset(tmp_cfg.Redstone, self.rs_cfg_phy)
|
||||||
|
|
||||||
|
local modified = #ini ~= #tmp
|
||||||
|
|
||||||
for i = 1, #tmp_cfg.Redstone do
|
for i = 1, #tmp_cfg.Redstone do
|
||||||
local def = tmp_cfg.Redstone[i]
|
local def = tmp_cfg.Redstone[i]
|
||||||
|
|
||||||
local name = rsio.to_string(def.port)
|
if def.relay == self.rs_cfg_phy then
|
||||||
local io_dir = tri(rsio.get_io_dir(def.port) == rsio.IO_DIR.IN, "\x1a", "\x1b")
|
local name = rsio.to_string(def.port)
|
||||||
local io_c = tri(rsio.is_digital(def.port), colors.blue, colors.purple)
|
local io_dir = tri(rsio.get_io_dir(def.port) == rsio.IO_DIR.IN, "\x1a", "\x1b")
|
||||||
local conn = def.side
|
local io_c = tri(rsio.is_digital(def.port), colors.blue, colors.purple)
|
||||||
local unit = util.strval(def.unit or "F")
|
local conn = def.side
|
||||||
|
local unit = util.strval(def.unit or "F")
|
||||||
|
|
||||||
if def.color ~= nil then conn = def.side .. "/" .. rsio.color_name(def.color) end
|
if def.color ~= nil then conn = def.side .. "/" .. rsio.color_name(def.color) end
|
||||||
|
|
||||||
local entry = Div{parent=rs_list,height=1}
|
local entry = Div{parent=rs_list,height=1}
|
||||||
TextBox{parent=entry,x=1,y=1,width=1,text=io_dir,fg_bg=cpair(tri(def.invert,colors.orange,io_c),colors.white)}
|
TextBox{parent=entry,x=1,y=1,width=1,text=io_dir,fg_bg=cpair(tri(def.invert,colors.orange,io_c),colors.white)}
|
||||||
TextBox{parent=entry,x=2,y=1,width=14,text=name}
|
TextBox{parent=entry,x=2,y=1,width=14,text=name}
|
||||||
TextBox{parent=entry,x=16,y=1,width=string.len(conn),text=conn,fg_bg=cpair(colors.gray,colors.white)}
|
TextBox{parent=entry,x=16,y=1,width=string.len(conn),text=conn,fg_bg=cpair(colors.gray,colors.white)}
|
||||||
TextBox{parent=entry,x=33,y=1,width=1,text=unit,fg_bg=cpair(colors.gray,colors.white)}
|
TextBox{parent=entry,x=33,y=1,width=1,text=unit,fg_bg=cpair(colors.gray,colors.white)}
|
||||||
PushButton{parent=entry,x=35,y=1,min_width=6,height=1,text="EDIT",callback=function()edit_rs_entry(i)end,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=entry,x=35,y=1,min_width=6,height=1,text="EDIT",callback=function()edit_rs_entry(i)end,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg}
|
||||||
PushButton{parent=entry,x=41,y=1,min_width=8,height=1,text="DELETE",callback=function()delete_rs_entry(i)end,fg_bg=cpair(colors.black,colors.red),active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=entry,x=41,y=1,min_width=8,height=1,text="DELETE",callback=function()delete_rs_entry(i)end,fg_bg=cpair(colors.black,colors.red),active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
if not modified then
|
if not modified then
|
||||||
local a = ini_cfg.Redstone[i]
|
local a = ini_cfg.Redstone[i]
|
||||||
local b = tmp_cfg.Redstone[i]
|
local b = tmp_cfg.Redstone[i]
|
||||||
|
|
||||||
modified = (a.unit ~= b.unit) or (a.port ~= b.port) or (a.side ~= b.side) or (a.color ~= b.color) or (a.invert ~= b.invert)
|
modified = (a.unit ~= b.unit) or (a.port ~= b.port) or (a.relay ~= b.relay) or (a.side ~= b.side) or (a.color ~= b.color) or (a.invert ~= b.invert)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -36,7 +36,8 @@ local changes = {
|
|||||||
{ "v1.7.15", { "Added front panel UI theme", "Added color accessibility modes" } },
|
{ "v1.7.15", { "Added front panel UI theme", "Added color accessibility modes" } },
|
||||||
{ "v1.9.2", { "Added standard with black off state color mode", "Added blue indicator color modes" } },
|
{ "v1.9.2", { "Added standard with black off state color mode", "Added blue indicator color modes" } },
|
||||||
{ "v1.10.2", { "Re-organized peripheral configuration UI, resulting in some input fields being re-ordered" } },
|
{ "v1.10.2", { "Re-organized peripheral configuration UI, resulting in some input fields being re-ordered" } },
|
||||||
{ "v1.11.8", { "Added advanced option to invert digital redstone signals" } }
|
{ "v1.11.8", { "Added advanced option to invert digital redstone signals" } },
|
||||||
|
{ "v1.12.0", { "Added support for redstone relays" } }
|
||||||
}
|
}
|
||||||
|
|
||||||
---@class rtu_configurator
|
---@class rtu_configurator
|
||||||
@ -76,6 +77,7 @@ local tool_ctl = {
|
|||||||
gen_summary = nil, ---@type function
|
gen_summary = nil, ---@type function
|
||||||
load_legacy = nil, ---@type function
|
load_legacy = nil, ---@type function
|
||||||
update_peri_list = nil, ---@type function
|
update_peri_list = nil, ---@type function
|
||||||
|
update_relay_list = nil, ---@type function
|
||||||
gen_peri_summary = nil, ---@type function
|
gen_peri_summary = nil, ---@type function
|
||||||
gen_rs_summary = nil, ---@type function
|
gen_rs_summary = nil, ---@type function
|
||||||
}
|
}
|
||||||
@ -128,7 +130,7 @@ end
|
|||||||
---@param data rtu_rs_definition[]
|
---@param data rtu_rs_definition[]
|
||||||
function tool_ctl.deep_copy_rs(data)
|
function tool_ctl.deep_copy_rs(data)
|
||||||
local array = {}
|
local array = {}
|
||||||
for _, d in ipairs(data) do table.insert(array, { unit = d.unit, port = d.port, side = d.side, color = d.color, invert = d.invert }) end
|
for _, d in ipairs(data) do table.insert(array, { unit = d.unit, port = d.port, relay = d.relay, side = d.side, color = d.color, invert = d.invert }) end
|
||||||
return array
|
return array
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -208,7 +210,6 @@ local function config_view(display)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function show_rs_conns()
|
local function show_rs_conns()
|
||||||
tool_ctl.gen_rs_summary()
|
|
||||||
main_pane.set_value(9)
|
main_pane.set_value(9)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -348,10 +349,12 @@ function configurator.configure(ask_config)
|
|||||||
---@diagnostic disable-next-line: discard-returns
|
---@diagnostic disable-next-line: discard-returns
|
||||||
ppm.handle_unmount(param1)
|
ppm.handle_unmount(param1)
|
||||||
tool_ctl.update_peri_list()
|
tool_ctl.update_peri_list()
|
||||||
|
tool_ctl.update_relay_list()
|
||||||
elseif event == "peripheral" then
|
elseif event == "peripheral" then
|
||||||
---@diagnostic disable-next-line: discard-returns
|
---@diagnostic disable-next-line: discard-returns
|
||||||
ppm.mount(param1)
|
ppm.mount(param1)
|
||||||
tool_ctl.update_peri_list()
|
tool_ctl.update_peri_list()
|
||||||
|
tool_ctl.update_relay_list()
|
||||||
end
|
end
|
||||||
|
|
||||||
if event == "terminate" then return end
|
if event == "terminate" then return end
|
||||||
|
|||||||
@ -11,10 +11,14 @@ local digital_write = rsio.digital_write
|
|||||||
|
|
||||||
-- create new redstone device
|
-- create new redstone device
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
|
---@param relay? table optional redstone relay to use instead of the computer's redstone interface
|
||||||
---@return rtu_rs_device interface, boolean faulted
|
---@return rtu_rs_device interface, boolean faulted
|
||||||
function redstone_rtu.new()
|
function redstone_rtu.new(relay)
|
||||||
local unit = rtu.init_unit()
|
local unit = rtu.init_unit()
|
||||||
|
|
||||||
|
-- physical interface to use
|
||||||
|
local phy = relay or rs
|
||||||
|
|
||||||
-- get RTU interface
|
-- get RTU interface
|
||||||
local interface = unit.interface()
|
local interface = unit.interface()
|
||||||
|
|
||||||
@ -30,104 +34,114 @@ function redstone_rtu.new()
|
|||||||
write_holding_reg = interface.write_holding_reg
|
write_holding_reg = interface.write_holding_reg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
-- change the phy in use (a relay or rs)
|
||||||
|
---@param new_phy table
|
||||||
|
function public.remount_phy(new_phy) phy = new_phy end
|
||||||
|
|
||||||
|
-- NOTE: for runtime speed, inversion logic results in extra code here but less code when functions are called
|
||||||
|
|
||||||
-- link digital input
|
-- link digital input
|
||||||
---@param side string
|
---@param side string
|
||||||
---@param color integer
|
---@param color integer
|
||||||
---@param invert boolean|nil
|
---@param invert boolean|nil
|
||||||
|
---@return integer count count of digital inputs
|
||||||
function public.link_di(side, color, invert)
|
function public.link_di(side, color, invert)
|
||||||
local f_read ---@type function
|
local f_read ---@type function
|
||||||
|
|
||||||
if color then
|
if color then
|
||||||
if invert then
|
if invert then
|
||||||
f_read = function () return digital_read(not rs.testBundledInput(side, color)) end
|
f_read = function () return digital_read(not phy.testBundledInput(side, color)) end
|
||||||
else
|
else
|
||||||
f_read = function () return digital_read(rs.testBundledInput(side, color)) end
|
f_read = function () return digital_read(phy.testBundledInput(side, color)) end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
if invert then
|
if invert then
|
||||||
f_read = function () return digital_read(not rs.getInput(side)) end
|
f_read = function () return digital_read(not phy.getInput(side)) end
|
||||||
else
|
else
|
||||||
f_read = function () return digital_read(rs.getInput(side)) end
|
f_read = function () return digital_read(phy.getInput(side)) end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
unit.connect_di(f_read)
|
return unit.connect_di(f_read)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- link digital output
|
-- link digital output
|
||||||
---@param side string
|
---@param side string
|
||||||
---@param color integer
|
---@param color integer
|
||||||
---@param invert boolean|nil
|
---@param invert boolean|nil
|
||||||
|
---@return integer count count of digital outputs
|
||||||
function public.link_do(side, color, invert)
|
function public.link_do(side, color, invert)
|
||||||
local f_read ---@type function
|
local f_read ---@type function
|
||||||
local f_write ---@type function
|
local f_write ---@type function
|
||||||
|
|
||||||
if color then
|
if color then
|
||||||
if invert then
|
if invert then
|
||||||
f_read = function () return digital_read(not colors.test(rs.getBundledOutput(side), color)) end
|
f_read = function () return digital_read(not colors.test(phy.getBundledOutput(side), color)) end
|
||||||
|
|
||||||
f_write = function (level)
|
f_write = function (level)
|
||||||
if level ~= IO_LVL.FLOATING and level ~= IO_LVL.DISCONNECT then
|
if level ~= IO_LVL.FLOATING and level ~= IO_LVL.DISCONNECT then
|
||||||
local output = rs.getBundledOutput(side)
|
local output = phy.getBundledOutput(side)
|
||||||
|
|
||||||
-- inverted conditions
|
-- inverted conditions
|
||||||
if digital_write(level) then
|
if digital_write(level) then
|
||||||
output = colors.subtract(output, color)
|
output = colors.subtract(output, color)
|
||||||
else output = colors.combine(output, color) end
|
else output = colors.combine(output, color) end
|
||||||
|
|
||||||
rs.setBundledOutput(side, output)
|
phy.setBundledOutput(side, output)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
f_read = function () return digital_read(colors.test(rs.getBundledOutput(side), color)) end
|
f_read = function () return digital_read(colors.test(phy.getBundledOutput(side), color)) end
|
||||||
|
|
||||||
f_write = function (level)
|
f_write = function (level)
|
||||||
if level ~= IO_LVL.FLOATING and level ~= IO_LVL.DISCONNECT then
|
if level ~= IO_LVL.FLOATING and level ~= IO_LVL.DISCONNECT then
|
||||||
local output = rs.getBundledOutput(side)
|
local output = phy.getBundledOutput(side)
|
||||||
|
|
||||||
if digital_write(level) then
|
if digital_write(level) then
|
||||||
output = colors.combine(output, color)
|
output = colors.combine(output, color)
|
||||||
else output = colors.subtract(output, color) end
|
else output = colors.subtract(output, color) end
|
||||||
|
|
||||||
rs.setBundledOutput(side, output)
|
phy.setBundledOutput(side, output)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
if invert then
|
if invert then
|
||||||
f_read = function () return digital_read(not rs.getOutput(side)) end
|
f_read = function () return digital_read(not phy.getOutput(side)) end
|
||||||
|
|
||||||
f_write = function (level)
|
f_write = function (level)
|
||||||
if level ~= IO_LVL.FLOATING and level ~= IO_LVL.DISCONNECT then
|
if level ~= IO_LVL.FLOATING and level ~= IO_LVL.DISCONNECT then
|
||||||
rs.setOutput(side, not digital_write(level))
|
phy.setOutput(side, not digital_write(level))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
f_read = function () return digital_read(rs.getOutput(side)) end
|
f_read = function () return digital_read(phy.getOutput(side)) end
|
||||||
|
|
||||||
f_write = function (level)
|
f_write = function (level)
|
||||||
if level ~= IO_LVL.FLOATING and level ~= IO_LVL.DISCONNECT then
|
if level ~= IO_LVL.FLOATING and level ~= IO_LVL.DISCONNECT then
|
||||||
rs.setOutput(side, digital_write(level))
|
phy.setOutput(side, digital_write(level))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
unit.connect_coil(f_read, f_write)
|
return unit.connect_coil(f_read, f_write)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- link analog input
|
-- link analog input
|
||||||
---@param side string
|
---@param side string
|
||||||
|
---@return integer count count of analog inputs
|
||||||
function public.link_ai(side)
|
function public.link_ai(side)
|
||||||
unit.connect_input_reg(function () return rs.getAnalogInput(side) end)
|
return unit.connect_input_reg(function () return phy.getAnalogInput(side) end)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- link analog output
|
-- link analog output
|
||||||
---@param side string
|
---@param side string
|
||||||
|
---@return integer count count of analog outputs
|
||||||
function public.link_ao(side)
|
function public.link_ao(side)
|
||||||
unit.connect_holding_reg(
|
return unit.connect_holding_reg(
|
||||||
function () return rs.getAnalogOutput(side) end,
|
function () return phy.getAnalogOutput(side) end,
|
||||||
function (value) rs.setAnalogOutput(side, value) end
|
function (value) phy.setAnalogOutput(side, value) end
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -399,43 +399,41 @@ function modbus.new(rtu_dev, use_parallel_read)
|
|||||||
return public
|
return public
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- create an error reply
|
||||||
|
---@nodiscard
|
||||||
|
---@param packet modbus_frame MODBUS packet frame
|
||||||
|
---@param code MODBUS_EXCODE exception code
|
||||||
|
---@return modbus_packet reply
|
||||||
|
local function excode_reply(packet, code)
|
||||||
|
-- reply back with error flag and exception code
|
||||||
|
local reply = comms.modbus_packet()
|
||||||
|
local fcode = bit.bor(packet.func_code, MODBUS_FCODE.ERROR_FLAG)
|
||||||
|
reply.make(packet.txn_id, packet.unit_id, fcode, { code })
|
||||||
|
return reply
|
||||||
|
end
|
||||||
|
|
||||||
|
-- return a SERVER_DEVICE_FAIL error reply
|
||||||
|
---@nodiscard
|
||||||
|
---@param packet modbus_frame MODBUS packet frame
|
||||||
|
---@return modbus_packet reply
|
||||||
|
function modbus.reply__srv_device_fail(packet) return excode_reply(packet, MODBUS_EXCODE.SERVER_DEVICE_FAIL) end
|
||||||
|
|
||||||
-- return a SERVER_DEVICE_BUSY error reply
|
-- return a SERVER_DEVICE_BUSY error reply
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
---@param packet modbus_frame MODBUS packet frame
|
---@param packet modbus_frame MODBUS packet frame
|
||||||
---@return modbus_packet reply
|
---@return modbus_packet reply
|
||||||
function modbus.reply__srv_device_busy(packet)
|
function modbus.reply__srv_device_busy(packet) return excode_reply(packet, MODBUS_EXCODE.SERVER_DEVICE_BUSY) end
|
||||||
-- reply back with error flag and exception code
|
|
||||||
local reply = comms.modbus_packet()
|
|
||||||
local fcode = bit.bor(packet.func_code, MODBUS_FCODE.ERROR_FLAG)
|
|
||||||
local data = { MODBUS_EXCODE.SERVER_DEVICE_BUSY }
|
|
||||||
reply.make(packet.txn_id, packet.unit_id, fcode, data)
|
|
||||||
return reply
|
|
||||||
end
|
|
||||||
|
|
||||||
-- return a NEG_ACKNOWLEDGE error reply
|
-- return a NEG_ACKNOWLEDGE error reply
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
---@param packet modbus_frame MODBUS packet frame
|
---@param packet modbus_frame MODBUS packet frame
|
||||||
---@return modbus_packet reply
|
---@return modbus_packet reply
|
||||||
function modbus.reply__neg_ack(packet)
|
function modbus.reply__neg_ack(packet) return excode_reply(packet, MODBUS_EXCODE.NEG_ACKNOWLEDGE) end
|
||||||
-- reply back with error flag and exception code
|
|
||||||
local reply = comms.modbus_packet()
|
|
||||||
local fcode = bit.bor(packet.func_code, MODBUS_FCODE.ERROR_FLAG)
|
|
||||||
local data = { MODBUS_EXCODE.NEG_ACKNOWLEDGE }
|
|
||||||
reply.make(packet.txn_id, packet.unit_id, fcode, data)
|
|
||||||
return reply
|
|
||||||
end
|
|
||||||
|
|
||||||
-- return a GATEWAY_PATH_UNAVAILABLE error reply
|
-- return a GATEWAY_PATH_UNAVAILABLE error reply
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
---@param packet modbus_frame MODBUS packet frame
|
---@param packet modbus_frame MODBUS packet frame
|
||||||
---@return modbus_packet reply
|
---@return modbus_packet reply
|
||||||
function modbus.reply__gw_unavailable(packet)
|
function modbus.reply__gw_unavailable(packet) return excode_reply(packet, MODBUS_EXCODE.GATEWAY_PATH_UNAVAILABLE) end
|
||||||
-- reply back with error flag and exception code
|
|
||||||
local reply = comms.modbus_packet()
|
|
||||||
local fcode = bit.bor(packet.func_code, MODBUS_FCODE.ERROR_FLAG)
|
|
||||||
local data = { MODBUS_EXCODE.GATEWAY_PATH_UNAVAILABLE }
|
|
||||||
reply.make(packet.txn_id, packet.unit_id, fcode, data)
|
|
||||||
return reply
|
|
||||||
end
|
|
||||||
|
|
||||||
return modbus
|
return modbus
|
||||||
|
|||||||
@ -19,7 +19,8 @@ local LED = require("graphics.elements.indicators.LED")
|
|||||||
local LEDPair = require("graphics.elements.indicators.LEDPair")
|
local LEDPair = require("graphics.elements.indicators.LEDPair")
|
||||||
local RGBLED = require("graphics.elements.indicators.RGBLED")
|
local RGBLED = require("graphics.elements.indicators.RGBLED")
|
||||||
|
|
||||||
local LINK_STATE = types.PANEL_LINK_STATE
|
local LINK_STATE = types.PANEL_LINK_STATE
|
||||||
|
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
|
||||||
|
|
||||||
local ALIGN = core.ALIGN
|
local ALIGN = core.ALIGN
|
||||||
|
|
||||||
@ -129,30 +130,45 @@ local function init(panel, units)
|
|||||||
-- show routine statuses
|
-- show routine statuses
|
||||||
for i = 1, list_length do
|
for i = 1, list_length do
|
||||||
TextBox{parent=threads,x=1,y=i,text=util.sprintf("%02d",i)}
|
TextBox{parent=threads,x=1,y=i,text=util.sprintf("%02d",i)}
|
||||||
local rt_unit = LED{parent=threads,x=4,y=i,label="RT",colors=ind_grn}
|
local rt_unit = LED{parent=threads,x=4,y=i,label="RT",colors=util.trinary(units[i].type~=RTU_UNIT_TYPE.REDSTONE,ind_grn,cpair(style.ind_bkg,style.ind_bkg))}
|
||||||
rt_unit.register(databus.ps, "routine__unit_" .. i, rt_unit.update)
|
rt_unit.register(databus.ps, "routine__unit_" .. i, rt_unit.update)
|
||||||
end
|
end
|
||||||
|
|
||||||
local unit_hw_statuses = Div{parent=panel,height=term_h-3,x=25,y=3}
|
local unit_hw_statuses = Div{parent=panel,height=term_h-3,x=25,y=3}
|
||||||
|
|
||||||
|
local relay_counter = 0
|
||||||
|
|
||||||
-- show hardware statuses
|
-- show hardware statuses
|
||||||
for i = 1, list_length do
|
for i = 1, list_length do
|
||||||
local unit = units[i]
|
local unit = units[i]
|
||||||
|
|
||||||
|
local is_rs = unit.type == RTU_UNIT_TYPE.REDSTONE
|
||||||
|
|
||||||
-- hardware status
|
-- hardware status
|
||||||
local unit_hw = RGBLED{parent=unit_hw_statuses,y=i,label="",colors={colors.red,colors.orange,colors.yellow,colors.green}}
|
local unit_hw = RGBLED{parent=unit_hw_statuses,y=i,label="",colors={colors.red,colors.orange,colors.yellow,colors.green}}
|
||||||
|
|
||||||
unit_hw.register(databus.ps, "unit_hw_" .. i, unit_hw.update)
|
unit_hw.register(databus.ps, "unit_hw_" .. i, unit_hw.update)
|
||||||
|
|
||||||
-- unit name identifier (type + index)
|
-- unit name identifier (type + index)
|
||||||
local function get_name(t) return util.c(UNIT_TYPE_LABELS[t + 1], " ", util.trinary(util.is_int(unit.index), unit.index, "")) end
|
local function get_name()
|
||||||
local name_box = TextBox{parent=unit_hw_statuses,y=i,x=3,text=get_name(unit.type),width=15}
|
if is_rs then
|
||||||
|
local is_local = unit.name == "redstone_local"
|
||||||
|
relay_counter = relay_counter + util.trinary(is_local, 0, 1)
|
||||||
|
return util.c("REDSTONE", util.trinary(is_local, "", " RELAY " .. relay_counter))
|
||||||
|
else
|
||||||
|
return util.c(UNIT_TYPE_LABELS[unit.type + 1], " ", util.trinary(util.is_int(unit.index), unit.index, ""))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
name_box.register(databus.ps, "unit_type_" .. i, function (t) name_box.set_value(get_name(t)) end)
|
local name_box = TextBox{parent=unit_hw_statuses,y=i,x=3,text=get_name(),width=util.trinary(is_rs,24,15)}
|
||||||
|
|
||||||
|
name_box.register(databus.ps, "unit_type_" .. i, function () name_box.set_value(get_name()) end)
|
||||||
|
|
||||||
-- assignment (unit # or facility)
|
-- assignment (unit # or facility)
|
||||||
local for_unit = util.trinary(unit.reactor == 0, "\x1a FACIL ", "\x1a UNIT " .. unit.reactor)
|
if unit.reactor then
|
||||||
TextBox{parent=unit_hw_statuses,y=i,x=term_w-32,text=for_unit,fg_bg=disabled_fg}
|
local for_unit = util.trinary(unit.reactor == 0, "\x1a FACIL ", "\x1a UNIT " .. unit.reactor)
|
||||||
|
TextBox{parent=unit_hw_statuses,y=i,x=term_w-32,text=for_unit,fg_bg=disabled_fg}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
13
rtu/rtu.lua
13
rtu/rtu.lua
@ -338,13 +338,7 @@ function rtu.comms(version, nic, conn_watchdog)
|
|||||||
local unit = units[i]
|
local unit = units[i]
|
||||||
|
|
||||||
if unit.type ~= nil then
|
if unit.type ~= nil then
|
||||||
local advert = { unit.type, unit.index, unit.reactor }
|
insert(advertisement, { unit.type, unit.index, unit.reactor or -1, unit.rs_conns })
|
||||||
|
|
||||||
if unit.type == RTU_UNIT_TYPE.REDSTONE then
|
|
||||||
insert(advert, unit.device)
|
|
||||||
end
|
|
||||||
|
|
||||||
insert(advertisement, advert)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -477,9 +471,10 @@ function rtu.comms(version, nic, conn_watchdog)
|
|||||||
local unit = units[packet.unit_id]
|
local unit = units[packet.unit_id]
|
||||||
local unit_dbg_tag = " (unit " .. packet.unit_id .. ")"
|
local unit_dbg_tag = " (unit " .. packet.unit_id .. ")"
|
||||||
|
|
||||||
if unit.name == "redstone_io" then
|
if unit.type == RTU_UNIT_TYPE.REDSTONE then
|
||||||
-- immediately execute redstone RTU requests
|
-- immediately execute redstone RTU requests
|
||||||
return_code, reply = unit.modbus_io.handle_packet(packet)
|
return_code, reply = unit.modbus_io.handle_packet(packet)
|
||||||
|
|
||||||
if not return_code then
|
if not return_code then
|
||||||
log.warning("requested MODBUS operation failed" .. unit_dbg_tag)
|
log.warning("requested MODBUS operation failed" .. unit_dbg_tag)
|
||||||
end
|
end
|
||||||
@ -496,7 +491,7 @@ function rtu.comms(version, nic, conn_watchdog)
|
|||||||
unit.pkt_queue.push_packet(packet)
|
unit.pkt_queue.push_packet(packet)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
log.warning("cannot perform requested MODBUS operation" .. unit_dbg_tag)
|
log.warning("requested MODBUS operation failed" .. unit_dbg_tag)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
|
|||||||
213
rtu/startup.lua
213
rtu/startup.lua
@ -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.8"
|
local RTU_VERSION = "v1.12.0"
|
||||||
|
|
||||||
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
|
||||||
@ -140,32 +140,36 @@ local function main()
|
|||||||
local rtu_redstone = config.Redstone
|
local rtu_redstone = config.Redstone
|
||||||
local rtu_devices = config.Peripherals
|
local rtu_devices = config.Peripherals
|
||||||
|
|
||||||
|
-- get a string representation of a port interface
|
||||||
|
---@param entry rtu_rs_definition
|
||||||
|
---@return string
|
||||||
|
local function entry_iface_name(entry)
|
||||||
|
return util.trinary(entry.color ~= nil, util.c(entry.side, "/", rsio.color_name(entry.color)), entry.side)
|
||||||
|
end
|
||||||
|
|
||||||
-- configure RTU gateway based on settings file definitions
|
-- configure RTU gateway based on settings file definitions
|
||||||
local function sys_config()
|
local function sys_config()
|
||||||
-- redstone interfaces
|
--#region Redstone Interfaces
|
||||||
local rs_rtus = {} ---@type { rtu: rtu_rs_device, capabilities: IO_PORT[] }[]
|
|
||||||
|
local rs_rtus = {} ---@type { name: string, hw_state: RTU_HW_STATE, rtu: rtu_rs_device, phy: table, banks: rtu_rs_definition[][] }[]
|
||||||
|
local all_conns = { [0] = {}, {}, {}, {}, {} }
|
||||||
|
|
||||||
-- go through redstone definitions list
|
-- go through redstone definitions list
|
||||||
for entry_idx = 1, #rtu_redstone do
|
for entry_idx = 1, #rtu_redstone do
|
||||||
local entry = rtu_redstone[entry_idx]
|
local entry = rtu_redstone[entry_idx]
|
||||||
|
|
||||||
local assignment
|
local assignment
|
||||||
local for_reactor = entry.unit
|
local for_reactor = entry.unit
|
||||||
local iface_name = util.trinary(entry.color ~= nil, util.c(entry.side, "/", rsio.color_name(entry.color)), entry.side)
|
local phy = entry.relay or 0
|
||||||
|
local phy_name = entry.relay or "local"
|
||||||
|
local iface_name = entry_iface_name(entry)
|
||||||
|
|
||||||
if util.is_int(entry.unit) and entry.unit > 0 and entry.unit < 5 then
|
if util.is_int(entry.unit) and entry.unit > 0 and entry.unit < 5 then
|
||||||
---@cast for_reactor integer
|
---@cast for_reactor integer
|
||||||
assignment = "reactor unit " .. entry.unit
|
assignment = "reactor unit " .. entry.unit
|
||||||
if rs_rtus[for_reactor] == nil then
|
|
||||||
log.debug(util.c("sys_config> allocated redstone RTU for reactor unit ", entry.unit))
|
|
||||||
rs_rtus[for_reactor] = { rtu = redstone_rtu.new(), capabilities = {} }
|
|
||||||
end
|
|
||||||
elseif entry.unit == nil then
|
elseif entry.unit == nil then
|
||||||
assignment = "facility"
|
assignment = "facility"
|
||||||
for_reactor = 0
|
for_reactor = 0
|
||||||
if rs_rtus[for_reactor] == nil then
|
|
||||||
log.debug(util.c("sys_config> allocated redstone RTU for the facility"))
|
|
||||||
rs_rtus[for_reactor] = { rtu = redstone_rtu.new(), capabilities = {} }
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
local message = util.c("sys_config> invalid unit assignment at block index #", entry_idx)
|
local message = util.c("sys_config> invalid unit assignment at block index #", entry_idx)
|
||||||
println(message)
|
println(message)
|
||||||
@ -173,14 +177,44 @@ local function main()
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- create the appropriate RTU if it doesn't exist and check relay name validity
|
||||||
|
if entry.relay then
|
||||||
|
if type(entry.relay) ~= "string" then
|
||||||
|
local message = util.c("sys_config> invalid redstone relay '", entry.relay, '"')
|
||||||
|
println(message)
|
||||||
|
log.fatal(message)
|
||||||
|
return false
|
||||||
|
elseif not rs_rtus[entry.relay] then
|
||||||
|
log.debug(util.c("sys_config> allocated relay redstone RTU on interface ", entry.relay))
|
||||||
|
|
||||||
|
local hw_state = RTU_HW_STATE.OK
|
||||||
|
local relay = ppm.get_periph(entry.relay)
|
||||||
|
|
||||||
|
if not relay then
|
||||||
|
hw_state = RTU_HW_STATE.OFFLINE
|
||||||
|
log.warning(util.c("sys_config> redstone relay ", entry.relay, " is not connected"))
|
||||||
|
local _, v_device = ppm.mount_virtual()
|
||||||
|
relay = v_device
|
||||||
|
elseif ppm.get_type(entry.relay) ~= "redstone_relay" then
|
||||||
|
hw_state = RTU_HW_STATE.FAULTED
|
||||||
|
log.warning(util.c("sys_config> redstone relay ", entry.relay, " is not a redstone relay"))
|
||||||
|
end
|
||||||
|
|
||||||
|
rs_rtus[entry.relay] = { name = entry.relay, hw_state = hw_state, rtu = redstone_rtu.new(relay), phy = relay, banks = { [0] = {}, {}, {}, {}, {} } }
|
||||||
|
end
|
||||||
|
elseif rs_rtus[0] == nil then
|
||||||
|
log.debug(util.c("sys_config> allocated local redstone RTU"))
|
||||||
|
rs_rtus[0] = { name = "redstone_local", hw_state = RTU_HW_STATE.OK, rtu = redstone_rtu.new(), phy = rs, banks = { [0] = {}, {}, {}, {}, {} } }
|
||||||
|
end
|
||||||
|
|
||||||
-- verify configuration
|
-- verify configuration
|
||||||
local valid = false
|
local valid = false
|
||||||
if rsio.is_valid_port(entry.port) and rsio.is_valid_side(entry.side) then
|
if rsio.is_valid_port(entry.port) and rsio.is_valid_side(entry.side) then
|
||||||
valid = util.trinary(entry.color == nil, true, rsio.is_color(entry.color))
|
valid = util.trinary(entry.color == nil, true, rsio.is_color(entry.color))
|
||||||
end
|
end
|
||||||
|
|
||||||
local rs_rtu = rs_rtus[for_reactor].rtu
|
local bank = rs_rtus[phy].banks[for_reactor]
|
||||||
local capabilities = rs_rtus[for_reactor].capabilities
|
local conns = all_conns[for_reactor]
|
||||||
|
|
||||||
if not valid then
|
if not valid then
|
||||||
local message = util.c("sys_config> invalid redstone definition at block index #", entry_idx)
|
local message = util.c("sys_config> invalid redstone definition at block index #", entry_idx)
|
||||||
@ -192,73 +226,105 @@ local function main()
|
|||||||
local mode = rsio.get_io_mode(entry.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, entry.port) then
|
if util.table_contains(conns, entry.port) then
|
||||||
local message = util.c("sys_config> skipping duplicate input for port ", rsio.to_string(entry.port), " on side ", iface_name)
|
local message = util.c("sys_config> skipping duplicate input for port ", rsio.to_string(entry.port), " on side ", iface_name, " @ ", phy_name)
|
||||||
println(message)
|
println(message)
|
||||||
log.warning(message)
|
log.warning(message)
|
||||||
else
|
else
|
||||||
rs_rtu.link_di(entry.side, entry.color, entry.invert)
|
table.insert(bank, entry)
|
||||||
end
|
end
|
||||||
elseif mode == rsio.IO_MODE.DIGITAL_OUT then
|
|
||||||
rs_rtu.link_do(entry.side, entry.color, entry.invert)
|
|
||||||
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, entry.port) then
|
if util.table_contains(conns, entry.port) then
|
||||||
local message = util.c("sys_config> skipping duplicate input for port ", rsio.to_string(entry.port), " on side ", iface_name)
|
local message = util.c("sys_config> skipping duplicate input for port ", rsio.to_string(entry.port), " on side ", iface_name, " @ ", phy_name)
|
||||||
println(message)
|
println(message)
|
||||||
log.warning(message)
|
log.warning(message)
|
||||||
else
|
else
|
||||||
rs_rtu.link_ai(entry.side)
|
table.insert(bank, entry)
|
||||||
end
|
end
|
||||||
elseif mode == rsio.IO_MODE.ANALOG_OUT then
|
elseif (mode == rsio.IO_MODE.DIGITAL_OUT) or (mode == rsio.IO_MODE.ANALOG_OUT) then
|
||||||
rs_rtu.link_ao(entry.side)
|
table.insert(bank, entry)
|
||||||
else
|
else
|
||||||
-- should be unreachable code, we already validated ports
|
-- should be unreachable code, we already validated ports
|
||||||
log.error("sys_config> fell through if chain attempting to identify IO mode at block index #" .. entry_idx, true)
|
log.fatal("sys_config> failed to identify IO mode at block index #" .. entry_idx)
|
||||||
println("sys_config> encountered a software error, check logs")
|
println("sys_config> encountered a software error, check logs")
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
table.insert(capabilities, entry.port)
|
table.insert(conns, entry.port)
|
||||||
|
|
||||||
log.debug(util.c("sys_config> linked redstone ", #capabilities, ": ", rsio.to_string(entry.port), " (", iface_name, ") for ", assignment))
|
log.debug(util.c("sys_config> banked redstone ", #conns, ": ", rsio.to_string(entry.port), " (", iface_name, " @ ", phy_name, ") for ", assignment))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- create unit entries for redstone RTUs
|
-- create unit entries for redstone RTUs
|
||||||
for for_reactor, def in pairs(rs_rtus) do
|
for _, def in pairs(rs_rtus) do
|
||||||
---@class rtu_registry_entry
|
local rtu_conns = { [0] = {}, {}, {}, {}, {} }
|
||||||
|
|
||||||
|
-- connect the IO banks
|
||||||
|
for for_reactor = 0, #def.banks do
|
||||||
|
local bank = def.banks[for_reactor]
|
||||||
|
local conns = rtu_conns[for_reactor]
|
||||||
|
local assign = util.trinary(for_reactor > 0, "reactor unit " .. for_reactor, "the facility")
|
||||||
|
|
||||||
|
-- link redstone to the RTU
|
||||||
|
for i = 1, #bank do
|
||||||
|
local conn = bank[i]
|
||||||
|
local phy_name = conn.relay or "local"
|
||||||
|
|
||||||
|
local mode = rsio.get_io_mode(conn.port)
|
||||||
|
if mode == rsio.IO_MODE.DIGITAL_IN then
|
||||||
|
def.rtu.link_di(conn.side, conn.color, conn.invert)
|
||||||
|
elseif mode == rsio.IO_MODE.DIGITAL_OUT then
|
||||||
|
def.rtu.link_do(conn.side, conn.color, conn.invert)
|
||||||
|
elseif mode == rsio.IO_MODE.ANALOG_IN then
|
||||||
|
def.rtu.link_ai(conn.side)
|
||||||
|
elseif mode == rsio.IO_MODE.ANALOG_OUT then
|
||||||
|
def.rtu.link_ao(conn.side)
|
||||||
|
else
|
||||||
|
log.fatal(util.c("sys_config> failed to identify IO mode of ", rsio.to_string(conn.port), " (", entry_iface_name(conn), " @ ", phy_name, ") for ", assign))
|
||||||
|
println("sys_config> encountered a software error, check logs")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(conns, conn.port)
|
||||||
|
|
||||||
|
log.debug(util.c("sys_config> linked redstone ", for_reactor, ".", #conns, ": ", rsio.to_string(conn.port), " (", entry_iface_name(conn), ")", " @ ", phy_name, ") for ", assign))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@type rtu_registry_entry
|
||||||
local unit = {
|
local unit = {
|
||||||
uid = 0, ---@type integer
|
uid = 0,
|
||||||
name = "redstone_io", ---@type string
|
name = def.name,
|
||||||
type = RTU_UNIT_TYPE.REDSTONE, ---@type RTU_UNIT_TYPE
|
type = RTU_UNIT_TYPE.REDSTONE,
|
||||||
index = false, ---@type integer|false
|
index = false,
|
||||||
reactor = for_reactor, ---@type integer
|
reactor = nil,
|
||||||
device = def.capabilities, ---@type IO_PORT[] use device field for redstone ports
|
device = def.phy,
|
||||||
is_multiblock = false, ---@type boolean
|
rs_conns = rtu_conns,
|
||||||
formed = nil, ---@type boolean|nil
|
is_multiblock = false,
|
||||||
hw_state = RTU_HW_STATE.OK, ---@type RTU_HW_STATE
|
formed = nil,
|
||||||
rtu = def.rtu, ---@type rtu_device|rtu_rs_device
|
hw_state = def.hw_state,
|
||||||
|
rtu = def.rtu,
|
||||||
modbus_io = modbus.new(def.rtu, false),
|
modbus_io = modbus.new(def.rtu, false),
|
||||||
pkt_queue = nil, ---@type mqueue|nil
|
pkt_queue = nil,
|
||||||
thread = nil ---@type parallel_thread|nil
|
thread = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
table.insert(units, unit)
|
table.insert(units, unit)
|
||||||
|
|
||||||
local for_message = "facility"
|
local type = util.trinary(def.phy == rs, "redstone", "redstone_relay")
|
||||||
if util.is_int(for_reactor) then
|
|
||||||
for_message = util.c("reactor unit ", for_reactor)
|
|
||||||
end
|
|
||||||
|
|
||||||
log.info(util.c("sys_config> initialized RTU unit #", #units, ": redstone_io (redstone) [1] for ", for_message))
|
log.info(util.c("sys_config> initialized RTU unit #", #units, ": ", unit.name, " (", type, ")"))
|
||||||
|
|
||||||
unit.uid = #units
|
unit.uid = #units
|
||||||
|
|
||||||
databus.tx_unit_hw_status(unit.uid, unit.hw_state)
|
databus.tx_unit_hw_status(unit.uid, unit.hw_state)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- mounted peripherals
|
--#endregion
|
||||||
|
--#region Mounted Peripherals
|
||||||
|
|
||||||
for i = 1, #rtu_devices do
|
for i = 1, #rtu_devices do
|
||||||
local entry = rtu_devices[i] ---@type rtu_peri_definition
|
local entry = rtu_devices[i] ---@type rtu_peri_definition
|
||||||
local name = entry.name
|
local name = entry.name
|
||||||
@ -439,19 +505,20 @@ local function main()
|
|||||||
|
|
||||||
---@class rtu_registry_entry
|
---@class rtu_registry_entry
|
||||||
local rtu_unit = {
|
local rtu_unit = {
|
||||||
uid = 0, ---@type integer
|
uid = 0, ---@type integer RTU unit ID
|
||||||
name = name, ---@type string
|
name = name, ---@type string unit name
|
||||||
type = rtu_type, ---@type RTU_UNIT_TYPE
|
type = rtu_type, ---@type RTU_UNIT_TYPE unit type
|
||||||
index = index or false, ---@type integer|false
|
index = index or false, ---@type integer|false device index
|
||||||
reactor = for_reactor, ---@type integer
|
reactor = for_reactor, ---@type integer|nil unit/facility assignment
|
||||||
device = device, ---@type table peripheral reference
|
device = device, ---@type table peripheral reference
|
||||||
is_multiblock = is_multiblock, ---@type boolean
|
rs_conns = nil, ---@type IO_PORT[][]|nil available redstone connections
|
||||||
formed = formed, ---@type boolean|nil
|
is_multiblock = is_multiblock, ---@type boolean if this is for a multiblock peripheral
|
||||||
hw_state = RTU_HW_STATE.OFFLINE, ---@type RTU_HW_STATE
|
formed = formed, ---@type boolean|nil if this peripheral is currently formed
|
||||||
rtu = rtu_iface, ---@type rtu_device|rtu_rs_device
|
hw_state = RTU_HW_STATE.OFFLINE, ---@type RTU_HW_STATE hardware device status
|
||||||
modbus_io = modbus.new(rtu_iface, true),
|
rtu = rtu_iface, ---@type rtu_device|rtu_rs_device RTU hardware interface
|
||||||
pkt_queue = mqueue.new(), ---@type mqueue|nil
|
modbus_io = modbus.new(rtu_iface, true), ---@type modbus MODBUS interface
|
||||||
thread = nil ---@type parallel_thread|nil
|
pkt_queue = mqueue.new(), ---@type mqueue|nil packet queue
|
||||||
|
thread = nil ---@type parallel_thread|nil associated RTU thread
|
||||||
}
|
}
|
||||||
|
|
||||||
rtu_unit.thread = threads.thread__unit_comms(__shared_memory, rtu_unit)
|
rtu_unit.thread = threads.thread__unit_comms(__shared_memory, rtu_unit)
|
||||||
@ -485,6 +552,8 @@ 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
|
||||||
|
|
||||||
|
--#endregion
|
||||||
|
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -495,17 +564,6 @@ local function main()
|
|||||||
log.debug("boot> running sys_config()")
|
log.debug("boot> running sys_config()")
|
||||||
|
|
||||||
if sys_config() then
|
if sys_config() then
|
||||||
-- start UI
|
|
||||||
local message
|
|
||||||
rtu_state.fp_ok, message = renderer.try_start_ui(units, config.FrontPanelTheme, config.ColorMode)
|
|
||||||
|
|
||||||
if not rtu_state.fp_ok then
|
|
||||||
println_ts(util.c("UI error: ", message))
|
|
||||||
println("startup> running without front panel")
|
|
||||||
log.error(util.c("front panel GUI render failed with error ", message))
|
|
||||||
log.info("startup> running in headless mode without front panel")
|
|
||||||
end
|
|
||||||
|
|
||||||
-- check modem
|
-- check modem
|
||||||
if smem_dev.modem == nil then
|
if smem_dev.modem == nil then
|
||||||
println("startup> wireless modem not found")
|
println("startup> wireless modem not found")
|
||||||
@ -527,6 +585,17 @@ local function main()
|
|||||||
|
|
||||||
databus.tx_hw_spkr_count(#smem_dev.sounders)
|
databus.tx_hw_spkr_count(#smem_dev.sounders)
|
||||||
|
|
||||||
|
-- start UI
|
||||||
|
local message
|
||||||
|
rtu_state.fp_ok, message = renderer.try_start_ui(units, config.FrontPanelTheme, config.ColorMode)
|
||||||
|
|
||||||
|
if not rtu_state.fp_ok then
|
||||||
|
println_ts(util.c("UI error: ", message))
|
||||||
|
println("startup> running without front panel")
|
||||||
|
log.error(util.c("front panel GUI render failed with error ", message))
|
||||||
|
log.info("startup> running in headless mode without front panel")
|
||||||
|
end
|
||||||
|
|
||||||
-- start connection watchdog
|
-- start connection watchdog
|
||||||
smem_sys.conn_watchdog = util.new_watchdog(config.ConnTimeout)
|
smem_sys.conn_watchdog = util.new_watchdog(config.ConnTimeout)
|
||||||
log.debug("startup> conn watchdog started")
|
log.debug("startup> conn watchdog started")
|
||||||
@ -553,7 +622,7 @@ local function main()
|
|||||||
-- run threads
|
-- run threads
|
||||||
parallel.waitForAll(table.unpack(_threads))
|
parallel.waitForAll(table.unpack(_threads))
|
||||||
else
|
else
|
||||||
println("configuration failed, exiting...")
|
println("system initialization failed, exiting...")
|
||||||
end
|
end
|
||||||
|
|
||||||
renderer.close_ui()
|
renderer.close_ui()
|
||||||
|
|||||||
@ -132,6 +132,8 @@ local function handle_unit_mount(smem, println_ts, iface, type, device, unit)
|
|||||||
unit.rtu, faulted = sna_rtu.new(device)
|
unit.rtu, faulted = sna_rtu.new(device)
|
||||||
elseif unit.type == RTU_UNIT_TYPE.ENV_DETECTOR then
|
elseif unit.type == RTU_UNIT_TYPE.ENV_DETECTOR then
|
||||||
unit.rtu, faulted = envd_rtu.new(device)
|
unit.rtu, faulted = envd_rtu.new(device)
|
||||||
|
elseif unit.type == RTU_UNIT_TYPE.REDSTONE then
|
||||||
|
unit.rtu.remount_phy(device)
|
||||||
else
|
else
|
||||||
unknown = true
|
unknown = true
|
||||||
log.error(util.c("failed to identify reconnected RTU unit type (", unit.name, ")"), true)
|
log.error(util.c("failed to identify reconnected RTU unit type (", unit.name, ")"), true)
|
||||||
|
|||||||
@ -17,7 +17,7 @@ local max_distance = nil
|
|||||||
local comms = {}
|
local comms = {}
|
||||||
|
|
||||||
-- protocol/data versions (protocol/data independent changes tracked by util.lua version)
|
-- protocol/data versions (protocol/data independent changes tracked by util.lua version)
|
||||||
comms.version = "3.0.5"
|
comms.version = "3.0.6"
|
||||||
comms.api_version = "0.0.9"
|
comms.api_version = "0.0.9"
|
||||||
|
|
||||||
---@enum PROTOCOL
|
---@enum PROTOCOL
|
||||||
|
|||||||
@ -125,7 +125,7 @@ function types.new_zero_coordinate() return { x = 0, y = 0, z = 0 } end
|
|||||||
---@field type RTU_UNIT_TYPE
|
---@field type RTU_UNIT_TYPE
|
||||||
---@field index integer|false
|
---@field index integer|false
|
||||||
---@field reactor integer
|
---@field reactor integer
|
||||||
---@field rsio IO_PORT[]|nil
|
---@field rs_conns IO_PORT[][]|nil
|
||||||
|
|
||||||
-- create a new reactor database
|
-- create a new reactor database
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
|
|||||||
@ -173,7 +173,7 @@ function facility.new(config)
|
|||||||
self.rtu_list = { self.redstone, self.induction, self.sps, self.tanks, self.envd }
|
self.rtu_list = { self.redstone, self.induction, self.sps, self.tanks, self.envd }
|
||||||
|
|
||||||
-- init redstone RTU I/O controller
|
-- init redstone RTU I/O controller
|
||||||
self.io_ctl = rsctl.new(self.redstone)
|
self.io_ctl = rsctl.new(self.redstone, 0)
|
||||||
|
|
||||||
-- fill blank alarm/tone states
|
-- fill blank alarm/tone states
|
||||||
for _ = 1, 12 do table.insert(self.test_alarm_states, false) end
|
for _ = 1, 12 do table.insert(self.test_alarm_states, false) end
|
||||||
|
|||||||
@ -9,7 +9,8 @@ local rsctl = {}
|
|||||||
-- create a new redstone RTU I/O controller
|
-- create a new redstone RTU I/O controller
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
---@param redstone_rtus redstone_session[] redstone RTU sessions
|
---@param redstone_rtus redstone_session[] redstone RTU sessions
|
||||||
function rsctl.new(redstone_rtus)
|
---@param bank integer I/O bank (unit/facility assignment) to interface with
|
||||||
|
function rsctl.new(redstone_rtus, bank)
|
||||||
---@class rs_controller
|
---@class rs_controller
|
||||||
local public = {}
|
local public = {}
|
||||||
|
|
||||||
@ -18,7 +19,7 @@ function rsctl.new(redstone_rtus)
|
|||||||
---@return boolean
|
---@return boolean
|
||||||
function public.is_connected(port)
|
function public.is_connected(port)
|
||||||
for i = 1, #redstone_rtus do
|
for i = 1, #redstone_rtus do
|
||||||
if redstone_rtus[i].get_db().io[port] ~= nil then return true end
|
if redstone_rtus[i].get_db().io[bank][port] ~= nil then return true end
|
||||||
end
|
end
|
||||||
|
|
||||||
return false
|
return false
|
||||||
@ -29,7 +30,7 @@ function rsctl.new(redstone_rtus)
|
|||||||
---@param value boolean
|
---@param value boolean
|
||||||
function public.digital_write(port, value)
|
function public.digital_write(port, value)
|
||||||
for i = 1, #redstone_rtus do
|
for i = 1, #redstone_rtus do
|
||||||
local io = redstone_rtus[i].get_db().io[port]
|
local io = redstone_rtus[i].get_db().io[bank][port]
|
||||||
if io ~= nil then io.write(value) end
|
if io ~= nil then io.write(value) end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -40,7 +41,7 @@ function rsctl.new(redstone_rtus)
|
|||||||
---@return boolean|nil
|
---@return boolean|nil
|
||||||
function public.digital_read(port)
|
function public.digital_read(port)
|
||||||
for i = 1, #redstone_rtus do
|
for i = 1, #redstone_rtus do
|
||||||
local io = redstone_rtus[i].get_db().io[port]
|
local io = redstone_rtus[i].get_db().io[bank][port]
|
||||||
if io ~= nil then return io.read() --[[@as boolean|nil]] end
|
if io ~= nil then return io.read() --[[@as boolean|nil]] end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -52,7 +53,7 @@ function rsctl.new(redstone_rtus)
|
|||||||
---@param max number maximum value for scaling 0 to 15
|
---@param max number maximum value for scaling 0 to 15
|
||||||
function public.analog_write(port, value, min, max)
|
function public.analog_write(port, value, min, max)
|
||||||
for i = 1, #redstone_rtus do
|
for i = 1, #redstone_rtus do
|
||||||
local io = redstone_rtus[i].get_db().io[port]
|
local io = redstone_rtus[i].get_db().io[bank][port]
|
||||||
if io ~= nil then io.write(rsio.analog_write(value, min, max)) end
|
if io ~= nil then io.write(rsio.analog_write(value, min, max)) end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -93,7 +93,7 @@ function rtu.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout, ad
|
|||||||
type = self.advert[i][1],
|
type = self.advert[i][1],
|
||||||
index = self.advert[i][2],
|
index = self.advert[i][2],
|
||||||
reactor = self.advert[i][3],
|
reactor = self.advert[i][3],
|
||||||
rsio = self.advert[i][4]
|
rs_conns = self.advert[i][4]
|
||||||
}
|
}
|
||||||
|
|
||||||
local u_type = unit_advert.type ---@type RTU_UNIT_TYPE|boolean
|
local u_type = unit_advert.type ---@type RTU_UNIT_TYPE|boolean
|
||||||
@ -104,14 +104,17 @@ function rtu.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout, ad
|
|||||||
advert_validator.assert(util.is_int(unit_advert.index) or (unit_advert.index == false))
|
advert_validator.assert(util.is_int(unit_advert.index) or (unit_advert.index == false))
|
||||||
advert_validator.assert_type_int(unit_advert.reactor)
|
advert_validator.assert_type_int(unit_advert.reactor)
|
||||||
|
|
||||||
if u_type == RTU_UNIT_TYPE.REDSTONE then
|
|
||||||
advert_validator.assert_type_table(unit_advert.rsio)
|
|
||||||
end
|
|
||||||
|
|
||||||
if advert_validator.valid() then
|
if advert_validator.valid() then
|
||||||
if util.is_int(unit_advert.index) then advert_validator.assert_min(unit_advert.index, 1) end
|
if util.is_int(unit_advert.index) then advert_validator.assert_min(unit_advert.index, 1) end
|
||||||
advert_validator.assert_min(unit_advert.reactor, 0)
|
|
||||||
advert_validator.assert_max(unit_advert.reactor, #self.fac_units)
|
if (unit_advert.reactor == -1) or (u_type == RTU_UNIT_TYPE.REDSTONE) then
|
||||||
|
advert_validator.assert((unit_advert.reactor == -1) and (u_type == RTU_UNIT_TYPE.REDSTONE))
|
||||||
|
advert_validator.assert_type_table(unit_advert.rs_conns)
|
||||||
|
else
|
||||||
|
advert_validator.assert_min(unit_advert.reactor, 0)
|
||||||
|
advert_validator.assert_max(unit_advert.reactor, #self.fac_units)
|
||||||
|
end
|
||||||
|
|
||||||
if not advert_validator.valid() then u_type = false end
|
if not advert_validator.valid() then u_type = false end
|
||||||
else
|
else
|
||||||
u_type = false
|
u_type = false
|
||||||
@ -126,15 +129,34 @@ function rtu.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout, ad
|
|||||||
-- validation fail
|
-- validation fail
|
||||||
log.debug(log_tag .. "_handle_advertisement(): advertisement unit validation failure")
|
log.debug(log_tag .. "_handle_advertisement(): advertisement unit validation failure")
|
||||||
else
|
else
|
||||||
if unit_advert.reactor > 0 then
|
if unit_advert.reactor == -1 then
|
||||||
local target_unit = self.fac_units[unit_advert.reactor]
|
-- redstone RTUs can be used in multiple different assignments
|
||||||
|
|
||||||
-- unit RTUs
|
|
||||||
if u_type == RTU_UNIT_TYPE.REDSTONE then
|
if u_type == RTU_UNIT_TYPE.REDSTONE then
|
||||||
-- redstone
|
-- redstone
|
||||||
unit = svrs_redstone.new(id, i, unit_advert, self.modbus_q)
|
unit = svrs_redstone.new(id, i, unit_advert, self.modbus_q)
|
||||||
if type(unit) ~= "nil" then target_unit.add_redstone(unit) end
|
|
||||||
elseif u_type == RTU_UNIT_TYPE.BOILER_VALVE then
|
-- link this to any subsystems this RTU provides connections for
|
||||||
|
if type(unit) ~= "nil" then
|
||||||
|
for assignment, conns in pairs(unit_advert.rs_conns) do
|
||||||
|
if #conns > 0 then
|
||||||
|
if assignment == 0 then
|
||||||
|
facility.add_redstone(unit)
|
||||||
|
elseif assignment > 0 and assignment <= #self.fac_units then
|
||||||
|
self.fac_units[assignment].add_redstone(unit)
|
||||||
|
else
|
||||||
|
log.warning(util.c(log_tag, "_handle_advertisement(): invalid redstone RTU assignment ", assignment))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
log.warning(util.c(log_tag, "_handle_advertisement(): encountered unsupported multi-assignment RTU type ", type_string))
|
||||||
|
end
|
||||||
|
elseif unit_advert.reactor > 0 then
|
||||||
|
local target_unit = self.fac_units[unit_advert.reactor]
|
||||||
|
|
||||||
|
-- unit RTUs
|
||||||
|
if u_type == RTU_UNIT_TYPE.BOILER_VALVE then
|
||||||
-- boiler
|
-- boiler
|
||||||
unit = svrs_boilerv.new(id, i, unit_advert, self.modbus_q)
|
unit = svrs_boilerv.new(id, i, unit_advert, self.modbus_q)
|
||||||
if type(unit) ~= "nil" then target_unit.add_boiler(unit) end
|
if type(unit) ~= "nil" then target_unit.add_boiler(unit) end
|
||||||
|
|||||||
@ -10,7 +10,6 @@ local redstone = {}
|
|||||||
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
|
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
|
||||||
local MODBUS_FCODE = types.MODBUS_FCODE
|
local MODBUS_FCODE = types.MODBUS_FCODE
|
||||||
|
|
||||||
local IO_PORT = rsio.IO
|
|
||||||
local IO_LVL = rsio.IO_LVL
|
local IO_LVL = rsio.IO_LVL
|
||||||
local IO_MODE = rsio.IO_MODE
|
local IO_MODE = rsio.IO_MODE
|
||||||
|
|
||||||
@ -39,6 +38,9 @@ local PERIODICS = {
|
|||||||
OUTPUT_SYNC = 200
|
OUTPUT_SYNC = 200
|
||||||
}
|
}
|
||||||
|
|
||||||
|
-- create a new block of IO banks (facility, then each unit)
|
||||||
|
local function new_io_block() return { [0] = {}, {}, {}, {}, {} } end
|
||||||
|
|
||||||
---@class dig_phy_entry
|
---@class dig_phy_entry
|
||||||
---@field phy IO_LVL actual value
|
---@field phy IO_LVL actual value
|
||||||
---@field req IO_LVL commanded value
|
---@field req IO_LVL commanded value
|
||||||
@ -74,27 +76,27 @@ function redstone.new(session_id, unit_id, advert, out_queue)
|
|||||||
next_ir_req = 0,
|
next_ir_req = 0,
|
||||||
next_hr_sync = 0
|
next_hr_sync = 0
|
||||||
},
|
},
|
||||||
---@class rs_io_list
|
---@class rs_io_map
|
||||||
io_list = {
|
io_map = {
|
||||||
digital_in = {}, ---@type IO_PORT[] discrete inputs
|
digital_in = {}, ---@type { bank: integer, port: IO_PORT }[] discrete inputs
|
||||||
digital_out = {}, ---@type IO_PORT[] coils
|
digital_out = {}, ---@type { bank: integer, port: IO_PORT }[] coils
|
||||||
analog_in = {}, ---@type IO_PORT[] input registers
|
analog_in = {}, ---@type { bank: integer, port: IO_PORT }[] input registers
|
||||||
analog_out = {} ---@type IO_PORT[] holding registers
|
analog_out = {} ---@type { bank: integer, port: IO_PORT }[] holding registers
|
||||||
},
|
},
|
||||||
phy_trans = { coils = -1, hold_regs = -1 },
|
phy_trans = { coils = -1, hold_regs = -1 },
|
||||||
-- last set/read ports (reflecting the current state of the RTU)
|
-- last set/read ports (reflecting the current state of the RTU)
|
||||||
---@class rs_io_states
|
---@class rs_io_states
|
||||||
phy_io = {
|
phy_io = {
|
||||||
digital_in = {}, ---@type dig_phy_entry[] discrete inputs
|
digital_in = new_io_block(), ---@type dig_phy_entry[][] discrete inputs
|
||||||
digital_out = {}, ---@type dig_phy_entry[] coils
|
digital_out = new_io_block(), ---@type dig_phy_entry[][] coils
|
||||||
analog_in = {}, ---@type ana_phy_entry[] input registers
|
analog_in = new_io_block(), ---@type ana_phy_entry[][] input registers
|
||||||
analog_out = {} ---@type ana_phy_entry[] holding registers
|
analog_out = new_io_block() ---@type ana_phy_entry[][] holding registers
|
||||||
},
|
},
|
||||||
---@class redstone_session_db
|
---@class redstone_session_db
|
||||||
db = {
|
db = {
|
||||||
-- read/write functions for connected I/O
|
-- read/write functions for connected I/O
|
||||||
---@type (rs_db_dig_io|rs_db_ana_io)[]
|
---@type (rs_db_dig_io|rs_db_ana_io)[][]
|
||||||
io = {}
|
io = new_io_block()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,93 +105,91 @@ function redstone.new(session_id, unit_id, advert, out_queue)
|
|||||||
|
|
||||||
-- INITIALIZE --
|
-- INITIALIZE --
|
||||||
|
|
||||||
-- create all ports as disconnected
|
|
||||||
for _ = 1, #IO_PORT do
|
|
||||||
table.insert(self.db, IO_LVL.DISCONNECT)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- setup I/O
|
-- setup I/O
|
||||||
for i = 1, #advert.rsio do
|
for bank = 0, 4 do
|
||||||
local port = advert.rsio[i]
|
for i = 1, #advert.rs_conns[bank] do
|
||||||
|
local port = advert.rs_conns[bank][i]
|
||||||
|
|
||||||
if rsio.is_valid_port(port) then
|
if rsio.is_valid_port(port) then
|
||||||
local mode = rsio.get_io_mode(port)
|
local mode = rsio.get_io_mode(port)
|
||||||
|
local io_entry = { bank = bank, port = port }
|
||||||
|
|
||||||
if mode == IO_MODE.DIGITAL_IN then
|
if mode == IO_MODE.DIGITAL_IN then
|
||||||
self.has_di = true
|
self.has_di = true
|
||||||
table.insert(self.io_list.digital_in, port)
|
table.insert(self.io_map.digital_in, io_entry)
|
||||||
|
|
||||||
self.phy_io.digital_in[port] = { phy = IO_LVL.FLOATING, req = IO_LVL.FLOATING }
|
self.phy_io.digital_in[bank][port] = { phy = IO_LVL.FLOATING, req = IO_LVL.FLOATING }
|
||||||
|
|
||||||
---@class rs_db_dig_io
|
---@class rs_db_dig_io
|
||||||
local io_f = {
|
local io_f = {
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
read = function () return rsio.digital_is_active(port, self.phy_io.digital_in[port].phy) end,
|
read = function () return rsio.digital_is_active(port, self.phy_io.digital_in[bank][port].phy) end,
|
||||||
write = function () end
|
write = function () end
|
||||||
}
|
}
|
||||||
|
|
||||||
self.db.io[port] = io_f
|
self.db.io[bank][port] = io_f
|
||||||
elseif mode == IO_MODE.DIGITAL_OUT then
|
elseif mode == IO_MODE.DIGITAL_OUT then
|
||||||
self.has_do = true
|
self.has_do = true
|
||||||
table.insert(self.io_list.digital_out, port)
|
table.insert(self.io_map.digital_out, io_entry)
|
||||||
|
|
||||||
self.phy_io.digital_out[port] = { phy = IO_LVL.FLOATING, req = IO_LVL.FLOATING }
|
self.phy_io.digital_out[bank][port] = { phy = IO_LVL.FLOATING, req = IO_LVL.FLOATING }
|
||||||
|
|
||||||
---@class rs_db_dig_io
|
---@class rs_db_dig_io
|
||||||
local io_f = {
|
local io_f = {
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
read = function () return rsio.digital_is_active(port, self.phy_io.digital_out[port].phy) end,
|
read = function () return rsio.digital_is_active(port, self.phy_io.digital_out[bank][port].phy) end,
|
||||||
---@param active boolean
|
---@param active boolean
|
||||||
write = function (active)
|
write = function (active)
|
||||||
local level = rsio.digital_write_active(port, active)
|
local level = rsio.digital_write_active(port, active)
|
||||||
if level ~= nil then self.phy_io.digital_out[port].req = level end
|
if level ~= nil then self.phy_io.digital_out[bank][port].req = level end
|
||||||
end
|
|
||||||
}
|
|
||||||
|
|
||||||
self.db.io[port] = io_f
|
|
||||||
elseif mode == IO_MODE.ANALOG_IN then
|
|
||||||
self.has_ai = true
|
|
||||||
table.insert(self.io_list.analog_in, port)
|
|
||||||
|
|
||||||
self.phy_io.analog_in[port] = { phy = 0, req = 0 }
|
|
||||||
|
|
||||||
---@class rs_db_ana_io
|
|
||||||
local io_f = {
|
|
||||||
---@nodiscard
|
|
||||||
---@return integer
|
|
||||||
read = function () return self.phy_io.analog_in[port].phy end,
|
|
||||||
write = function () end
|
|
||||||
}
|
|
||||||
|
|
||||||
self.db.io[port] = io_f
|
|
||||||
elseif mode == IO_MODE.ANALOG_OUT then
|
|
||||||
self.has_ao = true
|
|
||||||
table.insert(self.io_list.analog_out, port)
|
|
||||||
|
|
||||||
self.phy_io.analog_out[port] = { phy = 0, req = 0 }
|
|
||||||
|
|
||||||
---@class rs_db_ana_io
|
|
||||||
local io_f = {
|
|
||||||
---@nodiscard
|
|
||||||
---@return integer
|
|
||||||
read = function () return self.phy_io.analog_out[port].phy end,
|
|
||||||
---@param value integer
|
|
||||||
write = function (value)
|
|
||||||
if value >= 0 and value <= 15 then
|
|
||||||
self.phy_io.analog_out[port].req = value
|
|
||||||
end
|
end
|
||||||
end
|
}
|
||||||
}
|
|
||||||
|
|
||||||
self.db.io[port] = io_f
|
self.db.io[bank][port] = io_f
|
||||||
|
elseif mode == IO_MODE.ANALOG_IN then
|
||||||
|
self.has_ai = true
|
||||||
|
table.insert(self.io_map.analog_in, io_entry)
|
||||||
|
|
||||||
|
self.phy_io.analog_in[bank][port] = { phy = 0, req = 0 }
|
||||||
|
|
||||||
|
---@class rs_db_ana_io
|
||||||
|
local io_f = {
|
||||||
|
---@nodiscard
|
||||||
|
---@return integer
|
||||||
|
read = function () return self.phy_io.analog_in[bank][port].phy end,
|
||||||
|
write = function () end
|
||||||
|
}
|
||||||
|
|
||||||
|
self.db.io[bank][port] = io_f
|
||||||
|
elseif mode == IO_MODE.ANALOG_OUT then
|
||||||
|
self.has_ao = true
|
||||||
|
table.insert(self.io_map.analog_out, io_entry)
|
||||||
|
|
||||||
|
self.phy_io.analog_out[bank][port] = { phy = 0, req = 0 }
|
||||||
|
|
||||||
|
---@class rs_db_ana_io
|
||||||
|
local io_f = {
|
||||||
|
---@nodiscard
|
||||||
|
---@return integer
|
||||||
|
read = function () return self.phy_io.analog_out[bank][port].phy end,
|
||||||
|
---@param value integer
|
||||||
|
write = function (value)
|
||||||
|
if value >= 0 and value <= 15 then
|
||||||
|
self.phy_io.analog_out[bank][port].req = value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
self.db.io[bank][port] = io_f
|
||||||
|
else
|
||||||
|
-- should be unreachable code, we already validated ports
|
||||||
|
log.error(util.c(log_tag, "failed to identify advertisement port IO mode (", bank, ":", port, ")"), true)
|
||||||
|
return nil
|
||||||
|
end
|
||||||
else
|
else
|
||||||
-- should be unreachable code, we already validated ports
|
log.error(util.c(log_tag, "invalid advertisement port (", bank, ":", port, ")"), true)
|
||||||
log.error(util.c(log_tag, "failed to identify advertisement port IO mode (", port, ")"), true)
|
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
else
|
|
||||||
log.error(util.c(log_tag, "invalid advertisement port (", port, ")"), true)
|
|
||||||
return nil
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -197,12 +197,12 @@ function redstone.new(session_id, unit_id, advert, out_queue)
|
|||||||
|
|
||||||
-- query discrete inputs
|
-- query discrete inputs
|
||||||
local function _request_discrete_inputs()
|
local function _request_discrete_inputs()
|
||||||
self.session.send_request(TXN_TYPES.DI_READ, MODBUS_FCODE.READ_DISCRETE_INPUTS, { 1, #self.io_list.digital_in })
|
self.session.send_request(TXN_TYPES.DI_READ, MODBUS_FCODE.READ_DISCRETE_INPUTS, { 1, #self.io_map.digital_in })
|
||||||
end
|
end
|
||||||
|
|
||||||
-- query input registers
|
-- query input registers
|
||||||
local function _request_input_registers()
|
local function _request_input_registers()
|
||||||
self.session.send_request(TXN_TYPES.INPUT_REG_READ, MODBUS_FCODE.READ_INPUT_REGS, { 1, #self.io_list.analog_in })
|
self.session.send_request(TXN_TYPES.INPUT_REG_READ, MODBUS_FCODE.READ_INPUT_REGS, { 1, #self.io_map.analog_in })
|
||||||
end
|
end
|
||||||
|
|
||||||
-- write all coil outputs
|
-- write all coil outputs
|
||||||
@ -210,9 +210,9 @@ function redstone.new(session_id, unit_id, advert, out_queue)
|
|||||||
local params = { 1 }
|
local params = { 1 }
|
||||||
|
|
||||||
local outputs = self.phy_io.digital_out
|
local outputs = self.phy_io.digital_out
|
||||||
for i = 1, #self.io_list.digital_out do
|
for i = 1, #self.io_map.digital_out do
|
||||||
local port = self.io_list.digital_out[i]
|
local entry = self.io_map.digital_out[i]
|
||||||
table.insert(params, outputs[port].req)
|
table.insert(params, outputs[entry.bank][entry.port].req)
|
||||||
end
|
end
|
||||||
|
|
||||||
self.phy_trans.coils = self.session.send_request(TXN_TYPES.COIL_WRITE, MODBUS_FCODE.WRITE_MUL_COILS, params)
|
self.phy_trans.coils = self.session.send_request(TXN_TYPES.COIL_WRITE, MODBUS_FCODE.WRITE_MUL_COILS, params)
|
||||||
@ -220,7 +220,7 @@ function redstone.new(session_id, unit_id, advert, out_queue)
|
|||||||
|
|
||||||
-- read all coil outputs
|
-- read all coil outputs
|
||||||
local function _read_coils()
|
local function _read_coils()
|
||||||
self.session.send_request(TXN_TYPES.COIL_READ, MODBUS_FCODE.READ_COILS, { 1, #self.io_list.digital_out })
|
self.session.send_request(TXN_TYPES.COIL_READ, MODBUS_FCODE.READ_COILS, { 1, #self.io_map.digital_out })
|
||||||
end
|
end
|
||||||
|
|
||||||
-- write all holding register outputs
|
-- write all holding register outputs
|
||||||
@ -228,9 +228,9 @@ function redstone.new(session_id, unit_id, advert, out_queue)
|
|||||||
local params = { 1 }
|
local params = { 1 }
|
||||||
|
|
||||||
local outputs = self.phy_io.analog_out
|
local outputs = self.phy_io.analog_out
|
||||||
for i = 1, #self.io_list.analog_out do
|
for i = 1, #self.io_map.analog_out do
|
||||||
local port = self.io_list.analog_out[i]
|
local entry = self.io_map.analog_out[i]
|
||||||
table.insert(params, outputs[port].req)
|
table.insert(params, outputs[entry.bank][entry.port].req)
|
||||||
end
|
end
|
||||||
|
|
||||||
self.phy_trans.hold_regs = self.session.send_request(TXN_TYPES.HOLD_REG_WRITE, MODBUS_FCODE.WRITE_MUL_HOLD_REGS, params)
|
self.phy_trans.hold_regs = self.session.send_request(TXN_TYPES.HOLD_REG_WRITE, MODBUS_FCODE.WRITE_MUL_HOLD_REGS, params)
|
||||||
@ -238,7 +238,7 @@ function redstone.new(session_id, unit_id, advert, out_queue)
|
|||||||
|
|
||||||
-- read all holding register outputs
|
-- read all holding register outputs
|
||||||
local function _read_holding_registers()
|
local function _read_holding_registers()
|
||||||
self.session.send_request(TXN_TYPES.HOLD_REG_READ, MODBUS_FCODE.READ_MUL_HOLD_REGS, { 1, #self.io_list.analog_out })
|
self.session.send_request(TXN_TYPES.HOLD_REG_READ, MODBUS_FCODE.READ_MUL_HOLD_REGS, { 1, #self.io_map.analog_out })
|
||||||
end
|
end
|
||||||
|
|
||||||
-- PUBLIC FUNCTIONS --
|
-- PUBLIC FUNCTIONS --
|
||||||
@ -259,24 +259,24 @@ function redstone.new(session_id, unit_id, advert, out_queue)
|
|||||||
end
|
end
|
||||||
elseif txn_type == TXN_TYPES.DI_READ then
|
elseif txn_type == TXN_TYPES.DI_READ then
|
||||||
-- discrete input read response
|
-- discrete input read response
|
||||||
if m_pkt.length == #self.io_list.digital_in then
|
if m_pkt.length == #self.io_map.digital_in then
|
||||||
for i = 1, m_pkt.length do
|
for i = 1, m_pkt.length do
|
||||||
local port = self.io_list.digital_in[i]
|
local entry = self.io_map.digital_in[i]
|
||||||
local value = m_pkt.data[i]
|
local value = m_pkt.data[i]
|
||||||
|
|
||||||
self.phy_io.digital_in[port].phy = value
|
self.phy_io.digital_in[entry.bank][entry.port].phy = value
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")")
|
log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")")
|
||||||
end
|
end
|
||||||
elseif txn_type == TXN_TYPES.INPUT_REG_READ then
|
elseif txn_type == TXN_TYPES.INPUT_REG_READ then
|
||||||
-- input register read response
|
-- input register read response
|
||||||
if m_pkt.length == #self.io_list.analog_in then
|
if m_pkt.length == #self.io_map.analog_in then
|
||||||
for i = 1, m_pkt.length do
|
for i = 1, m_pkt.length do
|
||||||
local port = self.io_list.analog_in[i]
|
local entry = self.io_map.analog_in[i]
|
||||||
local value = m_pkt.data[i]
|
local value = m_pkt.data[i]
|
||||||
|
|
||||||
self.phy_io.analog_in[port].phy = value
|
self.phy_io.analog_in[entry.bank][entry.port].phy = value
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")")
|
log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")")
|
||||||
@ -288,15 +288,14 @@ function redstone.new(session_id, unit_id, advert, out_queue)
|
|||||||
-- update phy I/O table
|
-- update phy I/O table
|
||||||
-- if there are multiple outputs for the same port, they will overwrite eachother (but *should* be identical)
|
-- if there are multiple outputs for the same port, they will overwrite eachother (but *should* be identical)
|
||||||
-- given these are redstone outputs, if one worked they all should have, so no additional verification will be done
|
-- given these are redstone outputs, if one worked they all should have, so no additional verification will be done
|
||||||
if m_pkt.length == #self.io_list.digital_out then
|
if m_pkt.length == #self.io_map.digital_out then
|
||||||
for i = 1, m_pkt.length do
|
for i = 1, m_pkt.length do
|
||||||
local port = self.io_list.digital_out[i]
|
local entry = self.io_map.digital_out[i]
|
||||||
|
local state = self.phy_io.digital_out[entry.bank][entry.port]
|
||||||
local value = m_pkt.data[i]
|
local value = m_pkt.data[i]
|
||||||
|
|
||||||
self.phy_io.digital_out[port].phy = value
|
state.phy = value
|
||||||
if self.phy_io.digital_out[port].req == IO_LVL.FLOATING then
|
if state.req == IO_LVL.FLOATING then state.req = value end
|
||||||
self.phy_io.digital_out[port].req = value
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
self.phy_trans.coils = TXN_READY
|
self.phy_trans.coils = TXN_READY
|
||||||
@ -310,12 +309,12 @@ function redstone.new(session_id, unit_id, advert, out_queue)
|
|||||||
-- update phy I/O table
|
-- update phy I/O table
|
||||||
-- if there are multiple outputs for the same port, they will overwrite eachother (but *should* be identical)
|
-- if there are multiple outputs for the same port, they will overwrite eachother (but *should* be identical)
|
||||||
-- given these are redstone outputs, if one worked they all should have, so no additional verification will be done
|
-- given these are redstone outputs, if one worked they all should have, so no additional verification will be done
|
||||||
if m_pkt.length == #self.io_list.analog_out then
|
if m_pkt.length == #self.io_map.analog_out then
|
||||||
for i = 1, m_pkt.length do
|
for i = 1, m_pkt.length do
|
||||||
local port = self.io_list.analog_out[i]
|
local entry = self.io_map.analog_out[i]
|
||||||
local value = m_pkt.data[i]
|
local value = m_pkt.data[i]
|
||||||
|
|
||||||
self.phy_io.analog_out[port].phy = value
|
self.phy_io.analog_out[entry.bank][entry.port].phy = value
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")")
|
log.debug(log_tag .. "MODBUS transaction reply length mismatch (" .. TXN_TAGS[txn_type] .. ")")
|
||||||
@ -343,8 +342,17 @@ function redstone.new(session_id, unit_id, advert, out_queue)
|
|||||||
-- sync digital outputs
|
-- sync digital outputs
|
||||||
if self.has_do then
|
if self.has_do then
|
||||||
if (self.periodics.next_cl_sync <= time_now) and (self.phy_trans.coils == TXN_READY) then
|
if (self.periodics.next_cl_sync <= time_now) and (self.phy_trans.coils == TXN_READY) then
|
||||||
for _, entry in pairs(self.phy_io.digital_out) do
|
for bank = 0, 4 do
|
||||||
if entry.phy ~= entry.req then
|
local changed = false
|
||||||
|
|
||||||
|
for _, entry in pairs(self.phy_io.digital_out[bank]) do
|
||||||
|
if entry.phy ~= entry.req then
|
||||||
|
changed = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if changed then
|
||||||
_write_coils()
|
_write_coils()
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
@ -365,8 +373,17 @@ function redstone.new(session_id, unit_id, advert, out_queue)
|
|||||||
-- sync analog outputs
|
-- sync analog outputs
|
||||||
if self.has_ao then
|
if self.has_ao then
|
||||||
if (self.periodics.next_hr_sync <= time_now) and (self.phy_trans.hold_regs == TXN_READY) then
|
if (self.periodics.next_hr_sync <= time_now) and (self.phy_trans.hold_regs == TXN_READY) then
|
||||||
for _, entry in pairs(self.phy_io.analog_out) do
|
for bank = 0, 4 do
|
||||||
if entry.phy ~= entry.req then
|
local changed = false
|
||||||
|
|
||||||
|
for _, entry in pairs(self.phy_io.analog_out[bank]) do
|
||||||
|
if entry.phy ~= entry.req then
|
||||||
|
changed = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if changed then
|
||||||
_write_holding_registers()
|
_write_holding_registers()
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
@ -379,9 +396,10 @@ function redstone.new(session_id, unit_id, advert, out_queue)
|
|||||||
self.session.post_update()
|
self.session.post_update()
|
||||||
end
|
end
|
||||||
|
|
||||||
-- invalidate build cache
|
-- force a re-read of cached outputs
|
||||||
function public.invalidate_cache()
|
function public.invalidate_cache()
|
||||||
-- no build cache for this device
|
_read_coils()
|
||||||
|
_read_holding_registers()
|
||||||
end
|
end
|
||||||
|
|
||||||
-- get the unit session database
|
-- get the unit session database
|
||||||
|
|||||||
@ -23,7 +23,7 @@ local supervisor = require("supervisor.supervisor")
|
|||||||
|
|
||||||
local svsessions = require("supervisor.session.svsessions")
|
local svsessions = require("supervisor.session.svsessions")
|
||||||
|
|
||||||
local SUPERVISOR_VERSION = "v1.6.9"
|
local SUPERVISOR_VERSION = "v1.7.0"
|
||||||
|
|
||||||
local println = util.println
|
local println = util.println
|
||||||
local println_ts = util.println_ts
|
local println_ts = util.println_ts
|
||||||
|
|||||||
@ -244,7 +244,7 @@ function unit.new(reactor_id, num_boilers, num_turbines, ext_idle, aux_coolant)
|
|||||||
self.rtu_list = { self.redstone, self.boilers, self.turbines, self.tanks, self.snas, self.envd }
|
self.rtu_list = { self.redstone, self.boilers, self.turbines, self.tanks, self.snas, self.envd }
|
||||||
|
|
||||||
-- init redstone RTU I/O controller
|
-- init redstone RTU I/O controller
|
||||||
self.io_ctl = rsctl.new(self.redstone)
|
self.io_ctl = rsctl.new(self.redstone, reactor_id)
|
||||||
|
|
||||||
-- init boiler table fields
|
-- init boiler table fields
|
||||||
for _ = 1, num_boilers do
|
for _ = 1, num_boilers do
|
||||||
|
|||||||
@ -173,12 +173,8 @@ function logic.update_annunciator(self)
|
|||||||
|
|
||||||
annunc.EmergencyCoolant = 1
|
annunc.EmergencyCoolant = 1
|
||||||
|
|
||||||
for i = 1, #self.redstone do
|
if self.io_ctl.is_connected(IO.U_EMER_COOL) then
|
||||||
local io = self.redstone[i].get_db().io[IO.U_EMER_COOL]
|
annunc.EmergencyCoolant = util.trinary(self.io_ctl.digital_read(IO.U_EMER_COOL), 3, 2)
|
||||||
if io ~= nil then
|
|
||||||
annunc.EmergencyCoolant = util.trinary(io.read(), 3, 2)
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--#endregion
|
--#endregion
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user