diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 1ce7682..5cb8f72 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -134,7 +134,7 @@ local function main() -- get the configured modem if smem_dev.modem_wired then - smem_dev.modem = ppm.get_wired_modem(smem_dev.modem_iface) + smem_dev.modem = ppm.get_modem(smem_dev.modem_iface) else smem_dev.modem = ppm.get_wireless_modem() end -- initial state evaluation diff --git a/rtu/backplane.lua b/rtu/backplane.lua index 78b6ac2..c79f02d 100644 --- a/rtu/backplane.lua +++ b/rtu/backplane.lua @@ -41,7 +41,7 @@ function backplane.init(config, __shared_memory) -- init wired NIC if _bp.lan_en then - local modem = ppm.get_wired_modem(_bp.lan_iface) + local modem = ppm.get_modem(_bp.lan_iface) if modem then _bp.wd_nic = network.nic(modem) @@ -62,7 +62,7 @@ function backplane.init(config, __shared_memory) -- grab the preferred active NIC if _bp.wlan_pref then _bp.wl_act = true - _bp.act_nic = _bp.wl_nics[1] + _bp.act_nic = _bp.wl_nic else _bp.wl_act = false _bp.act_nic = _bp.wd_nic @@ -110,11 +110,11 @@ function backplane.detach(type, device, iface) local was_wl = wl_nic and wl_nic.is_modem(device) if wd_nic and was_wd then - log.info("BKPLN: WIRED PHY_DOWN " .. iface) wd_nic.disconnect() + log.info("BKPLN: WIRED PHY_DOWN " .. iface) elseif wl_nic and was_wl then - log.info("BKPLN: WIRELESS PHY_DOWN " .. iface) wl_nic.disconnect() + log.info("BKPLN: WIRELESS PHY_DOWN " .. iface) end -- we only care if this is our active comms modem diff --git a/rtu/config/system.lua b/rtu/config/system.lua index 27beef7..0f7a798 100644 --- a/rtu/config/system.lua +++ b/rtu/config/system.lua @@ -90,22 +90,73 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style) local net_c_1 = Div{parent=net_cfg,x=2,y=4,width=49} local net_c_2 = Div{parent=net_cfg,x=2,y=4,width=49} local net_c_3 = Div{parent=net_cfg,x=2,y=4,width=49} + local net_c_4 = Div{parent=net_cfg,x=2,y=4,width=49} - local net_pane = MultiPane{parent=net_cfg,x=1,y=4,panes={net_c_1,net_c_2,net_c_3}} + local net_pane = MultiPane{parent=net_cfg,x=1,y=4,panes={net_c_1,net_c_2,net_c_3,net_c_4}} TextBox{parent=net_cfg,x=1,y=2,text=" Network Configuration",fg_bg=cpair(colors.black,colors.lightBlue)} - TextBox{parent=net_c_1,x=1,y=1,text="Please set the network channels below."} - TextBox{parent=net_c_1,x=1,y=3,height=4,text="Each of the 5 uniquely named channels, including the 2 below, must be the same for each device in this SCADA network. For multiplayer servers, it is recommended to not use the default channels.",fg_bg=g_lg_fg_bg} + TextBox{parent=net_c_1,x=1,y=1,text="Please select the network interface(s)."} + TextBox{parent=net_c_1,x=41,y=1,text="new!",fg_bg=cpair(colors.red,colors._INHERIT)} ---@todo remove NEW tag on next revision - TextBox{parent=net_c_1,x=1,y=8,text="Supervisor Channel"} - local svr_chan = NumberField{parent=net_c_1,x=1,y=9,width=7,default=ini_cfg.SVR_Channel,min=1,max=65535,fg_bg=bw_fg_bg} - TextBox{parent=net_c_1,x=9,y=9,height=4,text="[SVR_CHANNEL]",fg_bg=g_lg_fg_bg} - TextBox{parent=net_c_1,x=1,y=11,text="RTU Channel"} - local rtu_chan = NumberField{parent=net_c_1,x=1,y=12,width=7,default=ini_cfg.RTU_Channel,min=1,max=65535,fg_bg=bw_fg_bg} - TextBox{parent=net_c_1,x=9,y=12,height=4,text="[RTU_CHANNEL]",fg_bg=g_lg_fg_bg} + local wl_pref = Checkbox{parent=net_c_1,x=30,y=3,label="Prefer Wireless",default=ini_cfg.PreferWireless,box_fg_bg=cpair(colors.lightBlue,colors.black),disable_fg_bg=g_lg_fg_bg,callback=function()end} - local chan_err = TextBox{parent=net_c_1,x=8,y=14,width=35,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} + local function dis_pref(value) + if not value then + wl_pref.set_value(false) + wl_pref.disable() + else wl_pref.enable() end + end + + dis_pref(ini_cfg.WirelessModem) + + local function on_wired_change(value) + tool_ctl.gen_modem_list(value) + end + + local wireless = Checkbox{parent=net_c_1,x=1,y=3,label="Wireless/Ender Modem",default=ini_cfg.WirelessModem,box_fg_bg=cpair(colors.lightBlue,colors.black),callback=dis_pref} + local wired = Checkbox{parent=net_c_1,x=1,y=5,label="Wired Modem",default=ini_cfg.WiredModem~=false,box_fg_bg=cpair(colors.lightBlue,colors.black),callback=on_wired_change} + TextBox{parent=net_c_1,x=3,y=6,text="MUST ONLY connect to SCADA computers",fg_bg=cpair(colors.red,colors._INHERIT)} + TextBox{parent=net_c_1,x=3,y=7,text="connecting to peripherals will cause problems",fg_bg=g_lg_fg_bg} + local modem_list = ListBox{parent=net_c_1,x=1,y=8,height=5,width=49,scroll_height=100,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)} + + local modem_err = TextBox{parent=net_c_1,x=8,y=14,width=35,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} + + local function submit_interfaces() + tmp_cfg.WirelessModem = wireless.get_value() + tmp_cfg.PreferWireless = wl_pref.get_value() + + if not wired.get_value() then + tmp_cfg.WiredModem = false + tool_ctl.gen_modem_list() + end + + if not (wired.get_value() or wireless.get_value()) then + modem_err.set_value("Please select a modem type.") + modem_err.show() + elseif wired.get_value() and type(tmp_cfg.WiredModem) ~= "string" then + modem_err.set_value("Please select a wired modem.") + modem_err.show() + else + net_pane.set_value(2) + modem_err.hide(true) + end + end + + PushButton{parent=net_c_1,x=1,y=14,text="\x1b Back",callback=function()main_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=net_c_1,x=44,y=14,text="Next \x1a",callback=submit_interfaces,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + + TextBox{parent=net_c_2,x=1,y=1,text="Please set the network channels below."} + TextBox{parent=net_c_2,x=1,y=3,height=4,text="Each of the 5 uniquely named channels, including the 2 below, must be the same for each device in this SCADA network. For multiplayer servers, it is recommended to not use the default channels.",fg_bg=g_lg_fg_bg} + + TextBox{parent=net_c_2,x=1,y=8,text="Supervisor Channel"} + local svr_chan = NumberField{parent=net_c_2,x=1,y=9,width=7,default=ini_cfg.SVR_Channel,min=1,max=65535,fg_bg=bw_fg_bg} + TextBox{parent=net_c_2,x=9,y=9,height=4,text="[SVR_CHANNEL]",fg_bg=g_lg_fg_bg} + TextBox{parent=net_c_2,x=1,y=11,text="RTU Channel"} + local rtu_chan = NumberField{parent=net_c_2,x=1,y=12,width=7,default=ini_cfg.RTU_Channel,min=1,max=65535,fg_bg=bw_fg_bg} + TextBox{parent=net_c_2,x=9,y=12,height=4,text="[RTU_CHANNEL]",fg_bg=g_lg_fg_bg} + + local chan_err = TextBox{parent=net_c_2,x=8,y=14,width=35,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} local function submit_channels() local svr_c = tonumber(svr_chan.get_value()) @@ -113,7 +164,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style) if svr_c ~= nil and rtu_c ~= nil then tmp_cfg.SVR_Channel = svr_c tmp_cfg.RTU_Channel = rtu_c - net_pane.set_value(2) + net_pane.set_value(3) chan_err.hide(true) elseif svr_c == nil then chan_err.set_value("Please set the supervisor channel.") @@ -124,19 +175,19 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style) end end - PushButton{parent=net_c_1,x=1,y=14,text="\x1b Back",callback=function()main_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} - PushButton{parent=net_c_1,x=44,y=14,text="Next \x1a",callback=submit_channels,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=net_c_2,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=net_c_2,x=44,y=14,text="Next \x1a",callback=submit_channels,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} - TextBox{parent=net_c_2,x=1,y=1,text="Connection Timeout"} - local timeout = NumberField{parent=net_c_2,x=1,y=2,width=7,default=ini_cfg.ConnTimeout,min=2,max=25,max_chars=6,max_frac_digits=2,allow_decimal=true,fg_bg=bw_fg_bg} - TextBox{parent=net_c_2,x=9,y=2,height=2,text="seconds (default 5)",fg_bg=g_lg_fg_bg} - TextBox{parent=net_c_2,x=1,y=3,height=4,text="You generally do not want or need to modify this. On slow servers, you can increase this to make the system wait longer before assuming a disconnection.",fg_bg=g_lg_fg_bg} + TextBox{parent=net_c_3,x=1,y=1,text="Connection Timeout"} + local timeout = NumberField{parent=net_c_3,x=1,y=2,width=7,default=ini_cfg.ConnTimeout,min=2,max=25,max_chars=6,max_frac_digits=2,allow_decimal=true,fg_bg=bw_fg_bg} + TextBox{parent=net_c_3,x=9,y=2,height=2,text="seconds (default 5)",fg_bg=g_lg_fg_bg} + TextBox{parent=net_c_3,x=1,y=3,height=4,text="You generally do not want or need to modify this. On slow servers, you can increase this to make the system wait longer before assuming a disconnection.",fg_bg=g_lg_fg_bg} - TextBox{parent=net_c_2,x=1,y=8,text="Trusted Range"} - local range = NumberField{parent=net_c_2,x=1,y=9,width=10,default=ini_cfg.TrustedRange,min=0,max_chars=20,allow_decimal=true,fg_bg=bw_fg_bg} - TextBox{parent=net_c_2,x=1,y=10,height=4,text="Setting this to a value larger than 0 prevents connections with devices that many meters (blocks) away in any direction.",fg_bg=g_lg_fg_bg} + TextBox{parent=net_c_3,x=1,y=8,text="Trusted Range (Wireless Only)"} + local range = NumberField{parent=net_c_3,x=1,y=9,width=10,default=ini_cfg.TrustedRange,min=0,max_chars=20,allow_decimal=true,fg_bg=bw_fg_bg} + TextBox{parent=net_c_3,x=1,y=10,height=4,text="Setting this to a value larger than 0 prevents connections with devices that many meters (blocks) away in any direction.",fg_bg=g_lg_fg_bg} - local p2_err = TextBox{parent=net_c_2,x=8,y=14,width=35,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} + local p2_err = TextBox{parent=net_c_3,x=8,y=14,width=35,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} local function submit_ct_tr() local timeout_val = tonumber(timeout.get_value()) @@ -144,7 +195,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style) if timeout_val ~= nil and range_val ~= nil then tmp_cfg.ConnTimeout = timeout_val tmp_cfg.TrustedRange = range_val - net_pane.set_value(3) + net_pane.set_value(4) p2_err.hide(true) elseif timeout_val == nil then p2_err.set_value("Please set the connection timeout.") @@ -155,23 +206,23 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style) end end - PushButton{parent=net_c_2,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} - PushButton{parent=net_c_2,x=44,y=14,text="Next \x1a",callback=submit_ct_tr,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=net_c_3,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=net_c_3,x=44,y=14,text="Next \x1a",callback=submit_ct_tr,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} - TextBox{parent=net_c_3,x=1,y=1,height=2,text="Optionally, set the facility authentication key below. Do NOT use one of your passwords."} - TextBox{parent=net_c_3,x=1,y=4,height=6,text="This enables verifying that messages are authentic, so it is intended for security on multiplayer servers. All devices on the same network MUST use the same key if any device has a key. This does result in some extra computation (can slow things down).",fg_bg=g_lg_fg_bg} + TextBox{parent=net_c_4,x=1,y=1,height=2,text="Optionally, set the facility authentication key below. Do NOT use one of your passwords."} + TextBox{parent=net_c_4,x=1,y=4,height=6,text="This enables verifying that messages are authentic, so it is intended for wireless security on multiplayer servers. All devices on the same wireless network MUST use the same key if any device has a key. This does result in some extra computation (can slow things down).",fg_bg=g_lg_fg_bg} - TextBox{parent=net_c_3,x=1,y=11,text="Facility Auth Key"} - local key, _ = TextField{parent=net_c_3,x=1,y=12,max_len=64,value=ini_cfg.AuthKey,width=32,height=1,fg_bg=bw_fg_bg} + TextBox{parent=net_c_4,x=1,y=11,text="Auth Key (Wireless Only, Not Used when Wired)"} + local key, _ = TextField{parent=net_c_4,x=1,y=12,max_len=64,value=ini_cfg.AuthKey,width=32,height=1,fg_bg=bw_fg_bg} local function censor_key(enable) key.censor(tri(enable, "*", nil)) end - local hide_key = Checkbox{parent=net_c_3,x=34,y=12,label="Hide",box_fg_bg=cpair(colors.lightBlue,colors.black),callback=censor_key} + local hide_key = Checkbox{parent=net_c_4,x=34,y=12,label="Hide",box_fg_bg=cpair(colors.lightBlue,colors.black),callback=censor_key} hide_key.set_value(true) censor_key(true) - local key_err = TextBox{parent=net_c_3,x=8,y=14,width=35,text="Key must be at least 8 characters.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} + local key_err = TextBox{parent=net_c_4,x=8,y=14,width=35,text="Key must be at least 8 characters.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} local function submit_auth() local v = key.get_value() @@ -182,8 +233,8 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style) else key_err.show() end end - PushButton{parent=net_c_3,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} - PushButton{parent=net_c_3,x=44,y=14,text="Next \x1a",callback=submit_auth,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=net_c_4,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(3)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=net_c_4,x=44,y=14,text="Next \x1a",callback=submit_auth,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} --#endregion @@ -382,6 +433,9 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style) load_settings(ini_cfg) try_set(s_vol, ini_cfg.SpeakerVolume) + try_set(wireless, ini_cfg.WirelessModem) + try_set(wired, ini_cfg.WiredModem ~= false) + try_set(wl_pref, ini_cfg.PreferWireless) try_set(svr_chan, ini_cfg.SVR_Channel) try_set(rtu_chan, ini_cfg.RTU_Channel) try_set(timeout, ini_cfg.ConnTimeout) @@ -665,6 +719,60 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style) end end + -- generate the list of available/assigned wired modems + function tool_ctl.gen_modem_list(enable) + modem_list.remove_all() + + local function select(iface) + tmp_cfg.WiredModem = iface + tool_ctl.gen_modem_list(true) + end + + local modems = ppm.get_wired_modem_list() + local missing = { tmp = true, ini = true } + + for iface, _ in pairs(modems) do + if ini_cfg.WiredModem == iface then + missing.ini = false + elseif tmp_cfg.WiredModem == iface then + missing.tmp = false + end + end + + if missing.tmp and tmp_cfg.WiredModem then + local line = Div{parent=modem_list,x=1,y=1,height=1} + + TextBox{parent=line,x=1,y=1,width=4,text="Used",fg_bg=cpair(tri(enable,colors.blue,colors.gray),colors.white)} + PushButton{parent=line,x=6,y=1,min_width=8,height=1,text="SELECT",callback=function()end,fg_bg=cpair(colors.black,colors.lightBlue),active_fg_bg=btn_act_fg_bg,dis_fg_bg=g_lg_fg_bg}.disable() + TextBox{parent=line,x=15,y=1,text="[missing]",fg_bg=cpair(colors.red,colors.white)} + TextBox{parent=line,x=25,y=1,text=tmp_cfg.WiredModem} + end + + if missing.ini and ini_cfg.WiredModem and (tmp_cfg.WiredModem ~= ini_cfg.WiredModem) then + local line = Div{parent=modem_list,x=1,y=1,height=1} + local used = tmp_cfg.WiredModem == ini_cfg.WiredModem + + TextBox{parent=line,x=1,y=1,width=4,text=tri(used,"Used","----"),fg_bg=cpair(tri(used and enable,colors.blue,colors.gray),colors.white)} + local select_btn = PushButton{parent=line,x=6,y=1,min_width=8,height=1,text="SELECT",callback=function()select(ini_cfg.WiredModem)end,fg_bg=cpair(colors.black,colors.lightBlue),active_fg_bg=btn_act_fg_bg,dis_fg_bg=g_lg_fg_bg} + TextBox{parent=line,x=15,y=1,text="[missing]",fg_bg=cpair(colors.red,colors.white)} + TextBox{parent=line,x=25,y=1,text=ini_cfg.WiredModem} + + if used or not enable then select_btn.disable() end + end + + -- list wired modems + for iface, _ in pairs(modems) do + local line = Div{parent=modem_list,x=1,y=1,height=1} + local used = tmp_cfg.WiredModem == iface + + TextBox{parent=line,x=1,y=1,width=4,text=tri(used,"Used","----"),fg_bg=cpair(tri(used and enable,colors.blue,colors.gray),colors.white)} + local select_btn = PushButton{parent=line,x=6,y=1,min_width=8,height=1,text="SELECT",callback=function()select(iface)end,fg_bg=cpair(colors.black,colors.lightBlue),active_fg_bg=btn_act_fg_bg,dis_fg_bg=g_lg_fg_bg} + TextBox{parent=line,x=15,y=1,text=iface} + + if used or not enable then select_btn.disable() end + end + end + --#endregion end diff --git a/rtu/configure.lua b/rtu/configure.lua index 1333b2b..a752342 100644 --- a/rtu/configure.lua +++ b/rtu/configure.lua @@ -37,7 +37,8 @@ local changes = { { "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.11.8", { "Added advanced option to invert digital redstone signals" } }, - { "v1.12.0", { "Added support for redstone relays" } } + { "v1.12.0", { "Added support for redstone relays" } }, + { "v1.13.0", { "Added support for wired communications modems" } } } ---@class rtu_configurator @@ -80,6 +81,8 @@ local tool_ctl = { update_relay_list = nil, ---@type function gen_peri_summary = nil, ---@type function gen_rs_summary = nil, ---@type function + + gen_modem_list = function (_) end } ---@class rtu_config @@ -112,7 +115,9 @@ local fields = { { "SVR_Channel", "SVR Channel", 16240 }, { "RTU_Channel", "RTU Channel", 16242 }, { "ConnTimeout", "Connection Timeout", 5 }, + { "WirelessModem", "Wireless Modem", true }, { "WiredModem", "Wired Modem", false }, + { "PreferWireless", "Prefer Wireless Modem", true }, { "TrustedRange", "Trusted Range", 0 }, { "AuthKey", "Facility Auth Key", "" }, { "LogMode", "Log Mode", log.MODE.APPEND }, @@ -317,8 +322,11 @@ function configurator.configure(ask_config) load_settings(settings_cfg, true) tool_ctl.has_config = load_settings(ini_cfg) + + -- set tmp_cfg so interface lists are correct tmp_cfg.Peripherals = tool_ctl.deep_copy_peri(ini_cfg.Peripherals) tmp_cfg.Redstone = tool_ctl.deep_copy_rs(ini_cfg.Redstone) + tmp_cfg.WiredModem = ini_cfg.WiredModem reset_term() @@ -333,6 +341,8 @@ function configurator.configure(ask_config) local display = DisplayBox{window=term.current(),fg_bg=style.root} config_view(display) + tool_ctl.gen_modem_list(ini_cfg.WiredModem ~= false) + while true do local event, param1, param2, param3, param4, param5 = util.pull_event() @@ -354,11 +364,13 @@ function configurator.configure(ask_config) ppm.handle_unmount(param1) tool_ctl.update_peri_list() tool_ctl.update_relay_list() + tool_ctl.gen_modem_list() elseif event == "peripheral" then ---@diagnostic disable-next-line: discard-returns ppm.mount(param1) tool_ctl.update_peri_list() tool_ctl.update_relay_list() + tool_ctl.gen_modem_list() end if event == "terminate" then return end diff --git a/scada-common/ppm.lua b/scada-common/ppm.lua index d540a34..5aeebbe 100644 --- a/scada-common/ppm.lua +++ b/scada-common/ppm.lua @@ -447,15 +447,15 @@ end ---@return table|nil reactor function table function ppm.get_fission_reactor() return ppm.get_device("fissionReactorLogicAdapter") end --- get a wired modem by name +-- get a modem by name ---@nodiscard ---@param iface string CC peripheral interface ---@return Modem|nil modem function table -function ppm.get_wired_modem(iface) +function ppm.get_modem(iface) local modem = nil local device = ppm_sys.mounts[iface] - if device.type == "modem" then modem = device.dev end + if device and device.type == "modem" then modem = device.dev end return modem end diff --git a/supervisor/pcie.lua b/supervisor/pcie.lua index dea089e..e98fa57 100644 --- a/supervisor/pcie.lua +++ b/supervisor/pcie.lua @@ -68,7 +68,7 @@ function pcie_bus.init(config, println) if type(config.WiredModem) == "string" then bus.wired_modem = config.WiredModem - local wired_modem = ppm.get_wired_modem(bus.wired_modem) + local wired_modem = ppm.get_modem(bus.wired_modem) if not (wired_modem and bus.wired_modem) then println("startup> wired comms modem not found")