#!/usr/bin/env python3 # -*- coding: utf-8 -*- # (c) B.Kerler 2018-2024 under GPLv3 license # If you use my code, make sure you refer to my name # # !!!!! If you use this code in commercial products, your product is automatically # GPLv3 and has to be open sourced under GPLv3 as well. !!!!! import time import serial import serial.tools.list_ports import argparse import requests from Exscript.protocols.telnetlib import Telnet import usb.core from enum import Enum import os, sys, inspect current_dir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) sys.path.insert(0, current_dir) try: from edlclient.Tools.qc_diag import qcdiag except ImportError as e: print(current_dir) from qc_diag import qcdiag pass try: from edlclient.Library.utils import LogBase except ImportError as e: from Library.utils import LogBase class vendor(Enum): sierra = 0x1199 quectel = 0x2c7c zte = 0x19d2 netgear = 0x0846 telit = 0x413c class deviceclass: vid = 0 pid = 0 def __init__(self, vid, pid): self.vid = vid self.pid = pid class connection(metaclass=LogBase): def __init__(self, port=""): self.serial = None self.tn = None self.connected = False if port == "": port = self.detect(port) if port == "": try: self.tn = Telnet("192.168.1.1", 5510) self.connected = True except: self.connected = False if port != "": self.serial = serial.Serial(port=port, baudrate=115200, bytesize=8, parity='N', stopbits=1, timeout=1) self.connected = self.serial.is_open def waitforusb(self, vid, pid): timeout = 0 while timeout < 10: for device in self.detectusbdevices(): if device.vid == vid: if device.pid == pid: return True time.sleep(1) timeout += 1 return False def websend(self, url): headers = {'Referer': 'http://192.168.0.1/index.html', 'Accept-Charset': 'UTF-8'} r = requests.get(url, headers=headers) if b"FACTORY:ok" in r.content or b"success" in r.content: print( f"Detected a ZTE in web mode .... switching mode success (convert back by sending \"AT+ZCDRUN=F\" via AT port)") return self.waitforusb(vendor.zte.value, 0x0016) return False def getserialports(self): return [port for port in serial.tools.list_ports.comports()] def detectusbdevices(self): dev = usb.core.find(find_all=True) ids = [deviceclass(cfg.idVendor, cfg.idProduct) for cfg in dev] return ids def detect(self, port): vendortable = { 0x1199: ["Sierra Wireless", 3], 0x2c7c: ["Quectel", 3], 0x19d2: ["ZTE", 2], 0x0846: ["Netgear", 2], 0x413c: ["Telit", 0] } mode = "Unknown" for device in self.detectusbdevices(): if device.vid == vendor.zte.value: if device.pid == 0x0016: print(f"Detected a {vendortable[device.vid][0]} device with pid {hex(device.pid)} in Diag mode") mode = "AT" break elif device.pid == 0x1403: print(f"Detected a {vendortable[device.vid][0]} device with pid {hex(device.pid)} in Web mode") mode = "Web" # url = 'http://192.168.0.1/goform/goform_set_cmd_process?goformId=USB_MODE_SWITCH&usb_mode=1' #adb url = 'http://192.168.0.1/goform/goform_process?goformId=MODE_SWITCH&switchCmd=FACTORY' if self.websend(url): mode = "AT" break elif device.vid == vendor.telit.value: if device.pid == 0x81d7: print(f"Detected a {vendortable[device.vid][0]} device with pid {hex(device.pid)} in Diag mode") print("Sending download mode command") interface = 5 diag = qcdiag(loglevel=self.__logger.level, portconfig=[[0x413c, 0x81d7, interface]]) if diag.connect(): data = diag.hdlc.receive_reply() res = diag.send(b"\x4b\x65\x01\x00") if res[0] == 0x4B: print("Sending download mode succeeded") diag.disconnect() break if mode == "AT" or mode == "Unknown": for port in self.getserialports(): if port.vid in vendortable: portid = port.location[-1:] if int(portid) == vendortable[port.vid][1]: print(f"Detected a {vendortable[port.vid][0]} at interface at: " + port.device) return port.device return "" def readreply(self): info = [] timeout = 0 if self.serial is not None: while True: tmp = self.serial.readline().decode('utf-8').replace('\r', '').replace('\n', '') if "OK" in tmp: info.append(tmp) return info elif "ERROR" in tmp: return -1 if tmp != "": info.append(tmp) else: timeout += 1 if timeout == 20: break return info def send(self, cmd): if self.tn is not None: self.tn.write(bytes(cmd + "\r", 'utf-8')) time.sleep(0.05) data = "" while True: tmp = self.tn.read_eager() if tmp != b"": data += tmp.strip().decode('utf-8') else: break return data.split("\r\n") elif self.serial is not None: self.serial.write(bytes(cmd + "\r", 'utf-8')) time.sleep(0.05) return self.readreply() def close(self): if self.tn is not None: self.tn.close() self.connected = False if self.serial is not None: self.serial.close() self.connected = False def ati(self): data = {} info = self.send("ATI") if info != -1: for line in info: if "Revision" in line: data["revision"] = line.split(":")[1].strip() if "Model" in line: data["model"] = line.split(":")[1].strip() if "Quectel" in line: data["vendor"] = "Quectel" if "Manufacturer" in line: data["manufacturer"] = line.split(":")[1].strip() if "Sierra Wireless" in data["manufacturer"]: data["vendor"] = "Sierra Wireless" elif "ZTE CORPORATION" in data["manufacturer"]: data["vendor"] = "ZTE" elif "Netgear" in data["manufacturer"]: data["vendor"] = "Netgear" elif "Telit" in data["manufacturer"]: data["vendor"] = "Telit" return data class dwnloadtools(metaclass=LogBase): def sendcmd(self, tn, cmd): tn.write(bytes(cmd, 'utf-8') + b"\n") time.sleep(0.05) return tn.read_eager().strip().decode('utf-8') def run(self, args): port = args.port cn = connection(port) if cn.connected: info = cn.ati() if "vendor" in info: if info["vendor"] == "Sierra Wireless" or info["vendor"] == "Netgear": print("Sending download mode command") print(cn.send("AT!BOOTHOLD\r")) print(cn.send('AT!QPSTDLOAD\r')) print("Done switching to download mode") elif info["vendor"] == "Quectel": print("Sending download mode command") interface = 0 diag = qcdiag(loglevel=self.__logger.level, portconfig=[[0x2c7c, 0x0125, interface]]) if diag.connect(): diag.hdlc.receive_reply() res = diag.send(b"\x4b\x65\x01\x00") diag.disconnect() print("Done switching to download mode") elif info["vendor"] == "Telit": print("Sending download mode command") interface = 0 diag = qcdiag(loglevel=self.__logger.level, portconfig=[[0x2c7c, 0x0125, interface]]) if diag.connect(): diag.hdlc.receive_reply() res = diag.send(b"\x4b\x65\x01\x00") diag.disconnect() print("Done switching to download mode") elif info["vendor"] == "ZTE": print("Sending download mode command") interface = 0 diag = qcdiag(loglevel=self.__logger.level, portconfig=[[0x19d2, 0x0016, interface]]) if diag.connect(): diag.hdlc.receive_reply() res = diag.send(b"\x4b\x65\x01\x00") if res[0] == 0x4B: print("Done switching to ENANDPRG mode") else: res = diag.send(b"\x3a") if res[0] == 0x3A: while True: state = cn.waitforusb(vendor.zte.value, 0x0076) if not state: diag.disconnect() if diag.connect(): res = diag.send(b"\x3a") else: break if state: print("Done switching to NANDPRG mode") else: print("Failed switching to download mode") diag.disconnect() cn.close() def main(): version = "1.1" info = 'Modem Gimme-EDL ' + version + ' (c) B. Kerler 2020-2021' parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter, description=info) parser.add_argument( '-port', '-p', help='use com port for auto unlock', default="") parser.add_argument( '-logfile', '-l', help='use logfile for debug log', default="") args = parser.parse_args() if not args.port: parser.print_help() return dw = dwnloadtools() dw.run(args) if __name__ == "__main__": main()