#!/usr/bin/lua -- Created by jiaqiang.ye@lnxall.com -- miscellaneous operations for various 4G modem -- 2024/08/21 local bit32 = require "bit32" local cjson = require "cjson" local posix = require "posix" local invoker = require "invoker" local mtab = {} local usbid_list = {} local omode = bit32.bor(posix.O_RDWR, posix.O_CLOEXEC, posix.O_NOCTTY, posix.O_NONBLOCK) local mfmt = string.format local g_4ginfo = "/tmp/.modem_4ginfo.json" mtab.ICCID_FILE = '/tmp/simcard_iccid' mtab.MODEM_INFO = g_4ginfo local theid = '2c7c:0904' -- EC800G usbid_list[theid] = {} usbid_list['2cb7:0a0b'] = {} usbid_list['2cb7:0a0a'] = {} usbid_list['2cb7:0a0c'] = {} local function load_quectel_info() local qh = io.open(g_4ginfo) if not qh then return nil, nil end local qd = qh:read("*a") qh:close(); qh = nil if not qd then return nil, nil end local okay, jq = pcall(cjson.decode, qd) if not okay then return nil, nil end if type(jq) ~= "table" then return nil, nil end return jq.hassim0, jq.hassim1 end local function set_baudrate(tdev) if posix.access(tdev) == 0 then invoker.invoke(invoker.NOSTDIO, 'stty', '-F', tdev, "raw", "115200", "cs8", "-hupcl", "-onlcr", "-iexten", "-echo", "-echoe", "-echok", "-echoctl", "-echoke") return true end return false end -- clear all remaining buffers local function clear_tty_cache(fd) while true do local res = invoker.readfd(fd, 8192) if not res or string.len(res) == 0 then break end end end local function tty_write(ttydev, what, msec) if not ttydev then return nil end local tfd = posix.open(ttydev, omode) if type(tfd) ~= "number" or tfd < 0 then return nil end clear_tty_cache(tfd) local res = posix.write(tfd, what) if not msec then msec = 250 end invoker.msleep(msec) invoker.closefd(tfd); tfd = -1 return res == #what end local function quectel_ec800g_checksim(ttydev) local r0, r1 = false, true local tfd = posix.open(ttydev, omode) if type(tfd) ~= "number" or tfd < 0 then return r0, r1 end clear_tty_cache(tfd) -- switch to internal SIM card posix.write(tfd, "AT+QDSIM=1\r\n") invoker.msleep(6000) posix.write(tfd, "AT+CPIN?\r\n") invoker.msleep(500) posix.write(tfd, "AT+ICCID\r\n") invoker.msleep(500) local res, res1, res0 = nil, {}, {} while true do res = invoker.readfd(tfd, 4096) if not res or string.len(res) == 0 then break end res1[#res1 + 1] = res end if #res1 > 0 then res1 = table.concat(res1) else res1 = "" end if string.find(res1, "CME ERROR: 58", 1, true) then res1 = "" end -- switch to external SIM card posix.write(tfd, "AT+QDSIM=0\r\n") invoker.msleep(6000) posix.write(tfd, "AT+CPIN?\r\n") invoker.msleep(500) posix.write(tfd, "AT+ICCID\r\n") invoker.msleep(500) while true do res = invoker.readfd(tfd, 4096) if not res or string.len(res) == 0 then break end res0[#res0 + 1] = res end invoker.closefd(tfd); tfd = -1 if #res0 > 0 then res0 = table.concat(res0) else res0 = "" end if string.find(res0, "CME ERROR: 58", 1, true) then res0 = "" end r0 = string.find(res0, "CPIN: READY", 1, true) and true or false r1 = string.find(res1, "CPIN: READY", 1, true) and true or false id0 = string.match(res0, "ICCID:%s+([%d%a]+)") id1 = string.match(res1, "ICCID:%s+([%d%a]+)") --[[ io.stdout:write(string.format("res0: %s===============================\n", res0)) io.stdout:write(string.format("res1: %s===============================\n", res1)) print("r0: ", r0 and "true" or "false") print("ID0: ", id0 and id0 or "nil") print("r1: ", r1 and "true" or "false") print("ID1: ", id1 and id1 or "nil") --]] return r0, r1, id0, id1 end local function quectel_mc661_checksim(ttydev) local r0, r1 = false, true local tfd = posix.open(ttydev, omode) if type(tfd) ~= "number" or tfd < 0 then return r0, r1 end clear_tty_cache(tfd) -- switch to internal SIM card posix.write(tfd, "AT+GTDUALSIM=1\r\n") invoker.msleep(6000) posix.write(tfd, "AT+CPIN?\r\n") invoker.msleep(500) posix.write(tfd, "AT+CCID\r\n") invoker.msleep(500) local res, res1, res0 = nil, {}, {} while true do res = invoker.readfd(tfd, 4096) if not res or string.len(res) == 0 then break end res1[#res1 + 1] = res end if #res1 > 0 then res1 = table.concat(res1) else res1 = "" end -- switch to external SIM card posix.write(tfd, "AT+GTDUALSIM=0\r\n") invoker.msleep(6000) posix.write(tfd, "AT+CPIN?\r\n") invoker.msleep(500) posix.write(tfd, "AT+CCID\r\n") invoker.msleep(500) while true do res = invoker.readfd(tfd, 4096) if not res or string.len(res) == 0 then break end res0[#res0 + 1] = res end invoker.closefd(tfd); tfd = -1 if #res0 > 0 then res0 = table.concat(res0) else res0 = "" end r0 = string.find(res0, "CPIN: READY", 1, true) and true or false r1 = string.find(res1, "CPIN: READY", 1, true) and true or false id0 = string.match(res0, "CCID:%s+([%d%a]+)") id1 = string.match(res1, "CCID:%s+([%d%a]+)") return r0, r1, id0, id1 end -- 双卡双待/双卡单待, get working SIM no local function quectel_getsimno(ttydev) if not ttydev then return nil end local tfd = posix.open(ttydev, omode) if type(tfd) ~= "number" or tfd < 0 then return nil end clear_tty_cache(tfd) posix.write(tfd, "AT+QDSIM?\r\n") invoker.msleep(1200) -- delay 1.2 second local res = nil while true do res = invoker.readfd(tfd, 8192) if not res or string.len(res) == 0 then break end local num = string.match(res, "QDSIM:[^\r\n%d]+(%d+)") --local num = string.match(res, "GTDUALSIM:[^\r\n%d]+(%d+)") if num then num = tostring(num) end if num then invoker.closefd(tfd); tfd = -1 return num end end invoker.closefd(tfd); tfd = -1 return nil end local function quectel_getsimno_mc661(ttydev) if not ttydev then return nil end local tfd = posix.open(ttydev, omode) if type(tfd) ~= "number" or tfd < 0 then return nil end clear_tty_cache(tfd) --posix.write(tfd, "AT+QDSIM?\r\n") posix.write(tfd, "AT+GTDUALSIM?\r\n") invoker.msleep(1200) -- delay 1.2 second local res = nil while true do res = invoker.readfd(tfd, 8192) if not res or string.len(res) == 0 then break end --local num = string.match(res, "QDSIM:[^\r\n%d]+(%d+)") local num = string.match(res, "GTDUALSIM:[^\r\n%d]+(%d+)") if num then num = tostring(num) end if num then invoker.closefd(tfd); tfd = -1 return num end end invoker.closefd(tfd); tfd = -1 return nil end -- 双卡双待/双卡单待, set working SIM no local function quectel_setsimno(ttydev, id, disc) if not ttydev then return nil end local tfd = posix.open(ttydev, omode) if type(tfd) ~= "number" or tfd < 0 then return nil end if disc then posix.write(tfd, "AT+QIDEACT=1\r\n") invoker.msleep(600) -- delay 0.6 second end clear_tty_cache(tfd) posix.write(tfd, mfmt("AT+QDSIM=%d\r\n", id)) invoker.msleep(2500) -- delay 2.5 second local res = invoker.readfd(tfd, 4096) invoker.closefd(tfd); tfd = -1 if type(res) == "string" and string.find(res, "OK") then return true end return false end local function quectel_setsimno_mc661(ttydev, id, disc) if not ttydev then return nil end local tfd = posix.open(ttydev, omode) if type(tfd) ~= "number" or tfd < 0 then return nil end if disc then posix.write(tfd, "AT+QIDEACT=1\r\n") invoker.msleep(600) -- delay 0.6 second end clear_tty_cache(tfd) posix.write(tfd, mfmt("AT+GTDUALSIM=%d\r\n", id)) invoker.msleep(2500) -- delay 2.5 second local res = invoker.readfd(tfd, 4096) invoker.closefd(tfd); tfd = -1 if type(res) == "string" and string.find(res, "OK") then return true end return false end usbid_list[theid]["init"] = function (self, ttydev) if self.nokaycnt == nil then self.cursim = 0 self.nokaycnt = 0 end local finit = false posix.unlink(mtab.ICCID_FILE) -- remove existing SIM ICCID file if posix.access(g_4ginfo) ~= 0 then finit = true end if self.hassim0 == nil then self.hassim0, self.hassim1 = load_quectel_info() if self.hassim0 == nil then finit = true end end set_baudrate(ttydev) if finit then tty_write(ttydev, "AT+QDSTYPE=0\r\n") -- work in 双卡单待 mode tty_write(ttydev, 'AT+QDSIMCFG="dsss",1\r\n') tty_write(ttydev, "AT+CFUN=1,1\r\n") -- take effect invoker.msleep(6000); set_baudrate(ttydev) local iccid0, iccid1 = nil, nil self.hassim0, self.hassim1, iccid0, iccid1 = quectel_ec800g_checksim(ttydev) local qinfo = cjson.encode({ ["hassim0"] = self.hassim0, ["hassim1"] = self.hassim1, ["ICCID0"] = iccid0, ["ICCID1"] = iccid1 }) local tagh = io.open(g_4ginfo, "wb") if tagh then tagh:write(qinfo) tagh:close(); tagh = nil end end -- check whether external SIM card exists if self.hassim0 then if finit then -- select external SIM card tty_write(ttydev, "AT+QDSIM=0\r\n") invoker.msleep(2500) end io.stdout:write("Found external SIM card.\n") io.stdout:flush() else io.stdout:write("External SIM card not found.\n") io.stdout:flush() tty_write(ttydev, "AT+QDSIM=1\r\n") -- select internal SIM card invoker.msleep(2500) end local simno = quectel_getsimno(ttydev) if simno then self.cursim = simno io.stdout:write(mfmt("Current SIMcard in use: %d\n", simno)) io.stdout:flush() return true end return false end usbid_list[theid]["update"] = function (self, ttydev, netokay) if netokay then if self.nokaycnt > 0 then self.nokaycnt = 0 end return true end set_baudrate(ttydev) posix.unlink(mtab.ICCID_FILE) self.nokaycnt = self.nokaycnt + 1 if self.nokaycnt >= 0x2 then local newno = self.cursim == 1 and 0 or 1 quectel_setsimno(ttydev, newno, true) local simno = quectel_getsimno(ttydev) or -1 self.nokaycnt = 0 self.cursim = newno io.stdout:write(mfmt("Updated new SIM card: %d, %d\n", newno, simno)) io.stdout:flush() else local simno = quectel_getsimno(ttydev) or -1 if simno == self.cursim then quectel_setsimno(ttydev, self.cursim, true) io.stdout:write(mfmt("Current SIM card no: %d\n", self.cursim)) else io.stdout:write(mfmt("Updated old SIM card: %d\n", self.cursim)) end io.stdout:flush() end return false end usbid_list['2cb7:0a0c']['init'] = function (self, ttydev) -- write AT+GTUSBMODE=74 AT+CFUN=1,1 tty_write(ttydev, "AT+GTUSBMODE=74\r\n") tty_write(ttydev, "AT+CFUN=1,1\r\n") invoker.msleep(6000) end usbid_list['2cb7:0a0a']['init'] = usbid_list['2cb7:0a0c']['init'] usbid_list['2cb7:0a0b']['init'] = function (self, ttydev) -- Read SIM ICCID if self.nokaycnt == nil then self.cursim = 0 self.nokaycnt = 0 end posix.unlink(mtab.ICCID_FILE) -- remove existing SIM ICCID file set_baudrate(ttydev) local iccid0, iccid1 = nil, nil self.hassim0, self.hassim1, iccid0, iccid1 = quectel_mc661_checksim(ttydev) local qinfo = cjson.encode({ ["hassim0"] = self.hassim0, ["hassim1"] = self.hassim1, ["ICCID0"] = iccid0, ["ICCID1"] = iccid1 }) local tagh = io.open(g_4ginfo, "wb") if tagh then tagh:write(qinfo) tagh:close(); tagh = nil end -- check external sim CARD, switch to external SIM chard if exists if self.hassim0 then tty_write(ttydev, "AT+GTDUALSIM=0\r\n") invoker.msleep(2500) io.stdout:write("Found external SIM card.\n") io.stdout:flush() else io.stdout:write("External SIM card not found.\n") io.stdout:flush() tty_write(ttydev, "AT+GTDUALSIM=1\r\n") -- select internal SIM card invoker.msleep(2500) end local simno = quectel_getsimno_mc661(ttydev) if simno then self.cursim = simno io.stdout:write(mfmt("Current SIMcard in use: %d\n", simno)) io.stdout:flush() return true end return false end usbid_list['2cb7:0a0c']['update'] = function (self, ttydev, netokay) os.exit(1) end usbid_list['2cb7:0a0a']['update'] = usbid_list['2cb7:0a0c']['update'] usbid_list['2cb7:0a0b']['update'] = function (self, ttydev, netokay) if netokay then if self.nokaycnt > 0 then self.nokaycnt = 0 end return true end set_baudrate(ttydev) posix.unlink(mtab.ICCID_FILE) self.nokaycnt = self.nokaycnt + 1 if self.nokaycnt >= 0x2 then local newno = self.cursim == 1 and 0 or 1 quectel_setsimno_mc661(ttydev, newno, true) local simno = quectel_getsimno(ttydev) or -1 self.nokaycnt = 0 self.cursim = newno io.stdout:write(mfmt("Updated new SIM card: %d, %d\n", newno, simno)) io.stdout:flush() else local simno = quectel_getsimno(ttydev) or -1 if simno == self.cursim then quectel_setsimno_mc661(ttydev, self.cursim, true) io.stdout:write(mfmt("Current SIM card no: %d\n", self.cursim)) else io.stdout:write(mfmt("Updated old SIM card: %d\n", self.cursim)) end io.stdout:flush() end return false end mtab.getmodem = function (usbid) if type(usbid) == "string" then return usbid_list[usbid] end return nil end mtab.hassimcard = function (ttydev) if posix.access(ttydev) ~= 0 then return nil end local tfd = posix.open(ttydev, omode) if type(tfd) ~= "number" or tfd < 0 then io.stderr:write(mfmt("Error, failed to open: %s\n", ttydev)) io.stderr:flush() return false end clear_tty_cache(tfd) posix.write(tfd, "AT+CPIN?\r\n") invoker.msleep(1200) -- delay 1.2 second while true do local res = invoker.readfd(tfd, 8192) if not res or string.len(res) == 0 then break end -- io.stdout:write(mfmt("has CPIN: %s\n", res)) -- io.stdout:flush() if string.find(res, "CPIN:%s+READY") then invoker.closefd(tfd); tfd = -1 return true end end invoker.closefd(tfd); tfd = -1 return false end return mtab