#!/opt/lnxall_app/bin/lua

-- Created by jiaqiang.ye@lnxall.com
-- New 4G service for Non-openwrt
-- 2025/04/17

-- Load external modules
local uci         = require "uci"
local invoker     = require "invoker"
local cjson       = require "cjson"

local plat4g      = require "plat4g"
local newmodems   = require "newmodems"
local comm4gnet   = require "comm4gnet"

local gfmt                  = string.format
local g_tty4g_file          = "/tmp/4g_modem_tty"
local g_iccid_file          = "/tmp/simcard_iccid"
local g_signal_file         = "/etc/signal_4g.txt"
local g_cardinfo_file       = "/tmp/.modem_4ginfo.json"
local g_status_file         = "/tmp/status_info.json"
local g_modem_file          = "/app/config/modefor4g.json"
local g_lang_file           = "/etc/default/locale"
local g_modem_md5sum        = nil

local g_SIMCARD_EXTERNAL    = 0
local g_SIMCARD_INTERNAL    = 1

-- global variables definition
local g_modem4g             = nil     -- 4G modem object
local g_platform            = nil     -- underlying hardware object
local g_netdev				= nil
local g_cardno				= g_SIMCARD_EXTERNAL
local g_resolved 			= {}
local g_4gmode				= {
    ["simno"]               = g_SIMCARD_INTERNAL,
    ["ips"]                 = { [1] = "mqtt.lnxall.com", [2] = "emsmqtt.lnxall.com",
        [3] = "smc.lnxall.com", [4] = "emsiot.lnxall.com" },
    ["operator_map"]        = {
        ["zh_CN"]           = {},
        ["en_US"]           = {}
    },
	["apn"]					= "internet"
}

-- if file `g_force4g_reconnect exists, it works as an indicator that, `new4gnet.lua should force 4G modem to connect 4G network
local function force4g_reconnect(keepfile)
	local tmpv = invoker.statfile(g_force4g_reconnect)
	if not keepfile then invoker.unlink(g_force4g_reconnect) end
	if tmpv then return true end
	return false
end

local function update_ttypath_file(stty)
	local ttypath = g_modem4g:f_ttypath()
	if not ttypath then
		io.stderr:write("Error, failed to determine 4G modem tty path.\n")
		io.stderr:flush()
		return false
	end

	if stty then
		local ttyrw = g_modem4g:f_opentty(115200)
		if ttyrw then ttyrw:close(); ttyrw = nil end
	end

	local oldpath = invoker.readfile(g_tty4g_file, invoker.TRIMEND)
	if oldpath == ttypath then return true end
	local th = io.open(g_tty4g_file, "wb")
	if th then
		th:write(gfmt("%s\n", ttypath))
		th:close(); th = nil
		return true
	end
	io.stderr:write(gfmt("Error, failed to open file for writing: %s\n", g_tty4g_file))
	io.stderr:flush()
	return false
end

local function update_signal_file(signal)
	local oldsignal = invoker.readfile(g_signal_file, invoker.TRIMEND)
	if signal and signal ~= oldsignal then
		local idh = io.open(g_signal_file, "wb")
		if idh then
			idh:write(gfmt("%s\n", signal))
			idh:close(); idh = nil
			return true
		end
	end
	return false
end

local function update_simcard_info(g_cardno)
	local cardinfo = g_modem4g:f_cardinfo(g_cardno)
	local oldinfo = invoker.readfile(g_cardinfo_file, invoker.TRIMEND)
	if not cardinfo then return false end

	local _,data = pcall(cjson.decode, cardinfo)
	if (data.ICCID1 and data.ICCID0 and data.ICCID0 == data.ICCID1)
		or (data.hassim1 and not data.ICCID1) or (data.hassim0 and not data.ICCID0) then
		cardinfo = g_modem4g:f_cardinfo(g_cardno)
	end
	if cardinfo and cardinfo ~= oldinfo then
		local idh = io.open(g_cardinfo_file, "wb")
		if idh then
			idh:write(gfmt("%s\n", cardinfo))
			idh:close(); idh = nil
			return true
		end
	end
	return false
