#!/opt/lnxall_app/bin/lua

local invoker = require "invoker"
local cjson = require "cjson"
local comm4g = require "comm4gnet"

local gfmt = string.format
local g_debug              = nil -- disable debugging
local g_SIMCARD_EXTERNAL   = 0
local g_SIMCARD_INTERNAL   = 1
local g_PREINIT_AGAIN      = 2025
local g_APN_ALREADY        = 2026

local function common_4gmodem_switchsim(self)
	-- note that self.dualsim must be `false
	return false -- Not supported, only one SIMcard
end

local function common_4gmodem_getsim(self)
	-- note that self.dualsim must be `false
	-- return g_SIMCARD_INTERNAL -- ?? WHICH ONE SHOULD I RETURN ??
	return g_SIMCARD_EXTERNAL -- Not supported, default to external SIMcard
end

local function common_4gmodem_preinit(self)
	-- Nothing important TODO!!!
	return true
end

local function common_4gmodem_ttypath(self)
	local jdx, kdx, idx = 0, 0, self.ttyindex
	while jdx < 32 do
		local ndev = gfmt("/sys/bus/usb-serial/drivers/option1/ttyUSB%d", jdx)
		if invoker.statfile(ndev) then
			ndev = gfmt("/dev/ttyUSB%d", jdx)
			if invoker.statfile(ndev) == "character" then
				if kdx >= idx then
					self.ttypath = ndev
					return ndev
				end
				kdx = kdx + 1
			end
		end
		jdx = jdx + 1
	end

	self.ttypath = false
	io.stderr:write(gfmt("Error, failed to find ttydev for %s\n", self.name))
	io.stderr:flush()
	return nil
end

local function common_4gmodem_reset(self)
	local ttyp = self.ttypath
	if not ttyp then ttyp = self:f_ttypath() end
	if not ttyp then return false end

	local ttyrw = comm4g.opentty(ttyp)
	if not ttyrw then return false end

	ttyrw:write("AT+RESET\r\n")
	ttyrw:close(); ttyrw = nil
	return true
end

local function common_4gmodem_connect(self)
	io.stderr:write(gfmt("Error, don't known how to connect for %s\n", self.name))
	io.stderr:flush()
	return false
end

local function common_4gmodem_opentty(self, baudrate)
	local ttyp = self.ttypath
	if not ttyp then ttyp = self:f_ttypath() end
	if not ttyp then return nil end

	local ttyrw = comm4g.opentty(ttyp, baudrate)
	if not ttyrw then return nil end
	return ttyrw
end

-- Note that return value is a table
local function atcmd_write_array(trw, cmds, mdelay)
	if type(mdelay) ~= "number" or mdelay < 300 then
		mdelay = 300
	end
	local replies = {}
	trw:clear() -- clear existing buffer
	for idx, atcmd in ipairs(cmds) do
		trw:write(atcmd)
		-- if trw:haserr() then break end
		local resp = trw:readany(mdelay, "\nOK", "ERROR")
		if type(resp) == "string" then replies[idx] = resp else replies[idx] = "" end
	end
	return replies
end

local function atcmd_resp_dump(cmds, replies)
	for idx, cmd in ipairs(cmds) do
		cmd = string.gsub(cmd, "[\r\n]+", "")
		local reply = replies[idx]
		if type(reply) == "string" and #reply > 0 then
			reply = string.gsub(reply, "[\r\n]+", " ")
		else
			reply = "WTH COMPLAINED THAT, AT COMMAND NO RESPONSE!!!"
		end
		io.stdout:write(gfmt("%32s | %s\n", cmd, reply))
		io.stdout:flush()
	end
end

local function common_4gmodem_iccid(self)
	local ttyrw = self:f_opentty()
	if not ttyrw then return false end

	local atcmds = {
		"AT\r\n",
		"AT+QCFG=\"nat\"\r\n",
		"AT+ICCID\r\n",
		"AT+QICCID\r\n",
		"AT+CCID\r\n",
	}

	local resp = atcmd_write_array(ttyrw, atcmds, 2000)
	if g_debug then atcmd_resp_dump(atcmds, resp) end
	-- resp is a table, shoud not be empty
	resp = table.concat(resp)

	local iccid = string.match(resp, "ICCID:%s+([%d%a]+)")
	if not iccid then iccid = string.match(resp, "CCID:%s+([%d%a]+)") end
	if not iccid then iccid = "no simcard" end
	if self.iccid ~= iccid then self.iccid = iccid end

	-- check for nat configuration
	if string.find(resp, 'QCFG: "nat"', 1, true) and not string.find(resp, 'QCFG: "nat",1', 1, true) then
		ttyrw:write("AT+QCFG=\"nat\",1\r\n"); invoker.msleep(300)
		ttyrw:write("AT+CFUN=1,1\r\n"); invoker.msleep(2500)
	end

	ttyrw:close(); ttyrw = nil
	return iccid
end

