#!/opt/lnxall_app/bin/lua local bit = require 'bit' local posix = require 'posix' local cjson = require 'cjson' local invoker = require 'invoker' local g_tty = { [1] = '/dev/ttymxc1', [2] = '/dev/ttymxc2', [3] = '/dev/ttymxc3', [4] = '/dev/ttymxc4', } local function netdev_hasipv4(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 io.stderr:write(string.format("Error, no ipv4 found for '%s'\n", ndev)) io.stderr:flush() return false end io.stdout:write(string.format("IPv4 address found for '%s': %s\n", ndev, ipaddr)) io.stdout:flush() return true end -- CRC-16 Modbus (polynomial 0x8005, reversed 0xA001) local function crc16_modbus(data) if not data then return nil end local crc = 0xFFFF for i = 1, #data do crc = bit.bxor(crc, string.byte(data, i)) for j = 1, 8 do if bit.band(crc, 0x0001) ~= 0 then crc = bit.rshift(crc, 1) crc = bit.bxor(crc, 0xA001) else crc = bit.rshift(crc, 1) end end end return string.char(bit.band(crc, 0xFF), bit.band(bit.rshift(crc, 8), 0xFF)) end local function test_rs485(p0, p1) local wtf = posix.stat(p0) local S_IFMT = 0XF000 local S_IFCHR = 0x2000 if type(wtf) ~= "table" or not wtf.st_mode or bit.band(wtf.st_mode, S_IFMT) ~= S_IFCHR then io.stderr:write(string.format("Error, invalid RS485 device: %s\n", p0)) io.stderr:flush() return false end wtf = posix.stat(p1) if type(wtf) ~= "table" or not wtf.st_mode or bit.band(wtf.st_mode, S_IFMT) ~= S_IFCHR then io.stderr:write(string.format("Error, invalid RS485 device: %s\n", p1)) io.stderr:flush() return false end -- for debian based system, we have a very strong `stty invoker.invoke(0, 'stty', '-F', p0, "raw", "115200", "cs8", "-hupcl", "-onlcr", "-iexten", "-echo", "-echoe", "-echok", "-echoctl", "-echoke", "-parenb") invoker.invoke(0, 'stty', '-F', p1, "raw", "115200", "cs8", "-hupcl", "-onlcr", "-iexten", "-echo", "-echoe", "-echok", "-echoctl", "-echoke", "-parenb") local rawstr = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' local rstr = invoker.randstr(0x8, rawstr) local crc = crc16_modbus(rstr) local crchex = string.format("%02X%02X", string.byte(crc, 1), string.byte(crc, 2)) local tmpfile = "/tmp/tmp-" .. rstr local catpid = invoker.invoke(invoker.NOWAIT, "/bin/sh", "-c", string.format("TTY_READ=1 exec tty_rw %s > %s", p0, tmpfile)) if type(catpid) ~= "number" then io.stderr:write(string.format("Error, failed to execute 'cat %s'\n", p0)) io.stderr:flush() posix.unlink(tmpfile) return false end invoker.msleep(100) invoker.invoke(0, 'tty_rw', p1, rstr .. crchex) invoker.msleep(200) invoker.kill(catpid, posix.SIGKILL) invoker.kill(catpid, posix.SIGTERM) invoker.waitpid(catpid) local output = invoker.readfile(tmpfile, invoker.TRIMEND) posix.unlink(tmpfile) if type(output) ~= "string" then io.stdout:write(string.format("Error, test RS485 port has failed: %s => %s\n", p1, p0)) io.stdout:write(string.format("\tNo output received\n")) io.stdout:flush() return false end local datalen = #output - 4 if datalen < 1 then io.stdout:write(string.format("Error, test RS485 port has failed: %s => %s\n", p1, p0)) io.stdout:write(string.format("\tReceived data too short: %d bytes\n", #output)) io.stdout:flush() return false end local recv_data = string.sub(output, 1, datalen) local recv_hex = string.sub(output, -4) if type(recv_data) ~= "string" or type(recv_hex) ~= "string" or not string.find(recv_data, rstr, 1, true) then io.stdout:write(string.format("Error, test RS485 port has failed: %s => %s\n", p1, p0)) if type(recv_data) == "string" then io.stdout:write(string.format("\tExpected: %s, output: %s\n", rstr, recv_data)) end io.stdout:flush() return false end local recv_crc = string.char(tonumber(string.sub(recv_hex, 1, 2), 16), tonumber(string.sub(recv_hex, 3, 4), 16)) local expected_crc = crc16_modbus(recv_data) if not recv_crc or not expected_crc or recv_crc ~= expected_crc then io.stdout:write(string.format("Error, test RS485 port has failed: %s => %s\n", p1, p0)) if type(expected_crc) == "string" and type(recv_crc) == "string" then io.stdout:write(string.format("\tCRC mismatch: expected 0x%02X%02X, received 0x%02X%02X\n", string.byte(expected_crc, 1), string.byte(expected_crc, 2), string.byte(recv_crc, 1), string.byte(recv_crc, 2))) end io.stdout:flush() return false end io.stdout:write(string.format("INFO: test RS485 port OKAY: %s => %s\n", p1, p0)) io.stdout:write(string.format("\tExpected: %s, output: %s, CRC OK\n", rstr, recv_data)) io.stdout:flush() return true end local function update_resolv() local res = invoker.readfile('/etc/resolv.conf') local myres = "nameserver 114.114.114.114\nnameserver 8.8.8.8\n" if res ~= myres then io.stdout:write("Updating /etc/resolv.conf...\n") io.stdout:flush() local filp = io.open('/etc/resolv.conf', 'wb') if filp then filp:write(myres) filp:close() filp = nil end end return true end local function ping_with(ifname, host) if not host then host = 'mqtt.lnxall.com' end if not netdev_hasipv4(ifname) then return false end io.stdout:write(string.format("Ping '%s' with interface '%s'...\n", host, ifname)) io.stdout:flush() local cmds = { "ping", "-c10", "-w10", "-I", ifname, host } local _, output = invoker.invoke(invoker.OUTPUT + invoker.NOSTDIO, cmds) if type(output) == "string" and string.find(output, "packet loss", 1, true) then if string.find(output, "100% packet loss", 1, true) then return false end return true end return false end local function get_lan_host() local ipaddr = {} local arpfile = io.open("/proc/net/arp", "rb") if not arpfile then io.stderr:write("Error, failed to open arp file.\n") io.stderr:flush() return ipaddr end for line in arpfile:lines() do if string.find(line, "br-lan", 1, true) then local ip = string.match(line, "^([%d%.]+)") if invoker.isipv4(ip) then ipaddr[#ipaddr + 1] = ip end end end if #ipaddr == 0 then io.stderr:write("Error, no device connected to LAN\n") io.stderr:flush() end return ipaddr end local function findtype(text, info) local start = string.find(text, info) if start then start_quote = string.find(text, '"', start) end_quote = string.find(text, '"', start_quote + 1) if start_quote and end_quote then final_type = string.sub(text, start_quote + 1, end_quote - 1) end return final_type else return "can't get info" end end local function findnumber(text, info) local start = string.find(text, info) if start then start = start + string.len(info) + 1 end_n = string.find(text, "\n", start) if end_n then final_number = string.sub(text, start, end_n - 1) end return final_number else return "can't get info" end end local function get_moudle_info(datj) print("MOUDLE INFO SETTINGS TEST START!") local eval,finfo = invoker.invoke(invoker.OUTPUT,"/app/moudleinfo") if eval and type(finfo) == "string" then Producer = findtype(finfo,"Producer:") if Producer == "no_modem" then datj["MOUDLE_INFO"] = nil else IMEI = findnumber(finfo,"imei info:") ICCID = findnumber(finfo,"iccid info:") SIGNAL = findnumber(finfo,"signal info:") if #ICCID < 20 then ICCID = "no sim card" sim_SP = "no sim card" else sim_SP_number = findnumber(finfo,"+COPS:") if #sim_SP_number > 10 then sim_SP = findtype(finfo,"+COPS:") else sim_SP = "sim card locked " end end if Producer == "Quectel" then _,_,moudle_4G_type = string.find(finfo,'Revision: (%w+)') end if Producer == "AirM2M" then moudle_4G_type = findtype(finfo,"+CGMM:") end end datj["MOUDLE_INFO"] = { ["IMEI"] = IMEI, ["moudle_4G_sp"] = Producer, ["moudle_4G_type"] = moudle_4G_type, ["iccid"] = ICCID, ["sim_sp"] = sim_SP, ["signal"] = SIGNAL } else print("MOUDLE INFO SETTINGS TEST FAILED!!") return false end print(datj["MOUDLE_INFO"]["IMEI"]) print(datj["MOUDLE_INFO"]["moudle_4G_sp"]) print(datj["MOUDLE_INFO"]["moudle_4G_type"]) print(datj["MOUDLE_INFO"]["iccid"]) print(datj["MOUDLE_INFO"]["sim_sp"]) print(datj["MOUDLE_INFO"]["signal"]) print("MOUDLE INFO SETTINGS TEST OK") return true end local function test_led() local result = {} local leds = { "led-internet", "led-heart", "led-lora" } for idx = 1, 8 do local wval = string.format("%d\n", bit.band(idx, 0x1)) for _, led in ipairs(leds) do local okay = false local ledp = string.format("/sys/class/leds/%s/brightness", led) if posix.access(ledp) == 0 then local lfile = io.open(ledp, "wb") if lfile then okay = true lfile:write(wval) lfile:close() lfile = nil end end if okay then result[led] = { interfaces = led, error_code = 0, error_msg = "success" } else result[led] = { interfaces = led, error_code = 1, error_msg = string.format("led_%s not found", led) } end end invoker.msleep(250) end local result_led = {} result_led[1] = result["led-internet"] result_led[2] = result["led-heart"] result_led[3] = result["led-lora"] return result_led end local function write_random_file(filepath) local n = 1 local filename = filepath .. ".bin" while true do if not posix.stat(filename) then break end filename = filepath .. "_" .. n .. ".bin" n = n + 1 io.stdout:write("File exists, using filename: " .. filename .. "\n") io.stdout:flush() end local orig_md5_cmd = "dd if=/dev/urandom bs=8192 count=1 2>/dev/null | tee " .. filename .. " | md5sum - | awk '{print $1}'" local orig_md5 = io.popen(orig_md5_cmd):read("*a"):gsub("\n", "") os.execute("sync") os.execute("echo 3 > /proc/sys/vm/drop_caches") local file = io.open(filename, "rb") if not file then io.stdout:write("Failed to open file: " .. filename .. "\n") io.stdout:flush() return false end file:seek("end") local size = file:seek() file:close() if size ~= 8192 then io.stdout:write("File size is not 8192 bytes: " .. filename .. "\n") io.stdout:flush() return false end local written_md5_cmd = "md5sum " .. filename .. " | awk '{print $1}'" local written_md5 = io.popen(written_md5_cmd):read("*a"):gsub("\n", "") if orig_md5 ~= written_md5 then io.stdout:write("File content is not the same: " .. filename .. "\n") io.stdout:flush() return false end os.execute("rm -f " .. filename) io.stdout:write(string.format("%s write success.\n",filename)) io.stdout:flush() return true end local function test_mmc(level) local device = "/dev/mmcblk1" if not posix.stat('/dev/mmcblk1') or not posix.stat('/dev/mmcblk1')['type'] == 'block device' then io.stdout:write("Error, not found mmc device\n") io.stdout:flush() return false end local okay, output = invoker.invoke(invoker.OUTPUT, "mmc", "extcsd", "read", device) if okay ~= 0 or type(output) ~= "string" then io.stdout:write("Error, failed to read MMC extcsd\n") io.stdout:flush() return false end local value_A = string.match(output, "Life Time Estimation A.-:%s*0x([%x]+)") local value_B = string.match(output, "Life Time Estimation B.-:%s*0x([%x]+)") local dec_value_A = value_A and tonumber(value_A, 16) or 0 local dec_value_B = value_B and tonumber(value_B, 16) or 0 local percent_A = dec_value_A * 10 local percent_B = dec_value_B * 10 if percent_A and percent_B then io.stdout:write(string.format("MMC result - percent_A: %d%%, percent_B: %d%%\n", percent_A, percent_B)) io.stdout:flush() if level == "1" and percent_A <= 10 and percent_B <= 10 then return true end if level == "0" and percent_A <= 70 and percent_B <= 70 then return true end end io.stdout:write("Failed to read MMC extcsd or MMC not health.\n") io.stdout:flush() return false end local function maindiag(arg1,arg2) local finalsummary = {} local summary = {} if not arg2 then io.stdout:write("Invaild input, set mmc/ssd lever to 10%.\n") io.stdout:flush() arg2 = "1" end local okay, facini = invoker.invoke(invoker.OUTPUT, "factory", "get") if okay ~= 0 or type(facini) ~= "string" then io.stderr:write("Error, 'factory get' has failed.\n") io.stderr:flush() return 1 end local gwsn = string.match(facini, "SN=([^\r\n]+)") local hwver = string.match(facini, "HWVER=([^\r\n]+)") or "" local macaddr = string.match(facini, "MAC=([^\r\n]+)") or "" local gwpro = string.match(facini, "PRODUCT=([^\r\n]+)") if not gwsn or not gwpro then io.stderr:write("Error, gateway SN or PRODUCT not found.\n") io.stderr:flush() return 2 end local softver = invoker.readfile("/lib/dyiot/issue", invoker.TRIMEND) if type(softver) ~= "string" or #softver == 0 then softver = invoker.readfile("/etc/issue", invoker.TRIMEND) if type(softver) ~= "string" or #softver == 0 then softver = "unknown" end end finalsummary["sn"] = gwsn finalsummary["timestamp"] = os.time() finalsummary["MAC"] = macaddr finalsummary["HWVER"] = hwver finalsummary["SWVER"] = softver summary["ioDetect"] = test_led() local ports = {} if test_rs485(g_tty[1], g_tty[2]) then ports[#ports + 1] = { interfaces = "RS485_1", error_msg = "success", error_code = 0 } else ports[#ports + 1] = { interfaces = "RS485_1", error_msg = "CANNOT WORK", error_code = 1 } end if test_rs485(g_tty[2], g_tty[1]) then ports[#ports + 1] = { interfaces = "RS485_2", error_msg = "success", error_code = 0 } else ports[#ports + 1] = { interfaces = "RS485_2", error_msg = "CANNOT WORK", error_code = 1 } end if test_rs485(g_tty[3], g_tty[4]) then ports[#ports + 1] = { interfaces = "RS485_3", error_msg = "success", error_code = 0 } else ports[#ports + 1] = { interfaces = "RS485_3", error_msg = "CANNOT WORK", error_code = 1 } end if test_rs485(g_tty[4], g_tty[3]) then ports[#ports + 1] = { interfaces = "RS485_4", error_msg = "success", error_code = 0 } else ports[#ports + 1] = { interfaces = "RS485_4", error_msg = "CANNOT WORK", error_code = 1 } end if posix.access('/dev/rtc0') == 0 then ports[#ports + 1] = { interfaces = "RTC", error_msg = "success", error_code = 0 } else ports[#ports + 1] = { interfaces = "RTC", error_msg = "RTC0 NOT FOUND", error_code = 1 } end if invoker.mountpoint('/mnt/sdisk') then ports[#ports + 1] = { interfaces = "SDCard", error_msg = "success", error_code = 0 } else ports[#ports + 1] = { interfaces = "SDCard", error_msg = "SDCard mount Error", error_code = 1 } end if test_mmc(arg2) and write_random_file("/random_data") then ports[#ports + 1] = { interfaces = "MMC", error_msg = "success", error_code = 0 } else ports[#ports + 1] = { interfaces = "MMC", error_msg = "NOT OKAY", error_code = 1 } end summary["OnBoardPort"] = ports local netw = {} update_resolv() if ping_with('eth0') or ping_with('eth0', '8.8.8.8') or ping_with('eth0', '114.114.114.114') then netw[#netw + 1] = { interfaces = "WAN", error_msg = "success", error_code = 0 } else netw[#netw + 1] = { interfaces = "WAN", error_msg = "NOT OKAY", error_code = 1 } end update_resolv() if string.match(gwpro,"4G") then if ping_with('usb0') or ping_with('usb0', '8.8.8.8') or ping_with('usb0', '114.114.114.114') then netw[#netw + 1] = { interfaces = "4G-Net", error_msg = "success", error_code = 0 } else netw[#netw + 1] = { interfaces = "4G-Net", error_msg = "NOT OKAY", error_code = 1 } end end local landevs, lanokay = get_lan_host(), false for _, ip in pairs(landevs) do if ping_with('br-lan', ip) then lanokay = true break end end if lanokay then netw[#netw + 1] = { interfaces = "LAN", error_msg = "success", error_code = 0 } else netw[#netw + 1] = { interfaces = "LAN", error_msg = "NOT OKAY", error_code = 1 } end if invoker.invoke(invoker.OUTPUT,"ip", "link", "show", "dev", "wlan0") == 0 then netw[#netw + 1] = { interfaces = "wlan0", error_msg = "success", error_code = 0} else netw[#netw + 1] = { interfaces = "wlan0", error_msg = "NOT OKAY", error_code = 1} end summary["NetWork"] = netw get_moudle_info(finalsummary) finalsummary["data"] = summary io.stdout:write("\n---TEST SUMMARY---\ntest_result:") io.stdout:write(cjson.encode(finalsummary)) io.stdout:write("\n") io.stdout:flush() end posix.setenv("PATH", "/opt/lnxall_app/bin:/usr/bin:/usr/sbin:/bin:/sbin:/lib/dyiot/bin/", true) maindiag(arg[1],arg[2]) os.exit(0)