end

local function update_using_simcard_info(str)
	if not str or type(str) ~= "table" then
		io.stderr:write("Warning, cannot get 4g status.\n")
		io.stderr:flush()
		return false
	end

	local okay = nil
	local data, itab = invoker.readfile(g_cardinfo_file), {}
	if type(data) == "string" then
		okay, data = pcall(cjson.decode, data)
	else
		io.stderr:write(gfmt("Error, failed to read file: %s\n", g_cardinfo_file))
		io.stderr:flush()
	end

	if okay and type(data) == "table" then
		itab["ICCID1"] = data.ICCID1
		itab["ICCID0"] = data.ICCID0
		itab["hassim1"] = data.hassim1
		itab["hassim0"] = data.hassim0
	end

	local hassim, simno, iccid = nil, str[2], str[3]
	if str[1] then
		hassim = string.find(str[1], "READY", 1, true) and true or false
	end
	if simno and #iccid > 1 then itab["ICCID" .. simno] = iccid end
	if simno and type(hassim) ~= 'nil' then itab["hassim" .. simno] = hassim end

	local cardinfo = cjson.encode({ ["ICCID1"] = itab["ICCID1"], ["ICCID0"] = itab["ICCID0"],
		["hassim1"] = itab["hassim1"], ["hassim0"] = itab["hassim0"] })

	local oldinfo = invoker.readfile(g_cardinfo_file, invoker.TRIMEND)
	if cardinfo and cardinfo ~= oldinfo then
		local idh = io.open(g_cardinfo_file, "wb")
		if idh then
			idh:write(gfmt("%s\n", cardinfo))
			idh:close(); idh = nil
			return true
		end
	end
	return false
end

local function update_simcard_iccid()
	local iccid = g_modem4g:f_iccid()
	if iccid then iccid = "ICCID: " .. iccid else iccid = "ICCID:  " end
	local oldiccid = invoker.readfile(g_iccid_file, invoker.TRIMEND)
	if iccid and iccid ~= oldiccid then
		local idh = io.open(g_iccid_file, "wb")
		if idh then
			idh:write(gfmt("%s\n", iccid))
			idh:close(); idh = nil
			return true
		end
	end
	return false
end

local function update_status_info(str, passcnt, failcnt)
	if not str or type(str) ~= "table" then
		io.stderr:write("Warning, cannot get 4g status.\n")
		io.stderr:flush()
		return false
	end

	local simno, operator = str[2], str[4]
	local LANG = invoker.readfile(g_lang_file, invoker.TRIMEND)
	if LANG and #LANG > 0 then
		local last_match = nil
		for line in string.gmatch(LANG, "[^\r\n]+") do
			local match = string.match(line, "=([^%.]+)")
			if match then last_match = match end
		end
		LANG = last_match
	end
	if not LANG or (not string.match(LANG,"en_US") and not string.match(LANG,"zh_CN")) then LANG = os.getenv('LANG') end
	if LANG and #LANG > 0 then LANG = string.match(LANG, "^([^%.]+)") end
	if not LANG or #LANG == 0 then LANG = "en_US" end

	local lantab = g_4gmode["operator_map"][LANG]
	if type(lantab) ~= "table" then lantab = {} end
	if lantab[operator] then operator = lantab[operator] end
	if LANG == "zh_CN" then
		simno = simno == "0" and "外置卡" or "内置卡"
	else 
		simno = simno == "0" and "External simcard" or "Internal simcard"
	end

	local newinfo = cjson.encode({ ["hassim"] = str[1], ["simno"] = simno,
		["iccid"] = str[3], ["operator"] = operator,
		["area"] = str[5], ["registerd"] = str[6],
		["signal"] = str[7], ["passcnt"] = passcnt, ["failcnt"] = failcnt })
	local oldinfo = invoker.readfile(g_status_file, invoker.TRIMEND)
	if newinfo and newinfo ~= oldinfo then
		local idh = io.open(g_status_file, "wb")
		if idh then
			idh:write(gfmt("%s\n", newinfo))
			idh:close(); idh = nil
			return true
		end
	end
	return false