local function common_4gmodem_status(self)
	local ttyrw = self:f_opentty()
	if not ttyrw then return false end

	local atcmds = {
		[1] = "AT+CPIN?\r\n",
		[2] = "AT+CREG=2\r\n",
		[3] = "AT+CCID\r\n",
		[4] = "AT+COPS?\r\n",
		[5] = "AT+CREG?\r\n",
		[6] = "AT+CGREG?\r\n",
		[7] = "AT+CSQ\r\n",
		[8] = "AT+CGCONTRDP\r\n",
		[9] = "AT+GTRNDIS?\r\n",
	}

	local resp = atcmd_write_array(ttyrw, atcmds, 3000)
	if g_debug then atcmd_resp_dump(atcmds, resp) end
	ttyrw:close(); ttyrw = nil
	-- resp is a table, should not be empty

	local privst, reply, str = self.status, nil, {}
	if type(privst) ~= "table" then privst = {} end

	reply = resp[1] -- AT+CPIN?
	if #reply > 0 then
		privst["AT+CPIN"] = reply
		privst["HAS_SIMCARD"] = string.find(reply, "CPIN:%s+READY") and true or false
		if string.match(reply,"%+CPIN:%s*(%w+)%s*OK") then str[#str+1] = string.match(reply,"%+CPIN:%s*(%w+)%s*OK") else str[#str+1] = " " end
	end

	reply = resp[3] -- AT+CCID
	if #reply > 0 then
		privst["AT+CCID"] = reply
		str[#str+1] = "0"
		if string.match(reply, "%+CCID%s+([%d%a]+)") then 
			str[#str+1] = string.match(reply, "%+CCID%s+([%d%a]+)")
		elseif string.match(reply, "CCID:%s+([%d%a]+)") then 
			str[#str+1] = string.match(reply, "CCID:%s+([%d%a]+)")
		else 
			str[#str+1] = "" 
		end
	end

	reply = resp[4] -- AT+COPS?
	if #reply > 0 then
		privst["AT+COPS"] = reply
		if string.match(reply, '"(.-)"') then str[#str+1] = string.match(reply, '"(.-)"') else str[#str+1] = " " end
	end

	reply = resp[5] -- AT+CREG?
	if #reply > 0 then
		privst["AT+CREG"] = reply
		local lac, cid = string.match(reply, '"(.-)","(.-)"')
		if lac and cid then 
			str[#str+1] = lac .. "," .. cid 
		else
			str[#str+1] = " "
		end
	end

	reply = resp[6] -- AT+CGREG?
	if #reply > 0 then
		privst["AT+CGREG"] = reply
		privst["HAS_REGISTERD"] = string.find(reply, "CGREG:%s+%d+,1") and true or false
		local n1, n2 = string.match(reply, "%+CGREG:%s(%d+),(%d+)")
		if n1 and n2 then 
			str[#str+1] = n1 .. "," .. n2
		else
			str[#str+1] = " "
		end
	end

	reply = resp[7] -- AT+CSQ
	if #reply > 0 then
		privst["AT+CSQ"] = reply
		local sigl = string.match(reply, "CSQ:%s+(%d+),")
		if sigl then sigl = tonumber(sigl) end
		if type(sigl) ~= "number" then sigl = 99 end
		privst["SIGNAL_QUALITY"] = sigl
		str[#str+1] = sigl
	end

	reply = resp[8] -- AT+CGCONTRDP
	if #reply > 0 then
		privst["AT+CGCONTRDP"] = reply
	end

	reply = resp[9] -- AT+GTRNDIS?
	if #reply > 0 then
		privst["AT+GTRNDIS"] = reply
	end

	local info = cjson.encode({ ["hassim"] = str[1], ["simno"] = str[2], ["iccid"] = str[3], ["operator"] = str[4], ["area"] = str[5], ["registerd"] = str[6], ["signal"] = str[7] })
	self.status = privst
	return privst, str
end

local function common_4gmodem_hassim(self)
	local ttyrw = self:f_opentty()
	if not ttyrw then return nil end

	ttyrw:clear()
	ttyrw:write("AT+CPIN?\r\n")
	local resp = ttyrw:readany(1500, "OK", "ERROR")
	if not resp then resp = "" end

	ttyrw:close(); ttyrw = nil
	if string.find(resp, "CPIN:%s+READY") then return true end
	if string.find(resp, "ERROR") then return false end
	return nil
end

local function common_4gmodem_setapn(self)
	-- YET TO BE SUPPORTED
	return false
end

local function common_4gmodem_getapn(self)
	-- YET TO BE SUPPORTED
	return g_APN_ALREADY
end

local function common_4gmodem_close(self)
	if type(self) ~= "table" then return false end
	self.name            = nil
	self.ttyindex        = nil
	self.dualsim         = nil
	self.ttypath         = nil
	self.isoption1       = nil
	setmetatable(self, nil)

	self = nil
	return true
end

local function common_modem4g_methods()
	return {
		["f_preinit"]        = common_4gmodem_preinit,
		["f_hassim"]         = common_4gmodem_hassim,
		["f_opentty"]        = common_4gmodem_opentty,
		["f_ttypath"]        = common_4gmodem_ttypath,
		["f_reset"]          = common_4gmodem_reset,
		["f_connect"]        = common_4gmodem_connect,
		["f_status"]         = common_4gmodem_status,
		["f_iccid"]          = common_4gmodem_iccid,
		["f_setapn"]         = common_4gmodem_setapn,
		["f_getapn"]         = common_4gmodem_getapn,
		["f_switch_sim"]     = common_4gmodem_switchsim,  -- switch to another SIMcard,
		["f_get_cursim"]     = common_4gmodem_getsim,  -- Get current working SIMcard,
		["close"]            = common_4gmodem_close,
	}
end

local function ec800g_4gmodem_switchsim(self,cardno)
	local ttyrw = self:f_opentty()
	if not ttyrw then return nil end

	ttyrw:clear()
	ttyrw:write(gfmt("AT+QDSIM=%d\r\n",cardno))
	local resp = ttyrw:readany(1500, "OK", "ERROR")
	if not resp then resp = "" end

	ttyrw:close(); ttyrw = nil
	io.stdout:write(gfmt("Switching SIMCARD to %s...\n",cardno))
	io.stdout:flush()
	if string.find(resp, "OK") then return true end
	if string.find(resp, "ERROR") then return false end
	return false -- Not supported, only one SIMcard
end

local function ec800g_4gmodem_getsim(self)
	local ttyrw = self:f_opentty()
	if not ttyrw then return nil end

	ttyrw:clear()
	ttyrw:write("AT+QDSIM?\r\n")
	local resp = ttyrw:readany(1500, "OK", "ERROR")
	if not resp then resp = "" end

	ttyrw:close(); ttyrw = nil
	local data = tonumber(string.match(resp,"%+QDSIM:%s*(%d+)"))
	if data then
		if data ~= 0 and data ~= 1 then 
			return nil 
		else
			io.stdout:write(gfmt("Using card %d\n",data))
			io.stdout:flush()
			return data 
		end
	end

	if string.find(resp, "ERROR") then return nil end
	return nil
end

local function ec800g_4gmodem_info(self,cardno)
	local ttyrw = self:f_opentty()
	if not ttyrw then return nil end

	if not cardno or type(cardno) ~= "number" then cardno = 0 end
	if cardno and cardno == 1 then oldno = 0 else oldno = 1 end
	local oldcmd = "AT+QDSIM=" .. oldno .. "\r\n"
	local newcmd = "AT+QDSIM=" .. cardno .. "\r\n"

	ttyrw:clear()
	ttyrw:write(oldcmd); invoker.msleep(6000)
	ttyrw:write("AT+CPIN?\r\n"); invoker.msleep(500)
	ttyrw:write("AT+ICCID\r\n"); invoker.msleep(500)
	local oldresp = ttyrw:read(500)
	if not oldresp then oldresp = "" end

	ttyrw:clear()
	ttyrw:write(newcmd); invoker.msleep(6000)
	ttyrw:write("AT+CPIN?\r\n"); invoker.msleep(500)
	ttyrw:write("AT+ICCID\r\n"); invoker.msleep(500)
	local newresp = ttyrw:read(500)
	if not newresp then newresp = "" end

	r0 = string.find(oldresp, "CPIN: READY", 1, true) and true or false
	r1 = string.find(newresp, "CPIN: READY", 1, true) and true or false
    id0 = string.match(oldresp, "ICCID:%s+([%d%a]+)")
    id1 = string.match(newresp, "ICCID:%s+([%d%a]+)")
	local info = cjson.encode({ ["hassim" .. oldno] = r0, ["hassim" .. cardno] = r1,["ICCID" .. oldno] = id0, ["ICCID" .. cardno] = id1 })

	ttyrw:close(); ttyrw = nil
	return info
end

local function mc661_4gmodem_preinit(self)
	local ttyrw = self:f_opentty()
	if not ttyrw then return nil end

	ttyrw:clear()
	ttyrw:write("AT+GTUSBMODE=74\r\n")
	tty:write("AT+CFUN=1,1\r\n")
	invoker.msleep(6000)

	ttyrw:close(); ttyrw = nil
	return g_PREINIT_AGAIN
end

local function mc661_4gmodem_switchsim(self,cardno)
	local ttyrw = self:f_opentty()
	if not ttyrw then return nil end

	ttyrw:clear()
	ttyrw:write(gfmt("AT+GTDUALSIM=%d\r\n",cardno))
	local resp = ttyrw:readany(1500, "OK", "ERROR")
	if not resp then resp = "" end

	ttyrw:close(); ttyrw = nil
	io.stdout:write(gfmt("Switching SIMCARD to %s...\n",cardno))
	io.stdout:flush()
	if string.find(resp, "OK)") then return true end
	if string.find(resp, "ERROR") then return false end
	return false -- Not supported, only one SIMcard
end

local function mc661_4gmodem_getsim(self)
	local ttyrw = self:f_opentty()
	if not ttyrw then return nil end

	ttyrw:clear()
	ttyrw:write("AT+GTDUALSIM?\r\n")
	local resp = ttyrw:readany(1500, "OK", "ERROR")
	if not resp then resp = "" end

	ttyrw:close(); ttyrw = nil
	local data = tonumber(string.match(resp,"%+GTDUALSIM:%s*(%d+)"))
	if data then
		if data ~= 0 and data ~= 1 then 
			return nil 
		else 
			io.stdout:write(gfmt("Using card %d\n", data))
			io.stdout:flush()
			return data 
		end
	end

	if string.find(resp, "ERROR") then return nil end
	return nil
end

local function mc661_4gmodem_info(self,cardno)
	local ttyrw = self:f_opentty()
	if not ttyrw then return nil end

	if not cardno or type(cardno) ~= "number" then cardno = 0 end
	if cardno and cardno == 1 then oldno = 0 else oldno = 1 end
	local oldcmd = "AT+GTDUALSIM=" .. oldno .. "\r\n"
	local newcmd = "AT+GTDUALSIM=" .. cardno .. "\r\n"

	ttyrw:clear()
	ttyrw:write(oldcmd); invoker.msleep(6000)
	ttyrw:write("AT+CPIN?\r\n"); invoker.msleep(500)
	ttyrw:write("AT+CCID\r\n"); invoker.msleep(500)
	local oldresp = ttyrw:read(500)
	if not oldresp then oldresp = "" end

	ttyrw:clear()
	ttyrw:write(newcmd); invoker.msleep(6000)
	ttyrw:write("AT+CPIN?\r\n"); invoker.msleep(500)
	ttyrw:write("AT+CCID\r\n"); invoker.msleep(500)
	local newresp = ttyrw:read(500)
	if not newresp then newresp = "" end

	r0 = string.find(oldresp, "CPIN: READY", 1, true) and true or false
	r1 = string.find(newresp, "CPIN: READY", 1, true) and true or false
    id0 = string.match(oldresp, "CCID:%s+([%d%a]+)")
    id1 = string.match(newresp, "CCID:%s+([%d%a]+)")
	local info = cjson.encode({ ["hassim" .. oldno] = r0, ["hassim" .. cardno] = r1,["ICCID" .. oldno] = id0, ["ICCID" .. cardno] = id1 })

	ttyrw:close(); ttyrw = nil
	return info
end

local function mc661_4gmodem_status(self)
	local ttyrw = self:f_opentty()
	if not ttyrw then return false end

	local atcmds = {
		[1] = "AT+CPIN?\r\n",
		[2] = "AT+GTDUALSIM?\r\n",
		[3] = "AT+CCID\r\n",
		[4] = "AT+COPS?\r\n",
		[5] = "AT+GTCCINFO?\r\n",
		[6] = "AT+CGREG?\r\n",
		[7] = "AT+CSQ\r\n",
		[8] = "AT+CGCONTRDP\r\n",
		[9] = "AT+GTRNDIS?\r\n",
	}

	local resp = atcmd_write_array(ttyrw, atcmds, 3000)
	if g_debug then atcmd_resp_dump(atcmds, resp) end
	ttyrw:close(); ttyrw = nil
	-- resp is a table, should not be empty

	local privst, reply, str = self.status, nil, {}
	if type(privst) ~= "table" then privst = {} end

	reply = resp[1] -- AT+CPIN?
	if #reply > 0 then
		privst["AT+CPIN"] = reply
		privst["HAS_SIMCARD"] = string.find(reply, "CPIN:%s+READY") and true or false
		if string.match(reply,"%+CPIN:%s*(%w+)%s*OK") then str[#str+1] = string.match(reply,"%+CPIN:%s*(%w+)%s*OK") else str[#str+1] = "" end
	end

	reply = resp[2] -- AT+GTDUALSIM?
	if #reply > 0 then
		privst["AT+GTDUALSIM"] = reply
		if string.match(reply,"%+GTDUALSIM:%s*(%d+)") then str[#str+1] = string.match(reply,"%+GTDUALSIM:%s*(%d+)") else str[#str+1] = "" end
	end

	reply = resp[3] -- AT+CCID
	if #reply > 0 then
		privst["AT+CCID"] = reply
		if string.match(reply, "%+CCID:%s+([%d%a]+)") then str[#str+1] = string.match(reply, "%+CCID:%s+([%d%a]+)") else str[#str+1] = "" end
	end

	reply = resp[4] -- AT+COPS?
	if #reply > 0 then
		privst["AT+COPS"] = reply
		if string.match(reply, '"(.-)"') then str[#str+1] = string.match(reply, '"(.-)"') else str[#str+1] = "" end
	end

	reply = resp[5] -- AT+GTCCINFO?
	if #reply > 0 then
		privst["AT+GTCCINFO"] = reply
		local lac, cid = string.match(reply, "LTE service cell:.-,%d+,%d+,%d+,(%x+),(%x+),")
		if lac and cid then 
			str[#str+1] = lac .. "," .. cid 
		else
			str[#str+1] = " "
		end
	end

	reply = resp[6] -- AT+CGREG?
	if #reply > 0 then
		privst["AT+CGREG"] = reply
		privst["HAS_REGISTERD"] = string.find(reply, "CGREG:%s+%d+,1") and true or false
		local n1, n2 = string.match(reply, "%+CGREG:%s(%d+),(%d+)")
		if n1 and n2 then 
			str[#str+1] = n1 .. "," .. n2
		else
			str[#str+1] = " "
		end
	end

	reply = resp[7] -- AT+CSQ
	if #reply > 0 then
		privst["AT+CSQ"] = reply
		local sigl = string.match(reply, "CSQ:%s+(%d+),")
		if sigl then sigl = tonumber(sigl) end
		if type(sigl) ~= "number" then sigl = 99 end
		privst["SIGNAL_QUALITY"] = sigl
		str[#str+1] = sigl
	end

	reply = resp[8] -- AT+CGCONTRDP
	if #reply > 0 then
		privst["AT+CGCONTRDP"] = reply
	end

	reply = resp[9] -- AT+GTRNDIS?
	if #reply > 0 then
		privst["AT+GTRNDIS"] = reply
	end

	local info = cjson.encode({ ["hassim"] = str[1], ["simno"] = str[2],["iccid"] = str[3], ["operator"] = str[4], ["area"] = str[5], ["registerd"] = str[6], ["signal"] = str[7] })
	self.status = privst
	return privst, str
end

local function l610_4gmodem_status(self)
	local ttyrw = self:f_opentty()
	if not ttyrw then return false end

	local atcmds = {
		[1] = "AT+CPIN?\r\n",
		[2] = "AT+CCID\r\n",
		[3] = "AT+COPS?\r\n",
		[4] = "AT+GTCCINFO?\r\n",
		[5] = "AT+CGREG?\r\n",
		[6] = "AT+CSQ\r\n",
		[7] = "AT+CGCONTRDP\r\n",
		[8] = "AT+GTRNDIS?\r\n",
	}

	local resp = atcmd_write_array(ttyrw, atcmds, 3000)
	if g_debug then atcmd_resp_dump(atcmds, resp) end
	ttyrw:close(); ttyrw = nil
	-- resp is a table, should not be empty

	local privst, reply, str = self.status, nil, {}
	if type(privst) ~= "table" then privst = {} end

	reply = resp[1] -- AT+CPIN?
	if #reply > 0 then
		privst["AT+CPIN"] = reply
		privst["HAS_SIMCARD"] = string.find(reply, "CPIN:%s+READY") and true or false
		if string.match(reply,"%+CPIN:%s*(%w+)%s*OK") then str[#str+1] = string.match(reply,"%+CPIN:%s*(%w+)%s*OK") else str[#str+1] = "" end
	end

	str[#str+1] = "0"
	reply = resp[2] -- AT+CCID
	if #reply > 0 then
		privst["AT+CCID"] = reply
		if string.match(reply, "%+CCID:%s+([%d%a]+)") then str[#str+1] = string.match(reply, "%+CCID:%s+([%d%a]+)") else str[#str+1] = "" end
	end

	reply = resp[3] -- AT+COPS?
	if #reply > 0 then
		privst["AT+COPS"] = reply
		if string.match(reply, '"(.-)"') then str[#str+1] = string.match(reply, '"(.-)"') else str[#str+1] = "" end
	end

	reply = resp[4] -- AT+GTCCINFO?
	if #reply > 0 then
		privst["AT+GTCCINFO"] = reply
		local lac, cid = string.match(reply, "LTE service cell:.-,%d+,%d+,%d+,(%x+),(%x+),")
		if lac and cid then 
			str[#str+1] = lac .. "," .. cid 
		else
			str[#str+1] = " "
		end
	end

	reply = resp[5] -- AT+CGREG?
	if #reply > 0 then
		privst["AT+CGREG"] = reply
		privst["HAS_REGISTERD"] = string.find(reply, "CGREG:%s+%d+,1") and true or false
		local n1, n2 = string.match(reply, "%+CGREG:%s(%d+),(%d+)")
		if n1 and n2 then 
			str[#str+1] = n1 .. "," .. n2
		else
			str[#str+1] = " "
		end
	end

	reply = resp[6] -- AT+CSQ
	if #reply > 0 then
		privst["AT+CSQ"] = reply
		local sigl = string.match(reply, "CSQ:%s+(%d+),")
		if sigl then sigl = tonumber(sigl) end
		if type(sigl) ~= "number" then sigl = 99 end
		privst["SIGNAL_QUALITY"] = sigl
		str[#str+1] = sigl
	end

	reply = resp[7] -- AT+CGCONTRDP
	if #reply > 0 then
		privst["AT+CGCONTRDP"] = reply
	end

	reply = resp[8] -- AT+GTRNDIS?
	if #reply > 0 then
		privst["AT+GTRNDIS"] = reply
	end

	local info = cjson.encode({ ["hassim"] = str[1], ["simno"] = str[2],["iccid"] = str[3], ["operator"] = str[4], ["area"] = str[5], ["registerd"] = str[6], ["signal"] = str[7] })
	self.status = privst
	return privst, str
end

local function ec800g_4gmodem_status(self)
	local ttyrw = self:f_opentty()
	if not ttyrw then return false end

	local atcmds = {
		[1] = "AT+CPIN?\r\n",
		[2] = "AT+QDSIM?\r\n",
		[3] = "AT+CCID\r\n",
		[4] = "AT+CREG=2\r\n",
		[5] = "AT+CREG?\r\n",
		[6] = "AT+CGREG?\r\n",
		[7] = "AT+CSQ\r\n",
		[8] = "AT+CGCONTRDP\r\n",
		[9] = "AT+GTRNDIS?\r\n",
		[10] = "AT+COPS?\r\n",
	}

	local resp = atcmd_write_array(ttyrw, atcmds, 3000)
	if g_debug then atcmd_resp_dump(atcmds, resp) end
	ttyrw:close(); ttyrw = nil
	-- resp is a table, should not be empty

	local privst, reply, str = self.status, nil, {}
	if type(privst) ~= "table" then privst = {} end

	reply = resp[1] -- AT+CPIN?
	if #reply > 0 then
		privst["AT+CPIN"] = reply
		privst["HAS_SIMCARD"] = string.find(reply, "CPIN:%s+READY") and true or false
		if string.match(reply,"%+CPIN:%s*(%w+)%s*OK") then str[#str+1] = string.match(reply,"%+CPIN:%s*(%w+)%s*OK") else str[#str+1] = " " end
	end

	reply = resp[2] -- AT+QDSIM?
	if #reply > 0 then
		privst["AT+QDSIM"] = reply
		if string.match(reply,"%+QDSIM:%s*(%d+)") then str[#str+1] = string.match(reply,"%+QDSIM:%s*(%d+)") else str[#str+1] = "" end
	end

	reply = resp[3] -- AT+CCID
	if #reply > 0 then
		privst["AT+CCID"] = reply
		if string.match(reply, "%+CCID:%s+([%d%a]+)") then str[#str+1] = string.match(reply, "%+CCID:%s+([%d%a]+)") else str[#str+1] = "" end
	end

	reply = resp[10] -- AT+COPS?
	if #reply > 0 then
		privst["AT+COPS"] = reply
		if string.match(reply, '"(.-)"') then str[#str+1] = string.match(reply, '"(.-)"') else str[#str+1] = " " end
	end

	reply = resp[5] -- AT+CREG?
	if #reply > 0 then
		privst["AT+CREG"] = reply
		local lac, cid = string.match(reply, '"(.-)","(.-)"')
		if lac and cid then 
			str[#str+1] = lac .. "," .. cid 
		else
			str[#str+1] = " "
		end
	end

	reply = resp[6] -- AT+CGREG?
	if #reply > 0 then
		privst["AT+CGREG"] = reply
		privst["HAS_REGISTERD"] = string.find(reply, "CGREG:%s+%d+,1") and true or false
		local n1, n2 = string.match(reply, "%+CGREG:%s(%d+),(%d+)")
		if n1 and n2 then 
			str[#str+1] = n1 .. "," .. n2
		else
			str[#str+1] = " "
		end
	end

	reply = resp[7] -- AT+CSQ
	if #reply > 0 then
		privst["AT+CSQ"] = reply
		local sigl = string.match(reply, "CSQ:%s+(%d+),")
		if sigl then sigl = tonumber(sigl) end
		if type(sigl) ~= "number" then sigl = 99 end
		privst["SIGNAL_QUALITY"] = sigl
		str[#str+1] = sigl
	end

	reply = resp[8] -- AT+CGCONTRDP
	if #reply > 0 then
		privst["AT+CGCONTRDP"] = reply
	end

	reply = resp[9] -- AT+GTRNDIS?
	if #reply > 0 then
		privst["AT+GTRNDIS"] = reply
	end

	local info = cjson.encode({ ["hassim"] = str[1], ["simno"] = str[2],["iccid"] = str[3], ["operator"] = str[4], ["area"] = str[5], ["registerd"] = str[6], ["signal"] = str[7] })
	self.status = privst
	return privst, str
end

local function ec200a_connect(self)
	local ttyrw = self:f_opentty()
	if not ttyrw then return false end

	local atcmds = {
		"AT+QCFG=\"usbnet\",1\r\n",
		"AT+CFUN=1,1\r\n",
		"AT+QNETDEVCTL=3,1,1\r\n",
		"AT+QIACT=1\r\n"
	}

	local resp = atcmd_write_array(ttyrw, atcmds, 2000)
	if g_debug then atcmd_resp_dump(atcmds, resp) end
	ttyrw:close(); ttyrw = nil
	return true
end

local function ec200u_connect(self)
	local ttyrw = self:f_opentty()
	if not ttyrw then return false end

	local atcmds = {
		"AT+QCFG=\"usbnet\",1\r\n",
		"AT+QNETDEVCTL=1,1,1\r\n",
	}

	local resp = atcmd_write_array(ttyrw, atcmds, 2000)
	if g_debug then atcmd_resp_dump(atcmds, resp) end
	ttyrw:close(); ttyrw = nil
	return true
end

local function ec2x_connect(self)
	local ttyrw = self:f_opentty()
	if not ttyrw then return false end

	local atcmds = {
		"AT+QCFG=\"usbnet\",1\r\n",
		"AT+CFUN=1,1\r\n",
	}

	local resp = atcmd_write_array(ttyrw, atcmds, 2000)
	if g_debug then atcmd_resp_dump(atcmds, resp) end
	ttyrw:close(); ttyrw = nil
	return true
end

local function air724_connect(self)
	local ttyrw = self:f_opentty()
	if not ttyrw then return false end

	local atcmds = {
		"AT+ROUTEIP=192.168.250.1\r\n",
		"AT+RESET\r\n",
		"AT+SETUSB=2\r\n",
	}

	local resp = atcmd_write_array(ttyrw, atcmds, 2000)
	if g_debug then atcmd_resp_dump(atcmds, resp) end
	ttyrw:close(); ttyrw = nil
	return true
end

local function ec800g_connect(self)
	local ttyrw = self:f_opentty()
	if not ttyrw then return false end

	local atcmds = {
		"AT+QCFG=\"usbnet\",1\r\n",
		"AT+QNETDEVCTL=3,1,1\r\n",
		"AT+CFUN=1,0\r\n",
	}

	local resp = atcmd_write_array(ttyrw, atcmds, 2000)
	if g_debug then atcmd_resp_dump(atcmds, resp) end
	ttyrw:close(); ttyrw = nil
	return true
end

local function mc661_connect(self)
	local ttyrw = self:f_opentty()
	if not ttyrw then return false end

	local atcmds = {
		"AT+CGDCONT=1\r\n",
		"AT+GTRNDIS=1,1\r\n",
		"AT+CFUN=1,0\r\n",
	}

	local resp = atcmd_write_array(ttyrw, atcmds, 2000)
	if g_debug then atcmd_resp_dump(atcmds, resp) end
	ttyrw:close(); ttyrw = nil
	return true
end

local function l610_connect(self)
	local ttyrw = self:f_opentty()
	if not ttyrw then return false end

	local atcmds = {
		"AT+GTUSBMODE=32\r\n",
		"AT+CGDCONT=1\r\n",
		"AT+GTRNDIS=1,1\r\n",
		"AT+CFUN=1,0\r\n",
	}

	local resp = atcmd_write_array(ttyrw, atcmds, 2000)
	if g_debug then atcmd_resp_dump(atcmds, resp) end
	ttyrw:close(); ttyrw = nil
	return true
end

local function ec200u_4gmodem_setapn(self,apn)
	local ttyrw = self:f_opentty()
	if not ttyrw then return false end
	if not apn then apn = "internet" end

	local apncmd = "AT+CGDCONT=1,\"IPV4V6\",\"" .. apn .. "\"\r\n"
	local atcmds = {
		"AT+CFUN=0\r\n",
		"AT+CGDCONT=1\r\n",
		apncmd,
		"AT+CFUN=1\r\n",
	}

	local resp = atcmd_write_array(ttyrw, atcmds, 2000)
	if g_debug then atcmd_resp_dump(atcmds, resp) end
	ttyrw:close(); ttyrw = nil
	return true
end

local function ec200u_4gmodem_getapn(self)
	local ttyrw = self:f_opentty()
	if not ttyrw then return nil end

	ttyrw:clear()
	ttyrw:write("AT+CGDCONT?\r\n")
	local resp = ttyrw:readany(1500, "OK", "ERROR")
	if not resp then resp = "" end

	ttyrw:close(); ttyrw = nil
	local data = string.match(resp,'%+CGDCONT:%s*1%s*,%s*"[^"]+"%s*,%s*"([^"]+)"%s*')
	if data then data = data:gsub("[\r\n]+$", "") end

	if not data or string.find(resp, "ERROR") then return nil end
	return data
end

local function init_func_ec200a()
	local mobj = {
		name            = "EC200A",
		ttyindex        = 2,
		dualsim         = false,
		ttypath         = false,
		isoption1       = true,
	}

	local mtab = common_modem4g_methods()
	mtab["f_connect"]   = ec200a_connect
	setmetatable(mobj, { __index = mtab, __gc = common_4gmodem_close, })
	return mobj
end

local function init_func_ec200n()
	local mobj = {
		name            = "EC200N",
		ttyindex        = 1,
		dualsim         = false,
		ttypath         = false,
		isoption1       = true,
	}

	local mtab = common_modem4g_methods()
	mtab["f_connect"]   = ec200a_connect
	setmetatable(mobj, { __index = mtab, __gc = common_4gmodem_close, })
	return mobj
end

local function init_func_ec200t()
	local mobj = {
		name            = "EC200T",
		ttyindex        = 2,
		dualsim         = false,
		ttypath         = false,
		isoption1       = true,
	}

	local mtab = common_modem4g_methods()
	mtab["f_connect"]   = ec200a_connect
	setmetatable(mobj, { __index = mtab, __gc = common_4gmodem_close, })
	return mobj
end

local function init_func_ec200u()
	local mobj = {
		name            = "EC200U",
		ttyindex        = 0,
		dualsim         = false,
		ttypath         = false,
		isoption1       = true,
	}

	local mtab = common_modem4g_methods()
	mtab["f_connect"]   = ec200u_connect
	mtab["f_setapn"]	= ec200u_4gmodem_setapn
	mtab["f_getapn"]	= ec200u_4gmodem_getapn
	setmetatable(mobj, { __index = mtab, __gc = common_4gmodem_close, })
	return mobj
end

local function init_func_ec2x()
	local mobj = {
		name            = "EC2X",
		ttyindex        = 2,
		dualsim         = false,
		ttypath         = false,
		isoption1       = true,
	}

	local mtab = common_modem4g_methods()
	mtab["f_connect"]   = ec2x_connect
	mtab["f_setapn"]	= ec200u_4gmodem_setapn
	mtab["f_getapn"]	= ec200u_4gmodem_getapn
	setmetatable(mobj, { __index = mtab, __gc = common_4gmodem_close, })
	return mobj
end

local function init_func_ec800g()
	local mobj = {
		name            = "EC800G",
		ttyindex        = 0,
		dualsim         = true,
		ttypath         = false,
		isoption1       = true,
	}

	local mtab = common_modem4g_methods()
	mtab["f_connect"]    = ec800g_connect
	mtab["f_cardinfo"]   = ec800g_4gmodem_info
	mtab["f_switch_sim"] = ec800g_4gmodem_switchsim
	mtab["f_get_cursim"] = ec800g_4gmodem_getsim
	mtab["f_status"]	 = ec800g_4gmodem_status
	setmetatable(mobj, { __index = mtab, __gc = common_4gmodem_close, })
	return mobj
end

local function init_func_air724()
	local mobj = {
		name            = "Air724UG",
		ttyindex        = 1,
		dualsim         = false,
		ttypath         = false,
		isoption1       = true,
	}

	local mtab = common_modem4g_methods()
	mtab["f_connect"]   = air724_connect
	setmetatable(mobj, { __index = mtab, __gc = common_4gmodem_close, })
	return mobj
end

local function init_func_mc661()
	local mobj = {
		name            = "MC661",
		ttyindex        = 0,
		dualsim         = true,
		ttypath         = false,
		isoption1       = true,
	}

	local mtab = common_modem4g_methods()
	mtab["f_connect"]    = mc661_connect
	mtab["f_cardinfo"]   = mc661_4gmodem_info
	mtab["f_switch_sim"] = mc661_4gmodem_switchsim
	mtab["f_get_cursim"] = mc661_4gmodem_getsim
	mtab["f_status"]	 = mc661_4gmodem_status
	setmetatable(mobj, { __index = mtab, __gc = common_4gmodem_close, })
	return mobj
end

local function init_func_mc661_0a0a()
	local mobj = {
		name            = "MC661",
		ttyindex        = 0,
		dualsim         = false,
		ttypath         = false,
		isoption1       = true,
	}

	local mtab = common_modem4g_methods()
	mtab["f_preinit"]   = mc661_4gmodem_preinit
	setmetatable(mobj, { __index = mtab, __gc = common_4gmodem_close, })
	return mobj
end

local function init_func_l610()
	local mobj = {
		name            = "L610",
		ttyindex        = 0,
		dualsim         = false,
		ttypath         = false,
		isoption1       = true,
	}

	local mtab = common_modem4g_methods()
	mtab["f_connect"]    = l610_connect
	mtab["f_status"]	 = l610_4gmodem_status
	setmetatable(mobj, { __index = mtab, __gc = common_4gmodem_close, })
	return mobj
end

local net4g_modems = {
	["2c7c:6005"] = init_func_ec200a,
	["2c7c:6002"] = init_func_ec200n,
	["2c7c:6026"] = init_func_ec200t,
	["2c7c:0901"] = init_func_ec200u,
	["2c7c:0904"] = init_func_ec800g,
	["2c7c:0125"] = init_func_ec2x,
	["1782:4e00"] = init_func_air724,
	["2cb7:0a0a"] = init_func_mc661_0a0a,
	["2cb7:0a0b"] = init_func_mc661,
	["2cb7:0a0c"] = init_func_mc661_0a0a,
	["1782:4d11"] = init_func_l610,
}

local function detect_4gmodem()
	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 nil
	end

	-- function to create 4G modem object
	local modem_init, usbid = nil, nil
	-- process USB-ID one by one
	for uid in string.gmatch(outbuf,  "(%x%x%x%x:%x%x%x%x)") do
		modem_init = net4g_modems[uid]
		if modem_init then
			usbid = uid
			break
		end
	end

	if not modem_init then return nil end
	-- create 4G modem object:
	local modemobj = modem_init()
	if modemobj.isoption1 then -- update new_id for optioin1 driver
		local optval = string.gsub(usbid, ":", " ") .. " ff\n"
		local new_id = "/sys/bus/usb-serial/drivers/option1/new_id"
		local oldval = invoker.readfile(new_id)
		if optval ~= oldval then
			local opth = io.open(new_id, "wb")
			if opth then opth:write(optval); opth:close(); opth = nil end
			invoker.waitsec(5)
		end
	end

	return modemobj
end

if os.getenv("MODEM_DEBUG") == "1" then
	g_debug = true
end
return { ["detect"]          = detect_4gmodem,
	["SIMCARD_EXTERNAL"]     = g_SIMCARD_EXTERNAL,
	["SIMCARD_INTERNAL"]     = g_SIMCARD_INTERNAL,
	["PREINIT_AGAIN"]        = g_PREINIT_AGAIN,
	["APN_ALREADY"]          = g_APN_ALREADY,
}
