#!/usr/bin/env python3 # -*- coding: utf-8 -*- # (c) B.Kerler 2018-2023 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() dw=dwnloadtools() dw.run(args) if __name__=="__main__": main()