end

local function modem4g_repower(delay0, delay1)
	g_platform:poweron(false); invoker.waitsec(delay0)
	g_platform:poweron(true); invoker.waitsec(delay1)
end

-- check whether 4G network is enabled or not
local function network4g_enabled()
	local netuci = uci.cursor('/etc/config')
	if not netuci then return true end
	local disabled = netuci:get("network", "wwan", "disabled")
	netuci:close(); netuci = nil
	if disabled == "1" then return false end
	return true
end

local function wwan_cannot_up()
	local st4g = g_modem4g:f_status()
	if not st4g['HAS_REGISTERD'] or not st4g['HAS_SIMCARD'] then return false end

    local result = invoker.invoke(0,'nmcli', '-w', '6', 'con', 'up' ,'wwan')
	if result == 0 then return false end
    return true
end

local function netdev_exists(iface)
	if type(iface) ~= "string" or #iface == 0 then
		io.stderr:write("Error, cannot determine 4G network device.\n")
		io.stderr:flush()
		return false
	end
	local rval = invoker.invoke(invoker.NOSTDIO,"ip", "link", "show", "dev", iface)
	return rval
end

local function ping_list_swap1(ptab)
	if type(ptab) ~= "table" then return {} end

	-- remove duplicated host names
	local dtab, idx = {}, 0
	while true do
		idx = idx + 1
		local h = ptab[idx]
		if type(h) == "string" and #h > 0 then
			dtab[h] = true
		end
		if idx >= 128 then break end
	end

	local ipnum = 0
	ptab = {} -- clear existing table
	for h, _ in pairs(dtab) do
		ipnum = ipnum + 1
		ptab[#ptab + 1] = h
	end

	-- check the number of entries in the table:
	if ipnum <= 1 or type(ptab[1]) ~= "string" then
		-- return original ping-table
		return ptab
	end

	local mi = invoker.random(ipnum) + 1
	if mi >= 0x2 and mi <= ipnum and type(ptab[mi]) == "string" then
		io.stdout:write(gfmt("Switch ping host %s with %s\n", ptab[1], ptab[mi]))
		io.stdout:flush()
		ptab[1], ptab[mi] = ptab[mi], ptab[1]
	end
	return ptab
end

local function endswith(str, suffix)
	if type(str) ~= "string" then return false end
	local l0, l1 = string.len(str), string.len(suffix)
	if l0 < l1 then return false end
	return str:sub(l0 - l1 + 1) == suffix
end

local function ipw_filter_lnxall(ptab)
	if type(ptab) ~= "table" then return {} end
	local ntab, idx = {}, 0
	while true do
		idx = idx + 1
		local h = ptab[idx]
		-- if type(h) ~= "string" then break end
		if endswith(h, ".lnxall.com") or endswith(h, ".lnxall.cn") then
			io.stderr:write(gfmt("Warning, skip lnxall hostname: %s\n", h))
			io.stderr:flush()
		elseif h == "ntp.aliyun.com" or endswith(h, ".aliyuncs.com") then
			io.stderr:write(gfmt("Warning, skip ntp/aliyuncs hostname: %s\n", h))
			io.stderr:flush()
		elseif type(h) == "string" and #h > 0 then
			ntab[#ntab + 1] = h
			io.stdout:write(gfmt("Adding client IP whitelist: %s\n", h))
			io.stdout:flush()
		end
		if idx >= 128 then break end
	end
	return ntab
end

local function load_json_config(onlyip)
	local mret, md5val = invoker.invoke(invoker.OUTPUT, "md5sum", g_modem_file)
	if mret ~= 0 or type(md5val) ~= "string" then
		md5val = nil
	else
		md5val = string.match(md5val, "^([%x]+)")
	end

	if md5val and g_modem_md5sum == md5val then
		-- configuration file already loaded
		return true
	end
	g_modem_md5sum = md5val

	local jsonc = invoker.readfile(g_modem_file, nil, 0x40000)
	if type(jsonc) ~= "string" or #jsonc == 0 then
		io.stderr:write(gfmt("Error, failed to read: %s\n", g_modem_file))
		io.stderr:flush()
		return false
	end

	local okay, data = pcall(cjson.decode, jsonc)
	if not okay or type(data) ~= "table" then
		io.stderr:write(gfmt("Error, failed to decode as JSON: %s\n", g_modem_file))
		io.stderr:flush()
		return false
	end

	-- host/iparray for WhiteList
	g_4gmode["ipw"] = ping_list_swap1(ipw_filter_lnxall(data.ipw))
	g_4gmode["ips"] = ping_list_swap1(data.ips)
	if not onlyip then
		if data.apn then g_4gmode["apn"] = data.apn end
		if data.simno then g_4gmode["simno"] = data.simno end
		if data.operator_map then g_4gmode["operator_map"] = data.operator_map end
	end
	return true
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 ping_with_netdev(ndev, phost)
	if type(phost) ~= "string" or #phost == 0 then
		io.stderr:write("Error, invalid host given for `ping\n")
		io.stderr:flush()
		return nil
	end

	local cmds = { "ping", "-c3", "-w12" }
	if type(ndev) == "string" and #ndev > 0 then
		cmds[#cmds + 1] = "-I"
		cmds[#cmds + 1] = ndev
	end
	local hipv4 = nil
	if not invoker.isipv4(phost) then
		hipv4 = g_resolved[phost]
		if not hipv4 then
			local ip = nslookup(phost)
			if ip then
				hipv4 = ip[1]
				io.stdout:write(gfmt("DNS resolved: %s => %s\n", phost, hipv4))
				io.stdout:flush()
				g_resolved[phost] = hipv4
			else
				io.stderr:write(gfmt("Error, DNS resolved failed for %s\n", phost))
				io.stderr:flush()
				return false
			end
		end
	end

	cmds[#cmds + 1] = hipv4 or phost
	local _, output = invoker.invoke(invoker.OUTPUT, cmds)
	if type(output) ~= "string" then output = "" end

	-- process output from `ping
	if not string.find(output, "packet loss", 1, true) or
		string.find(output, "100% packet loss", 1, true) then
		io.stderr:write(gfmt("Error, ping host has failed: %s\n", phost))
		io.stderr:flush()
		return false
	end
	io.stdout:write(gfmt("INFO, ping host okay: %s\n", phost))
	io.stdout:flush()
	return true
end

local function network_okay(iface)
	load_json_config(true)
	local ipt = g_4gmode["ipw"]
	if type(ipt) ~= "table" then ipt = {} end

	local idx, numping = 0, 0
	while true do
		idx = idx + 1
		local hostn = ipt[idx]
		if type(hostn) ~= "string" then break end
		if ping_with_netdev(iface, hostn) then return hostn end
		io.stderr:write(gfmt("Error, ping whitelist host failed: %s\n", hostn))
		io.stderr:flush()
		numping = numping + 1
	end

	if numping >= 1 then
		io.stderr:write("Error, hostnames in whitelist has all failed.\n")
		io.stderr:flush()
		return false
	end

	ipt, idx, numping = g_4gmode["ips"], 0, 0
	while true do
		idx = idx + 1
		local hostn = ipt[idx]
		if type(hostn) ~= "string" then break end
		if ping_with_netdev(iface, hostn) then return hostn end
		numping = numping + 1
	end
	return numping <= 0
end

local function switch_simcard()
	if g_cardno == 1 then g_cardno = 0 else g_cardno = 1 end
	io.stdout:write(gfmt("Error, 4G Network cannot work, switching SIMCARD: %d ...\n", g_cardno))
	io.stdout:flush()
	while g_cardno ~= g_modem4g:f_get_cursim() do
		g_modem4g:f_switch_sim(g_cardno)
		invoker.waitsec(5)
	end
	return true
end

local function set_apn()
	g_modem4g:f_setapn(g_4gmode["apn"])
	invoker.msleep(2000)
	io.stdout:write(gfmt("set apn '%s'\n", g_4gmode["apn"]))
	io.stdout:flush()
	return true
end

local function new4gnet_init()
	invoker.setname("NEW4GNET")
	invoker.setenv("PATH", "/opt/lnxall_app/bin:/opt/lnxall_app/sbin:/usr/local/bin:/usr/bin:/usr/sbin:/bin:/sbin")
	invoker.unlink(g_tty4g_file)
	invoker.unlink(g_iccid_file)
	invoker.unlink(g_signal_file)
	invoker.unlink(g_cardinfo_file)
	invoker.unlink(g_status_file)
	load_json_config(false)

	-- detect hardware platform
	local platdev = nil
	while not platdev do
		platdev = plat4g.detect()
		if platdev then break end
		invoker.waitsec(invoker.uptime() < 120 and 5 or 60)
	end
	g_platform = platdev
	io.stdout:write(gfmt("Found hardware device: %s\n", platdev.name))
	io.stdout:flush()

	-- power on 4G modem unconditionally
	local tmpval = 3
	if not platdev:ison() then tmpval = 15 end
	if platdev:poweron(true) then
		io.stdout:write("4G modem has been powered up\n")
		io.stdout:flush()
		invoker.waitsec(tmpval)
	else
		io.stderr:write("Error, failed to power on 4G modem\n")
		io.stderr:flush()
	end

	-- wait for 4G modem to show up
	local modem4g = nil
	while true do
		modem4g = newmodems.detect()
		if modem4g and modem4g:f_preinit() == newmodems.PREINIT_AGAIN then
			local name = modem4g.name or "WTH SAID THAT, 4G NOT WORKING..."
			io.stdout:write(gfmt("Restarting 4G modem checking for '%s'\n", name))
			io.stdout:flush()
			modem4g:close(); modem4g = nil
		end
		if modem4g then break end
		invoker.waitsec(invoker.uptime() < 120 and 15 or 60)
	end
	g_modem4g = modem4g
	io.stdout:write(gfmt("Found 4G modem device: %s\n", modem4g.name))
	io.stdout:flush()

	-- wait for netdev
	g_netdev = g_platform:netdev()
	if not g_netdev or modem4g.name == "L610" then
		io.stderr:write("4G netdev not found or modem name is L610\n")
		io.stderr:flush()
		modem4g_repower(5, 20)

		io.stdout:write("Warning, trying to reconnect 4G modem to find netdev...\n")
		io.stdout:flush()
		g_modem4g:f_connect()
		invoker.waitsec(15)
	else
		io.stdout:write(gfmt("Found netdev: %s\n", g_netdev))
		io.stdout:flush()
	end
	update_ttypath_file(true)

	-- set apn
	local current_apn, renum = nil, 0
	while renum <= 2 do
		current_apn = g_modem4g:f_getapn()
		if current_apn == newmodems.APN_ALREADY or current_apn == g_4gmode["apn"] then
			io.stdout:write(gfmt("Apn not need set or already set : '%s'\n", g_4gmode["apn"]))
			io.stdout:flush()
			break
		end
		set_apn()
		renum = renum + 1
		invoker.waitsec(3)
	end

	-- read simcard no
	if g_modem4g.dualsim then
		g_cardno = tonumber(g_4gmode["simno"])
		if g_cardno ~= g_SIMCARD_EXTERNAL and g_cardno ~= g_SIMCARD_INTERNAL then
			g_cardno = g_SIMCARD_INTERNAL
			io.stderr:write("Error, invalid SIMCard choice from config file.\n")
			io.stderr:flush()
		end
	end
	io.stdout:write(gfmt("Firstly use card %d\n", g_cardno))
	io.stdout:flush()

	-- update ttydev/iccid files
	if g_modem4g.dualsim then update_simcard_info(g_cardno) end
	while g_cardno ~= g_modem4g:f_get_cursim() do
		g_modem4g:f_switch_sim(g_cardno)
		invoker.waitsec(5)
	end
	return true
end

local function mainfunc()
	local tmpval, itercnt, failcnt, passcnt = 0, 0, 0, 0
	local at_okay, at_recon, at_time = false, 3, 0
	local sim_okay, sim_recon, sim_time = false , 3, 0
	local ping_recon = 3
	local nowtime, changenum = 0, 2
	while true do
		tmpval = 120
		-- whether 4g network is enabled
		if network4g_enabled() then
			local phost, recon, change_disable = false, false, false
			local st4g, stinfo = {}, {}
			itercnt = itercnt + 1

			if not invoker.statfile(g_iccid_file) or (itercnt % 6) == 1 then
				update_simcard_iccid()
			end

			-- check AT-command replay
			local ttyrw = g_modem4g:f_opentty(115200)
			if not ttyrw then
				at_okay = true
				io.stderr:write("Error, not find tty dev")
				io.stderr:flush()
			else
				if at_recon >= 0 then at_okay = ttyrw:hasreply() end
				if at_okay then
					at_recon, at_time = 3, 0
				elseif at_recon > 0 then
					if at_time == 0 or invoker.uptime() - at_time >= 180 then
						at_time, recon = -1, true
						at_recon = at_recon - 1
					end
					io.stderr:write(gfmt("%d:Error, AT-commond not replay.\n", at_recon+1))
					io.stderr:flush()
				else
					at_time, change_disable = 0, true
					--ulog.log(ulog.LOG_ERR, gfmt("ERROR, AT-command not have replay %d.", itercnt))
				end
				ttyrw:close(); ttyrw = nil
			end

			-- check hassim
			if sim_recon >= 0 and at_okay then sim_okay = g_modem4g:f_hassim() end
			if sim_okay then
				sim_recon, sim_time = 3, 0
			elseif sim_recon > 0 and at_okay then
				if sim_time == 0 or invoker.uptime() - sim_time >= 300 then
					sim_time, recon = -1, true
					sim_recon = sim_recon - 1
				end
				io.stderr:write(gfmt("%d:Error, not found simcard.\n", sim_recon+1))
				io.stderr:flush()
			else
				sim_time, change_disable = 0, true
			end

			-- check status
			if at_okay then st4g, stinfo = g_modem4g:f_status() end
			if type(st4g) == "table" then
				if not st4g['HAS_REGISTERD'] then
					io.stdout:write(gfmt("Error, 4G modem not registerd: \n%s\n,%s\n",
						st4g["AT+CREG"] or "WTH SAIED THAT...", st4g["AT+CGREG"] or "WTH SAIED THAT..."))
				else
					io.stdout:write(gfmt("INFO, 4G modem has registered: \n%s\n,%s\n",
						st4g["AT+CREG"] or "WTH SAIED THAT...", st4g["AT+CGREG"] or "WTH SAIED THAT..."))
				end
				io.stdout:flush()
			end

			-- ping test
			if at_okay and sim_okay and ping_recon >= 0 then
				phost = network_okay(nil)
			elseif not at_okay or not sim_okay then
				passcnt = 0
				failcnt = failcnt + 1
			end
			if at_okay and sim_okay and ping_recon >= 0 and not phost then
				io.stdout:write(gfmt("%s: Network ping failed\n", os.date()))
				if ping_recon == 3 then
					io.stdout:write(gfmt("Warning, trying to reset 4G modem %s ...\n", g_modem4g.name))
					io.stdout:flush()
					g_modem4g:f_reset()
					invoker.msleep(7000)
					update_ttypath_file(true)

					local current_apn = g_modem4g:f_getapn()
					if current_apn ~= newmodems.APN_ALREADY and current_apn ~= g_4gmode["apn"] then set_apn() end
					io.stdout:write(gfmt("Warning, trying to reconnect 4G modem %s ...\n", g_modem4g.name))
					io.stdout:flush()
					g_modem4g:f_connect()
				elseif wwan_cannot_up() then
					io.stdout:write(gfmt("%d: Warning, trying to restart Network Daemon...\n", ping_recon))
					io.stdout:flush()
					invoker.invoke(invoker.NOSTDIO, "netdaemon-restart", "restart")
					invoker.msleep(3000)
					if g_modem4g.name == "MC661" or g_platform.name == "MA35D1" then g_modem4g:f_connect() end
					invoker.msleep(3000)
					phost = true
				else
					io.stdout:write(gfmt("%d: Warning, trying to reconnect 4G modem %s ...\n", ping_recon, g_modem4g.name))
					io.stdout:flush()
					g_modem4g:f_connect()
				end

				tmpval, passcnt = 60, 0
				failcnt = failcnt + 1
				ping_recon = ping_recon - 1
			elseif phost then
				io.stdout:write(gfmt("%s: Network ping okay: %s\n", os.date(), phost))
				io.stdout:flush()

				passcnt = passcnt + 1
				failcnt, ping_recon = 0, 3
				nowtime, changenum = 0, 2
			end

			-- no AT-command replay or not hassim
			if recon then
				io.stdout:write(gfmt("4G SIMCard found: %s, re-power 4G modem...\n", sim_okay and "true" or "false"))
				io.stdout:flush()
				modem4g_repower(5, 20)
				update_ttypath_file(true)

				io.stdout:write(gfmt("Warning, trying to reconnect 4G modem %s ...\n", g_modem4g.name))
				io.stdout:flush()
				g_modem4g:f_connect()

				if at_time == -1 then at_time = invoker.uptime() end
				if sim_time == -1 then sim_time = invoker.uptime() end
				tmpval = 60
			end

			-- reset more than 3
			local flag = ((sim_recon == 0) or (at_recon == 0)) and change_disable or (ping_recon < 0)
			if flag and g_modem4g.dualsim then
				if changenum > 0 then
					switch_simcard()
					tmpval = 30
					changenum = changenum - 1
					at_recon, sim_recon, ping_recon = 3, 3, 3
				elseif nowtime == 0 then
					invoker.invoke(invoker.NOSTDIO, "ip", "addr", "flush", g_netdev)
					nowtime = invoker.uptime()
					io.stdout:write("Waiting 2 hours for switching simcard......\n")
					io.stdout:flush()
				elseif (invoker.uptime() - nowtime) > 7200 then
					nowtime, changenum = 0, 2
					at_recon, sim_recon = 3, 3
					tmpval, ping_recon = 30, 3
				end
			elseif flag and not g_modem4g.dualsim then
				if nowtime == 0 then
					invoker.invoke(invoker.NOSTDIO, "ip", "addr", "flush", g_netdev)
					nowtime = invoker.uptime()
					io.stdout:write("Waiting 2 hours for restarting modem......\n")
					io.stdout:flush()
				elseif (invoker.uptime() - nowtime) > 7200 then
					tmpval = 30
					nowtime, at_recon = 0, 3
					sim_recon, ping_recon = 3, 3
				end
			end

			local signal = 99
			if stinfo and (type(stinfo[4]) ~= 'string' or string.len(stinfo[4]) < 2) then stinfo[7] = 99 end
			if stinfo then signal = stinfo[7] end

			update_signal_file(signal)
			update_using_simcard_info(stinfo)
			update_status_info(stinfo,passcnt,failcnt)
		else
			io.stdout:write("Warning, network4g is disabled!!!\n")
			io.stdout:flush()
			-- TODO: bring down 4G network
			if g_netdev then
				invoker.invoke(0, "ip", "addr", "flush", "dev", g_netdev)
				invoker.invoke(0, "ip", "link", "set", "dev", g_netdev, "down")
			end
		end
		invoker.waitsec(tmpval)	
	end
end

new4gnet_init()
mainfunc()
os.exit(1)
