#!/opt/lnxall_app/bin/lua -- Created by jiaqiang.ye@lnxall.com -- Network 4G service for Debian-based gateways -- 2023/12/01 -- Load external modules local uci = require 'uci' local bit32 = require 'bit32' local posix = require 'posix' local ulog = require 'ubuslog' local invoker = require 'invoker' local modem4g = require 'modem4g' local g_modem = nil -- from modem4g.getmodem local plat4gOps = {} -- Platform dependent 4G operations -- global CONSTANT variables local g_netuci = "/opt/lnxall_app/bin/netuci" local g_recount = 0 local gfmt = string.format local CHECK_INTERVAL = 180 -- check-interval for 4G network connection local PING_ADDRS = { [1] = 'emsmqtt.lnxall.com', [2] = 'smc.lnxall.com', [3] = 'emsiot.lnxall.com', [4] = 'mqtt.lnxall.com' } local g_metrics, g_metric_idx = { 100, 50 }, 1 local g_resolved = {} -- host to IPv4 address mapping local g_backcfg = "/opt/lnxall_app/platspec/etc-config-network" -- global varibales local g_usbid = nil -- USB ID of 4G network modem local g_netdev = nil -- name of USB network device local g_4ginfo = {} -- Array of supported USB modem local g_dns = { [1] = '8.8.8.8', [2] = '114.114.114.114' } g_4ginfo['2c7c:6005'] = { ['ttyindex'] = 2, ['script'] = 'ec200.gcom', -- EC200A ['bringup'] = 'AT+QCFG="usbnet",1\r\n', } g_4ginfo['2c7c:6002'] = { ['ttyindex'] = 1, ['script'] = 'ec200.gcom', -- EC200N ['bringup'] = 'AT+QCFG="usbnet",1\r\n', } g_4ginfo['2c7c:0904'] = { ['ttyindex'] = 0, ['script'] = 'ec800g.gcom', -- EC800G ['bringup'] = 'AT+QCFG="usbnet",1\r\n', } g_4ginfo['2c7c:6026'] = { ['ttyindex'] = 2, ['script'] = 'ec200.gcom' } -- EC200T g_4ginfo['2c7c:0901'] = { ['ttyindex'] = 0, ['script'] = 'ec200u.gcom' } -- EC200U g_4ginfo['2c7c:0125'] = { ['ttyindex'] = 2, ['script'] = 'ec2x.gcom' } -- 全球通模组 g_4ginfo['1782:4e00'] = { ['ttyindex'] = 1, ['script'] = 'air720.gcom', ['bringup'] = "AT+SETUSB=2\r\n", } -- Air724UG g_4ginfo['19d1:0001'] = { ['ttyindex'] = 0, ['script'] = 'air780.gcom'} --Air780.gcom g_4ginfo['2cb7:0a0a'] = { ['ttyindex'] = 0, ['script'] = 'mc661.gcom'} -- MC661 g_4ginfo['2cb7:0a0b'] = { ['ttyindex'] = 0, ['script'] = 'mc661.gcom'} -- MC661 g_4ginfo['2cb7:0a0c'] = { ['ttyindex'] = 0, ['script'] = 'mc661.gcom'} -- MC661 local function netdev_exists(iface) if type(iface) ~= "string" or #iface == 0 then return false -- invalid method argument end local rval = invoker.invoke(invoker.NOSTDIO, "ip", "link", "show", "dev", iface) return rval == 0 end local function nslookup(hostn) local pid, pfd = invoker.invoke(invoker.NOWAIT + invoker.NOSTDIO + invoker.OUTPUT, "nslookup", hostn) if not pid then return nil end local swait = invoker.uptimsec() local nwait, exited = swait, false swait = swait + 4000 while nwait < swait do if invoker.waitpid(pid, true) then exited = true break end invoker.msleep(250) nwait = invoker.uptimsec() end if not exited then invoker.kill(pid, 9) invoker.waitpid(pid, false) end local output = invoker.readfd(pfd, 8192) if type(pfd) == "number" then invoker.closefd(pfd); pfd = nil end if type(output) ~= "string" then return nil end local iptab, ipn, ans = {}, 0, false for line in string.gmatch(output, "[^\r\n]+") do if not ans then if string.find(line, "answer:", 1, true) then ans = true end else local ipv4 = string.match(line, "Address:%s+([%d%.]+)") if ipv4 and invoker.isipv4(ipv4) then ipn = ipn + 1 iptab[ipn] = ipv4 end end end if ipn == 0 then return nil end return iptab end local function has_ipv4(ndev) local okay, output = invoker.invoke(invoker.OUTPUT, "ip", "addr", "show", "dev", ndev) if okay ~= 0 or type(output) ~= "string" then return false end local ipaddr = string.match(output, "inet%s+([%d%.]+)/") if not ipaddr then return false end return ipaddr end local function find_4g_netdev() local okay, output = invoker.invoke(invoker.OUTPUT, "ip", "link", "show") if okay ~= 0 or type(output) ~= "string" then return nil end for mdev in string.gmatch(output, "enp[^:\r\n]+:") do local nlen = string.len(mdev) if nlen >= 9 then return mdev:sub(1, nlen - 1) end end return nil end local function network_disabled(which) if type(which) ~= "string" then which = 'wwan' end local nuci = uci.cursor('/etc/config') local iface = nuci:get('network', which) if iface == nil then -- no configuration found. default to true nuci:close(); nuci = nil return false end local dev = nuci:get('network', which, 'device') local dis = nuci:get('network', which, 'disabled') nuci:close(); nuci = nil if dev == nil or dis == "1" or dis == 1 then return true end return false end local function fetch_ttyusb(idx) local jdx, kdx = 0, 0 while jdx < 32 do local ndev = gfmt("/sys/bus/usb-serial/drivers/option1/ttyUSB%d", jdx) if posix.access(ndev) == 0 then ndev = gfmt("/dev/ttyUSB%d", jdx) local info = posix.stat(ndev) if type(info) == "table" and info["type"] == "character device" then if kdx >= idx then return ndev end kdx = kdx + 1 end end jdx = jdx + 1 end jdx = 0 kdx = 0 while jdx < 32 do local ndev = find_device_path (gfmt ("ttyACM%d", jdx)) if not ndev then break end local vpid = get_vid_pid (ndev) if vpid == g_usbid then if posix.access(ndev) == 0 then ndev = gfmt("/dev/ttyACM%d", jdx) local info = posix.stat(ndev) if type(info) == "table" and info["type"] == "character device" then if kdx >= idx then return ndev end kdx = kdx + 1 end end end jdx = jdx + 1 end return nil end local function update_sim_iccid(usbdev) local outfile = modem4g.ICCID_FILE if posix.access(outfile) == 0 then return true end if not usbdev then if not g_usbid then return false end local i4g = g_4ginfo[g_usbid] usbdev = fetch_ttyusb(i4g.ttyindex) end if posix.access(usbdev) ~= 0 then return false end invoker.invoke(invoker.NOSTDIO, "stty", "-F", usbdev, "raw", "cs8", "-echo") local usbfd = posix.open(usbdev, posix.O_RDWR + posix.O_NONBLOCK + posix.O_NOCTTY) if type(usbfd) ~= "number" or usbfd < 0 then return false end while true do local ldat = posix.read(usbfd, 1024) if type(ldat) ~= "string" or #ldat == 0 then break end end local restab = {} posix.write(usbfd, "AT\r\n"); invoker.msleep(400) posix.write(usbfd, "AT+QCFG=\"nat\"\r\n"); invoker.msleep(400) posix.write(usbfd, "AT+ICCID\r\n"); invoker.msleep(400) posix.write(usbfd, "AT+QICCID\r\n"); invoker.msleep(400) posix.write(usbfd, "AT+CCID\r\n"); invoker.msleep(400) while true do local ldat = posix.read(usbfd, 1024) if type(ldat) == "string" and #ldat > 0 then restab[#restab + 1] = ldat else break end end local output = #restab > 0 and table.concat(restab) or nil if type(output) ~= "string" then posix.close(usbfd) return false end local iccid = string.match(output, "ICCID:%s+([%d%a]+)") if iccid == nil then iccid = string.match(output, "CCID:%s+([%d%a]+)") end if iccid then local filp = io.open(outfile, "wb") if filp then filp:write(gfmt("ICCID: %s\n", iccid)) filp:close(); filp = nil end end -- check for nat configuration if string.find(output, 'QCFG: "nat"', 1, true) and not string.find(output, 'QCFG: "nat",1', 1, true) then posix.write(usbfd, "AT+QCFG=\"nat\",1\r\n"); invoker.msleep(300) posix.write(usbfd, "AT+CFUN=1,1\r\n"); invoker.msleep(1500) end posix.close(usbfd) return iccid end local function get_vid_pid(device_path) -- get the Vendor and Product ID from the real device path local vid, pid local path = device_path repeat local uevent_file = io.open(path .. "/uevent") -- make sure the V and P id is saved in uevent, of course uedvadm also will get the information from uevent file if uevent_file then for line in uevent_file:lines() do local key, value = line:match("(%a+)=(.+)") if key == "PRODUCT" then vid, pid = value:match("(%x+)%/(%x+)%/(%x+)") if vid and pid then vid = string.format("%04x", tonumber(vid, 16)) pid = string.format("%04x", tonumber(pid, 16)) return vid .. ":" .. pid end end end uevent_file:close() end path = path:match("(.+)/[^/]+") -- if not found, next step is to check the parent directory. until not path or path == "" return nil, nil end local function find_device_path(device_name) local device_path -- unfortunately, this method is strong depends on kernel version. As far as I know it can't work on 2.4 kernel. for class in io.popen("ls " .. "/sys/class/"):lines() do local path = "/sys/class/" .. class .. "/" .. device_name if io.open(path) then local link = io.popen("readlink -f " .. path):read("*line") -- get the link path if link then while link:match("%.%./") do link = link:gsub("/[^/]+/%.%./", "/") -- get the real path end device_path = link break end end end return device_path end local function check_resolv() local has_sr = "" if posix.access('/lib/systemd/systemd-resolved') == 0 then local conts, restart = {}, false local syscfg = '/etc/systemd/resolved.conf' conts[#conts + 1] = "# Generated by deb4gnet.lua\n[Resolve]\n" conts[#conts + 1] = string.format("DNS=%s %s\n", g_dns[1], g_dns[2]) conts[#conts + 1] = "DNSSEC=no\n" conts[#conts + 1] = "Cache=yes\n" conts[#conts + 1] = "LLMNR=false\n" conts = table.concat(conts) local sfile, sdat = io.open(syscfg, "rb"), nil if sfile then sdat = sfile:read("*a") sfile:close(); sfile = nil end if sdat ~= conts then restart = true sfile = io.open(syscfg, "wb") if sfile then sfile:write(conts) sfile:close(); sfile = nil end end if not restart then local _, output = invoker.invoke(invoker.OUTPUT, "systemctl", "status", "systemd-resolved") if type(output) ~= "string" then output = "" end if not string.find(output, "Active: active (running)", 1, true) then restart = true end end if restart then io.stdout:write("Restarting systemd-resolved service...\n") io.stdout:flush() invoker.invoke(0, "systemctl", "enable", "systemd-resolved") invoker.invoke(0, "systemctl", "restart", "systemd-resolved") end has_sr = "nameserver 127.0.0.53\n" end local content = gfmt("# Generated by deb4gnet.lua\n%snameserver %s\nnameserver %s\noptions timeout:12 attempts:3\n", has_sr, g_dns[1], g_dns[2]) local rfile = "/etc/resolv.conf" local resolv, cont = io.open(rfile, "rb"), nil if resolv then cont = resolv:read('*a') resolv:close(); resolv = nil end if cont == content then return true end -- if cont then io.stdout:write("Warning: old DNS settings:\n" .. cont) end ulog.log(ulog.LOG_INFO, "Network4g: updating resolv.conf...") resolv = io.open(rfile, "wb") if resolv then resolv:write(content) resolv:close(); resolv = nil posix.sync() return true end return false end local function systemd_restart(initp, name) if posix.access(initp) == 0 then return invoker.invoke(invoker.NOSTDIO, "systemctl", "restart", name) == 0 end return false end local function wwan_cannot_up() local file = io.open("/tmp/4g_modem_tty","r") local usbdev if file then local tty_usb = file:read("*all") usbdev = string.sub(tty_usb, 1,12) end file:close();file = nil local usbfd = posix.open(usbdev, posix.O_RDWR + posix.O_NONBLOCK + posix.O_NOCTTY) if type(usbfd) ~= "number" or usbfd < 0 then io.stderr:write(gfmt("failed to open %s for restart NetworkManager\n",usbdev)) io.stderr:flush() return false end posix.write(usbfd, "AT+CEREG?\r\n"); invoker.msleep(400) local ldat = posix.read(usbfd,1024) if type(ldat) ~= "string" or #ldat <= 0 or not string.match(ldat,"0,1") then return false end posix.write(usbfd, "AT+CPIN?\r\n"); invoker.msleep(400) ldat = posix.read(usbfd,1024) if type(ldat) ~= "string" or #ldat <= 0 or not string.match(ldat,"READY") then return false end posix.close(usbfd) local result = os.execute("nmcli con up wwan") if result == 0 then return false end return true end local function net4g_reload(iface, metric) local okay, output = invoker.invoke(invoker.OUTPUT, "nmcli", "-m", "tabular", "-t", "con", "show") if okay ~= 0 or type(output) ~= "string" then output = "" end local ndels = 0 for line in string.gmatch(output, "[^\r\n]+") do local con = string.match(line, "^([^:]+)") if con and con ~= 'wwan' then local cinfo = nil okay, cinfo = invoker.invoke(invoker.OUTPUT + 0x70000, "nmcli", "con", "show", con) if okay == 0 and type(cinfo) == "string" and string.find(cinfo, iface, 1, true) then ndels = ndels + 1 invoker.invoke(invoker.NOSTDIO, "nmcli", "con", "down", con) invoker.invoke(invoker.NOSTDIO, "nmcli", "con", "delete", con) io.stderr:write(gfmt("Delete NetworkManager connection: %s\n", con)) io.stderr:flush() end end end local wuci, doc = uci.cursor('/etc/config'), false local ndev = wuci:get('network', 'wwan', 'device') if metric then metric = tostring(metric) local oldm = wuci:get('network', 'wwan', 'metric') if metric ~= oldm then if type(oldm) ~= "string" then oldm = "nil" end io.stdout:write(gfmt("Update wwan metric from %s to %s\n", oldm, metric)) io.stdout:flush() doc = true wuci:delete('network', 'wwan', 'metric') wuci:delete('network', 'wwan', 'metric') wuci:set('network', 'wwan', 'metric', metric) end end if ndev ~= iface then if ndev == nil then -- recheck whether wwan section exists ndev = wuci:get('network', 'wwan') end if ndev == nil then -- configuration section might be missing, force copy of network-configuration local r = invoker.invoke(invoker.NOSTDIO, "cp", "-v", "-f", g_backcfg, "/etc/config/network") if type(r) ~= "number" then r = 10 end io.stderr:write(gfmt("Error, restoring network configuration from '%s': %d\n", g_backcfg, r)) io.stderr:flush() else doc = true wuci:delete('network', 'wwan', 'device') wuci:delete('network', 'wwan', 'device') wuci:set('network', 'wwan', 'device', iface) end end if doc then wuci:commit('network') end invoker.invoke(invoker.NOSTDIO, "lua", g_netuci) invoker.msleep(5000) if wwan_cannot_up() then io.stdout:write("restart NetworkManager") io.stdout:flush() invoker.invoke(invoker.NOSTDIO, "systemctl", "restart", "NetworkManager") invoker.msleep(3000) systemd_restart("/etc/systemd/system/multi-user.target.wants/bdhcpd.service", "bdhcpd") systemd_restart("/etc/systemd/system/multi-user.target.wants/lnxhostapd.service", "lnxhostapd") end return true end local function network_connect(iface) -- kill dhclient application first local i4g = g_4ginfo[g_usbid] local dftdev = gfmt('/dev/ttyUSB%d', i4g.ttyindex) update_sim_iccid(dftdev) -- check for /dev/ttyUSBx device local devn = fetch_ttyusb(i4g.ttyindex) or dftdev local devi = posix.stat(devn) if type(devi) ~= "table" or devi["type"] ~= "character device" then io.stderr:write(gfmt("Error, ttyUSB device not found: %s\n", devn)) io.stderr:flush() return false end -- miscellaneous operations for 4G modem if g_modem then g_modem.update(g_modem, devn, false) end if i4g.script then -- we need to execute comgt script invoker.invoke(invoker.NOSTDIO, "ip", "addr", "flush", iface) -- invoke comgt to establish network connection local okay = invoker.invoke(invoker.NOSTDIO, 'comgt', '-d', devn, '-s', gfmt('/opt/lnxall_app/etc/gcom/%s', i4g.script)) if okay ~= 0 then io.stderr:write(gfmt("Error, comgt has returned %d\n", type(okay) == "number" and okay or 1)) io.stderr:flush() end elseif i4g.bringup then local tfd = posix.open(devn, posix.O_RDWR + posix.O_NONBLOCK + posix.O_NOCTTY) if type(tfd) == "number" and tfd >= 0 then posix.write(tfd, i4g['bringup']) posix.close(tfd) end end local count = 0 invoker.msleep(5000) -- delay 3 seconds before continue -- wait for `iface network to show up while true do count = count + 1 if count >= 10 then break end if netdev_exists(iface) then break end invoker.msleep(1000) end invoker.msleep(1000) -- again, delay one second -- check again for the existence of `iface if not netdev_exists(iface) then io.stderr:write(gfmt("Error, network device not found: %s\n", iface)) io.stderr:flush() return false end local midx = nil if invoker.uptime() >= 90 then midx = g_metric_idx midx = midx + 1 if midx > 3 then midx = 1 end g_metric_idx = midx midx = g_metrics[midx] end net4g_reload(iface, midx) return true end -- initialize working environment local function n4g_init(initcnt) -- Enumerate output from `lsusb, -- and determine the 4G modem USB ID local okay, outbuf = invoker.invoke(invoker.OUTPUT, "lsusb") if okay ~= 0 or type(outbuf) ~= "string" or string.len(outbuf) == 0 then io.stderr:write("Error, failed to invoke `lsusb!\n") io.stderr:flush() return false end -- process line by line for output in string.gmatch(outbuf, "([^\r\n]+)") do local usbid = string.match(output, "(%x%x%x%x:%x%x%x%x)") if usbid and g_4ginfo[usbid] then g_usbid = usbid break end end if not g_usbid then if (initcnt % 32) == 0 then io.stderr:write("Warning, no 4G network modem found!\n") io.stderr:flush() end return false end -- write USBID to `/sys/bus/usb-serial/drivers/option1/new_id local opt = string.gsub(g_usbid, ":", " ") .. " ff\n" local new_id = "/sys/bus/usb-serial/drivers/option1/new_id" local oldpt = invoker.readfile(new_id) if opt ~= oldpt then local opth = io.open(new_id, "wb") if opth then opth:write(opt); opth:close(); opth = nil end invoker.waitsec(4) end opt, new_id, oldpt = nil, nil, nil -- remove local variables local nowtim = invoker.uptime() while nowtim < 65 do -- do not check 4G network in such a haste check_resolv() -- update resolv.conf before wait invoker.waitsec(65 - nowtim) nowtim = invoker.uptime() end check_resolv() local ttydev = fetch_ttyusb(g_4ginfo[g_usbid]["ttyindex"]) if ttydev then -- write /tmp/4g_modem_tty with the control path of ttyUSB device local fhdl = io.open("/tmp/4g_modem_tty", "wb") if fhdl then fhdl:write(gfmt("%s\n", ttydev)) fhdl:close(); fhdl = nil end end -- check network device g_netdev = plat4gOps.netdev() if type(g_netdev) ~= "string" or not netdev_exists(g_netdev) then g_netdev = nil local info4g = g_4ginfo[g_usbid] if ttydev and info4g['bringup'] then local tfd = posix.open(ttydev, posix.O_RDWR + posix.O_NONBLOCK + posix.O_NOCTTY) if type(tfd) == "number" and tfd >= 0 then posix.write(tfd, info4g['bringup']) posix.close(tfd) end end g_modem = modem4g.getmodem(g_usbid) if g_modem then g_modem.init(g_modem, ttydev) end if (initcnt % 0x5) == 0 then plat4gOps.poweron(false) elseif (initcnt % 0x5) == 1 then plat4gOps.poweron(true) elseif (initcnt % 0x5) == 2 then network_connect('wwan0') end io.stderr:write("Error, cannot determine 4G network device.\n") io.stderr:flush() return false end g_modem = modem4g.getmodem(g_usbid) if g_modem then g_modem.init(g_modem, ttydev) end local fc = '/etc/force_connect.txt' if invoker.uptime() < 240 and posix.access(fc) ~= 0 then network_connect(g_netdev) local filp = io.open(fc, "wb") if filp then filp:write(gfmt("%s: Forcing network reconnect.\n", os.date())) filp:close(); filp = nil end invoker.waitsec(15) elseif not has_ipv4(g_netdev) then invoker.invoke(invoker.NOSTDIO, "nmcli", "con", "del", "wwan") invoker.invoke(invoker.NOSTDIO, "lua", g_netuci) end return true end local function network_okay(iface) local cmds = { "ping", "-c3", "-w12" } local cmdidx = 0x1 + #cmds if type(iface) == "string" then cmds[cmdidx] = "-I" cmds[cmdidx + 0x1] = iface cmdidx = cmdidx + 0x2 end for _, hname in pairs(PING_ADDRS) do local hipv4 = nil if invoker.isipv4(hname) then hipv4 = hname else hipv4 = g_resolved[hname] if not hipv4 then local ips = nslookup(hname) if ips then hipv4 = ips[1] io.stdout:write(gfmt("DNS resolved: %s => %s\n", hname, hipv4)) io.stdout:flush() g_resolved[hname] = hipv4 end end end if hipv4 then cmds[cmdidx] = hipv4 local okay, output = invoker.invoke(invoker.OUTPUT + invoker.NOSTDIO, cmds) if type(output) == "string" and string.find(output, 'packet loss', 1, true) and not string.find(output, '100% packet loss', 1, true) then return hname end if okay == 0 then return hname end if invoker.tcpcheck(hipv4, 80, 1500) then return hname end if invoker.tcpcheck(hipv4, 3883, 1500) then return hname end end end return false end local function reset_4g_modem(sdely) local resettag = '/tmp/.4g_modem_reset' if posix.access(resettag) == 0 then return false end local i4g = g_4ginfo[g_usbid] local dftdev = gfmt('/dev/ttyUSB%d', i4g.ttyindex) local devn = fetch_ttyusb(i4g.ttyindex) or dftdev local devi = posix.stat(devn) if type(devi) ~= "table" or devi["type"] ~= "character device" then io.stderr:write(gfmt("Error, ttyUSB device not found: %s\n", devn)) io.stderr:flush() return false end local usbfd = posix.open(devn, bit32.bor(posix.O_WRONLY, posix.O_TRUNC, posix.O_NONBLOCK, posix.O_NOCTTY)) if type(usbfd) ~= "number" or usbfd < 0 then io.stderr:write(gfmt("Error, failed to open %s for reset\n", devn)) io.stderr:flush() return false end posix.write(usbfd, "AT+RESET\r\n"); invoker.msleep(300); posix.close(usbfd) local tagf = io.open(resettag, "wb") if tagf then tagf:write(gfmt("%s: 4G modem reset via %s\n", os.date(), devn)) tagf:close(); tagf = nil end invoker.waitsec(sdelay) return true end local function watch_resolv(delay) local flags = bit32.bor(invoker.IN_CREATE, invoker.IN_DELETE_SELF, invoker.IN_MODIFY, invoker.IN_CLOSE_WRITE) local rfile = '/etc/resolv.conf' rfile = invoker.realpath(rfile) if not posix.access(rfile) then check_resolv() end local changed, errmsg = invoker.watchfile(flags, delay * 1000, nil, rfile) if changed == nil then if type(errmsg) ~= "string" then errmsg = "unknown watchfile error" end io.stderr:write(errmsg); io.stderr:write("\n"); io.stderr:flush() invoker.waitsec(delay) return false end if changed and changed > 0 then invoker.msleep(1500) -- delay 1.5 second to avoid modifying by too many processes check_resolv() end return true end local function isat_time(hour) local nowt = os.date("*t") return nowt["hour"] == hour end local function mainloop() local dcnt = 0 -- disconnect count local nowtim = invoker.uptime() local nextim = nowtim + CHECK_INTERVAL update_sim_iccid() while true do check_resolv() -- check resolv.conf before continue local phost = network_okay() if phost then io.stdout:write(gfmt("%s: Network ping okay: %s\n", os.date(), phost)) io.stdout:flush() if dcnt > 0 then dcnt = 0 end if posix.access(modem4g.ICCID_FILE) ~= 0 then update_sim_iccid() end if g_modem then g_modem.update(g_modem, nil, true) end elseif not network_disabled() then if isat_time(23) and dcnt >= 4 and posix.access(modem4g.ICCID_FILE) == 0 then -- it is 23 at midnight, safe to reboot ulog.log(ulog.LOG_ERR, gfmt("reboot due to 4G network failure: %d.", dcnt)) invoker.invoke(invoker.NOSTDIO, "reboot"); invoker.waitsec(5) end if not reset_4g_modem(10) and bit32.band(dcnt, 0x1) == 0x1 then plat4gOps.poweron(false); invoker.waitsec(3) plat4gOps.poweron(true); invoker.waitsec(12) end dcnt = dcnt + 1 -- increment disconnected g_netdev = plat4gOps.netdev() if not netdev_exists(g_netdev) then ulog.log(ulog.LOG_ERR, gfmt("Error, no 4G net device found: %d", dcnt)) else local midx = math.floor(dcnt % 2) + 1 ulog.log(ulog.LOG_ERR, gfmt("reconnect 4G network, count: %d", dcnt)) network_connect(g_netdev) end end check_resolv() -- check resolv.conf again nowtim = invoker.uptime() -- delay until next checking time while nowtim < nextim do watch_resolv(nextim - nowtim) nowtim = invoker.uptime() end -- increment next checking time nextim = nextim + CHECK_INTERVAL if nextim <= nowtim then nextim = nextim + CHECK_INTERVAL end end end local function check_platform() local bname = invoker.readfile('/tmp/sysinfo/board_name', invoker.TRIMEND) if bname == 'WLOSANY_X86_V3' then invoker.invoke(invoker.NOSTDIO, "modprobe", "option") plat4gOps['init'] = function () return true end plat4gOps['poweron'] = function (ison) return true end plat4gOps['netdev'] = function () local ndev = "enp0s20f0u7" -- if netdev_exists(ndev) then return ndev end local okay, output = invoker.invoke(invoker.OUTPUT, "ip", "link", "show") if okay == 0 and type(output) == "string" then for line in string.gmatch(output, "[^\r\n]+") do local mdev = string.match(line, "^%d+: ([^:]+):") if mdev and #mdev >= 8 and mdev:sub(1, 3) == "enp" then ndev = mdev break end end end return ndev end return true end if bname == 'WLOSANY_BAU-B30-003' or bname == 'WLOSANY_WooLink2004' then plat4gOps['init'] = function () -- TODO: add 4G modem poweron operation here return true end plat4gOps['poweron'] = function (ison) -- TODO: add 4G modem power on/off operation here return true end plat4gOps['netdev'] = function () return 'usb0' end return true end if bname == 'WLOSANY_RK3568_FORLINX' then plat4gOps['init'] = function () local gpfx = "/sys/class/gpio/gpio101" local direct = gfmt("%s/direction", gpfx) local output = invoker.readfile(direct, invoker.TRIMEND) if output ~= "out" then invoker.invoke(0, "/bin/sh", "-c", "echo 101 > /sys/class/gpio/export") invoker.invoke(0, "/bin/sh", "-c", gfmt("echo out > %s", direct)) end -- power on 4G module invoker.invoke(0, "/bin/sh", "-c", gfmt("echo 1 > %s/value", gpfx)) return true end plat4gOps['poweron'] = function (ison) local cmd = gfmt("echo %s > /sys/class/gpio/gpio101value", ison and "1" or "0") invoker.invoke(0, "/bin/sh", "-c", cmd) return true end plat4gOps['netdev'] = function () return 'usb0' end return true end if bname == 'WLOSANY_RK3568' or bname == 'WLOSANY_RK3568_XDDL' then plat4gOps['init'] = function () local gpfx = "/sys/class/gpio/gpio74" local direct = gfmt("%s/direction", gpfx) local output = invoker.readfile(direct, invoker.TRIMEND) if output ~= "out" then invoker.invoke(0, "/bin/sh", "-c", "echo 74 > /sys/class/gpio/export") invoker.invoke(0, "/bin/sh", "-c", gfmt("echo out > %s", direct)) end -- power on 4G module invoker.invoke(0, "/bin/sh", "-c", gfmt("echo 1 > %s/value", gpfx)) return true end plat4gOps['poweron'] = function (ison) local cmd = gfmt("echo %s > /sys/class/gpio/gpio74/value", ison and "1" or "0") invoker.invoke(0, "/bin/sh", "-c", cmd) return true end plat4gOps['netdev'] = function () return 'usb0' end return true end if bname == 'WLOSANY_VM1220T' then plat4gOps['init'] = function () local gpfx = "/sys/class/gpio/gpio342" local direct = gfmt("%s/direction", gpfx) local output = invoker.readfile(direct, invoker.TRIMEND) if output ~= "out" then invoker.invoke(0, "/bin/sh", "-c", "echo 342 > /sys/class/gpio/export") invoker.invoke(0, "/bin/sh", "-c", gfmt("echo out > %s", direct)) end -- power on 4G module invoker.invoke(0, "/bin/sh", "-c", gfmt("echo 0 > %s/value", gpfx)) return true end plat4gOps['poweron'] = function (ison) local cmd = gfmt("echo %s > /sys/class/gpio/gpio342/value", ison and "0" or "1") invoker.invoke(0, "/bin/sh", "-c", cmd) return true end plat4gOps['netdev'] = function () return 'usb0' end return true end if bname == 'WLOSANY_RK3588' then plat4gOps['init'] = function () local gpfx = "/sys/class/gpio/gpio120" local direct = gfmt("%s/direction", gpfx) local output = invoker.readfile(direct, invoker.TRIMEND) if output ~= "out" then invoker.invoke(0, "/bin/sh", "-c", "echo 120 > /sys/class/gpio/export") invoker.invoke(0, "/bin/sh", "-c", gfmt("echo out > %s", direct)) end -- power on 4G module invoker.invoke(0, "/bin/sh", "-c", gfmt("echo 1 > %s/value", gpfx)) return true end plat4gOps['poweron'] = function (ison) local cmd = gfmt("echo %s > /sys/class/gpio/gpio120/value", ison and "1" or "0") invoker.invoke(0, "/bin/sh", "-c", cmd) return true end plat4gOps['netdev'] = function () return 'usb0' end return true end if bname == 'WLOSANY_LC100' then plat4gOps['init'] = function () local gpfx = "/sys/class/gpio/gpio132" local direct = gfmt("%s/direction", gpfx) local output = invoker.readfile(direct, invoker.TRIMEND) if output ~= "out" then invoker.invoke(0, "/bin/sh", "-c", "echo 132 > /sys/class/gpio/export") invoker.invoke(0, "/bin/sh", "-c", gfmt("echo out > %s", direct)) end -- power on 4G module invoker.invoke(0, "/bin/sh", "-c", gfmt("echo 1 > %s/value", gpfx)) return true end plat4gOps['poweron'] = function (ison) local cmd = gfmt("echo %s > /sys/class/gpio/gpio132/value", ison and "1" or "0") invoker.invoke(0, "/bin/sh", "-c", cmd) return true end plat4gOps['netdev'] = function () return 'usb0' end return true end if bname == 'WLOSANY_EM1000' then plat4gOps['init'] = function () invoker.invoke(0, 'gpioset', '0', '25=0') -- power on 4G module return true end plat4gOps['poweron'] = function (ison) invoker.invoke(0, 'gpioset', '0', gfmt("25=%s", ison and "0" or "1")) return true end plat4gOps['netdev'] = function () if netdev_exists('modem0') then return 'modem0' end return netdev_exists('eth0') and 'eth0' or 'usb0' end return true end io.stderr:write("Error, unsupported board_name for DEBIAN 4GNET.\n") io.stderr:flush() return false end local function preinit(pname, dns1, dns2) posix.unlink(modem4g.ICCID_FILE) if posix.access("/usr/lib/systemd/system/ModemManager.service") == 0 then invoker.invoke(invoker.NOSTDIO, "systemctl", "stop", "ModemManager") invoker.invoke(invoker.NOSTDIO, "systemctl", "disable", "ModemManager") end -- setup PATH environment variable posix.setenv('PATH', '/opt/lnxall_app/bin:/usr/bin:/usr/sbin:/bin:/sbin', true) posix.chdir("/") ulog.reinit(pname) invoker.setname(pname) if type(dns1) == "string" and invoker.isipv4(dns1) then g_dns[1] = dns1 end if type(dns2) == "string" and invoker.isipv4(dns2) then g_dns[2] = dns2 end -- wait until we have correct board_name while not check_platform() do invoker.msleep(5000) end check_resolv() local count = 0 plat4gOps.init() -- initialize 4G module while network_disabled() do watch_resolv(30) if (invoker.uptime() - count) >= 30 then check_resolv() count = invoker.uptime() end end count = 0 -- reset to zero while not n4g_init(count) do watch_resolv(15) count = count + 1 end return true end preinit('DEBIAN_4GNET', arg[1], arg[2]) ulog.log(ulog.LOG_INFO, "4G modem found, entering loop...") mainloop() -- application hangs in the function os.exit(2)