Exemple #1
0
class MainWindow(QMainWindow):
    errorSignal = pyqtSignal(str, str)
    hintSignal = pyqtSignal(str, str)
    updateProgressSignal = pyqtSignal(str, int, int, str)
    updateProgressPrintSignal = pyqtSignal(str)
    showSerialComboboxSignal = pyqtSignal()
    downloadResultSignal = pyqtSignal(bool, str)
    DataPath = "./"
    app = None
    firmware_start_bytes = [b'\x21\xa8', b'\xef\xbe', b'\xad\xde']

    def __init__(self, app):
        super().__init__()
        self.app = app
        self.programStartGetSavedParameters()
        self.initVar()
        self.initWindow()
        self.initEvent()
        self.updateFrameParams()

    def __del__(self):
        pass

    def initVar(self):
        self.burning = False
        self.isDetectSerialPort = False
        self.DataPath = parameters.dataPath
        self.kflash = KFlash(print_callback=self.kflash_py_printCallback)
        self.saveKfpkDir = ""
        self.packing = False
        self.zipTempFiles = []
        self.fileSelectWidgets = []

    def setWindowSize(self, w=520, h=550):
        self.resize(w, h)

    def setFileSelectItemLayout(self, item, isKfpkg):
        if isKfpkg:
            item[4].hide()
            item[2].setStretch(0, 1)
            item[2].setStretch(1, 12)
            item[2].setStretch(3, 4)
            item[2].setStretch(4, 1)
        else:
            item[4].show()
            item[2].setStretch(0, 1)
            item[2].setStretch(1, 8)
            item[2].setStretch(2, 4)
            item[2].setStretch(3, 4)
            item[2].setStretch(4, 1)

    def addFileSelectionItem(self):
        enableCheckbox = QCheckBox()
        filePathWidget = QLineEdit()
        fileBurnAddrWidget = QLineEdit("0x00000")
        openFileButton = QPushButton(tr("OpenFile"))
        removeButton = QPushButton()
        removeButton.setProperty("class", "remove_file_selection")
        oneFilePathWidget = QWidget()
        oneFilePathWidgetLayout = QHBoxLayout()
        oneFilePathWidget.setLayout(oneFilePathWidgetLayout)
        oneFilePathWidgetLayout.addWidget(enableCheckbox)
        oneFilePathWidgetLayout.addWidget(filePathWidget)
        oneFilePathWidgetLayout.addWidget(fileBurnAddrWidget)
        oneFilePathWidgetLayout.addWidget(openFileButton)
        oneFilePathWidgetLayout.addWidget(removeButton)

        filesItemLen = len(self.fileSelectWidgets)
        if filesItemLen != 0 and self.fileSelectWidgets[filesItemLen -
                                                        1][4].isHidden():
            fileBurnAddrWidget.hide()
        else:
            fileBurnAddrWidget.show()
        if filesItemLen == 0:
            removeButton.hide()
        elif filesItemLen == 1:
            self.fileSelectWidgets[0][7].show()
        #                0        1                   2                       3               4                   5               6           7             8
        item = [
            "kfpkg", oneFilePathWidget, oneFilePathWidgetLayout,
            filePathWidget, fileBurnAddrWidget, openFileButton, False,
            removeButton, enableCheckbox
        ]
        # for "bin":    ["bin", oneFilePathWidget,   oneFilePathWidgetLayout, filePathWidget, fileBurnAddrWidget, openFileButton, isFirmware, removeButton, enableCheckbox]
        self.fileSelectWidgets.append(item)

        self.setFileSelectItemLayout(item, True)

        openFileButton.clicked.connect(lambda: self.selectFile(item))
        removeButton.clicked.connect(
            lambda: self.removeFileSelectionItem(item))
        self.fileSelectLayout.addWidget(oneFilePathWidget)
        return item

    def removeFileSelectionItem(self, item):
        if self.packing:
            self.hintSignal.emit(tr("Busy"), tr("Please wait, packing ..."))
            return
        if len(self.fileSelectWidgets) <= 1:
            return
        item[5].clicked.disconnect()
        item[7].clicked.disconnect()
        item[1].setParent(None)
        self.fileSelectWidgets.remove(item)
        self.downloadWidget.resize(self.downloadWidget.width(), 58)
        self.setWindowSize(self.width())

    def initWindow(self):
        QToolTip.setFont(QFont('SansSerif', 10))
        # main layout
        self.frameWidget = QWidget()
        mainWidget = QSplitter(Qt.Horizontal)
        self.frameLayout = QVBoxLayout()
        self.settingWidget = QWidget()
        settingLayout = QVBoxLayout()
        self.settingWidget.setProperty("class", "settingWidget")
        mainLayout = QVBoxLayout()
        self.settingWidget.setLayout(settingLayout)
        mainLayout.addWidget(self.settingWidget)
        mainLayout.setStretch(0, 2)
        menuLayout = QHBoxLayout()

        self.progressHint = QLabel()
        self.progressHint.hide()

        self.progressbarRootWidget = QWidget()
        progressbarLayout = QVBoxLayout()
        self.progressbarRootWidget.setProperty("class", "progressbarWidget")
        self.progressbarRootWidget.setLayout(progressbarLayout)

        self.downloadWidget = QWidget()
        downloadLayout = QVBoxLayout()
        self.downloadWidget.setProperty("class", "downloadWidget")
        self.downloadWidget.setLayout(downloadLayout)

        mainWidget.setLayout(mainLayout)
        # menu
        # -----
        # settings and others
        # -----
        # progress bar
        # -----
        # download button
        # -----
        # status bar
        self.frameLayout.addLayout(menuLayout)
        self.frameLayout.addWidget(mainWidget)
        self.frameLayout.addWidget(self.progressHint)
        self.frameLayout.addWidget(self.progressbarRootWidget)
        self.frameLayout.addWidget(self.downloadWidget)
        self.frameWidget.setLayout(self.frameLayout)
        self.setCentralWidget(self.frameWidget)
        self.setFrameStrentch(1)

        # option layout
        self.langButton = QPushButton()
        self.skinButton = QPushButton()
        self.aboutButton = QPushButton()
        self.langButton.setProperty("class", "menuItemLang")
        self.skinButton.setProperty("class", "menuItem2")
        self.aboutButton.setProperty("class", "menuItem3")
        self.langButton.setObjectName("menuItem")
        self.skinButton.setObjectName("menuItem")
        self.aboutButton.setObjectName("menuItem")
        menuLayout.addWidget(self.langButton)
        menuLayout.addWidget(self.skinButton)
        menuLayout.addWidget(self.aboutButton)
        menuLayout.addStretch(0)

        # widgets file select
        self.fileSelectGroupBox = QGroupBox(tr("SelectFile"))
        # container
        settingLayout.addWidget(self.fileSelectGroupBox)
        self.fileSelectContainerLayout = QVBoxLayout()
        self.fileSelectGroupBox.setLayout(self.fileSelectContainerLayout)
        # file selection
        self.fileSelecWidget = QWidget()
        self.fileSelectLayout = QVBoxLayout()
        self.fileSelecWidget.setLayout(self.fileSelectLayout)
        self.fileSelectContainerLayout.addWidget(self.fileSelecWidget)

        # add file selection item
        self.addFileSelectionItem()

        # add fileselection functions
        mergeBinWidget = QWidget()
        mergeBinWidgetLayout = QHBoxLayout()
        mergeBinWidget.setLayout(mergeBinWidgetLayout)
        self.addFileButton = QPushButton(tr("Add File"))
        self.packFilesButton = QPushButton(tr("Pack to kfpkg"))
        self.mergeBinButton = QPushButton(tr("Merge to .bin"))
        mergeBinWidgetLayout.addWidget(self.addFileButton)
        mergeBinWidgetLayout.addWidget(self.packFilesButton)
        mergeBinWidgetLayout.addWidget(self.mergeBinButton)
        self.fileSelectContainerLayout.addWidget(mergeBinWidget)

        # widgets board select
        boardSettingsGroupBox = QGroupBox(tr("BoardSettings"))
        settingLayout.addWidget(boardSettingsGroupBox)
        boardSettingsLayout = QGridLayout()
        boardSettingsGroupBox.setLayout(boardSettingsLayout)
        self.boardLabel = QLabel(tr("Board"))
        self.boardCombobox = ComboBox()
        self.boardCombobox.addItem(parameters.SipeedMaixDock)
        self.boardCombobox.addItem(parameters.SipeedMaixBit)
        self.boardCombobox.addItem(parameters.SipeedMaixBitMic)
        self.boardCombobox.addItem(parameters.SipeedMaixduino)
        self.boardCombobox.addItem(parameters.SipeedMaixGo)
        self.boardCombobox.addItem(parameters.SipeedMaixGoD)
        self.boardCombobox.addItem(parameters.M5StickV)
        self.boardCombobox.addItem(parameters.KendryteKd233)
        self.boardCombobox.addItem(parameters.kendryteTrainer)
        self.boardCombobox.addItem(parameters.Auto)
        self.burnPositionLabel = QLabel(tr("BurnTo"))
        self.burnPositionCombobox = ComboBox()
        self.burnPositionCombobox.addItem(tr("Flash"))
        self.burnPositionCombobox.addItem(tr("SRAM"))
        boardSettingsLayout.addWidget(self.boardLabel, 0, 0)
        boardSettingsLayout.addWidget(self.boardCombobox, 0, 1)
        boardSettingsLayout.addWidget(self.burnPositionLabel, 1, 0)
        boardSettingsLayout.addWidget(self.burnPositionCombobox, 1, 1)

        # widgets serial settings
        serialSettingsGroupBox = QGroupBox(tr("SerialSettings"))
        serialSettingsLayout = QGridLayout()
        serialPortLabek = QLabel(tr("SerialPort"))
        serailBaudrateLabel = QLabel(tr("SerialBaudrate"))
        slowModeLabel = QLabel(tr("Speed mode"))
        self.serialPortCombobox = ComboBox()
        self.serailBaudrateCombobox = ComboBox()
        self.serailBaudrateCombobox.addItem("115200")
        self.serailBaudrateCombobox.addItem("921600")
        self.serailBaudrateCombobox.addItem("1500000")
        self.serailBaudrateCombobox.addItem("2000000")
        self.serailBaudrateCombobox.addItem("3500000")
        self.serailBaudrateCombobox.addItem("4000000")
        self.serailBaudrateCombobox.addItem("4500000")
        self.serailBaudrateCombobox.setCurrentIndex(1)
        self.serailBaudrateCombobox.setEditable(True)
        self.slowModeCombobox = ComboBox()
        self.slowModeCombobox.addItem(tr("Slow mode"))
        self.slowModeCombobox.addItem(tr("Fast mode"))
        slowModeLabel.setToolTip(tr("slow mode tips"))
        self.slowModeCombobox.setToolTip(tr("slow mode tips"))

        serialSettingsLayout.addWidget(serialPortLabek, 0, 0)
        serialSettingsLayout.addWidget(serailBaudrateLabel, 1, 0)
        serialSettingsLayout.addWidget(slowModeLabel, 2, 0)
        serialSettingsLayout.addWidget(self.serialPortCombobox, 0, 1)
        serialSettingsLayout.addWidget(self.serailBaudrateCombobox, 1, 1)
        serialSettingsLayout.addWidget(self.slowModeCombobox, 2, 1)
        serialSettingsGroupBox.setLayout(serialSettingsLayout)
        settingLayout.addWidget(serialSettingsGroupBox)

        # set stretch
        settingLayout.setStretch(0, 1)
        settingLayout.setStretch(1, 1)
        settingLayout.setStretch(2, 2)

        # widgets progress bar

        self.progressbar = QProgressBar(self.progressbarRootWidget)
        self.progressbar.setValue(0)
        self.progressbarRootWidget.hide()

        # widgets download area
        self.downloadButton = QPushButton(tr("Download"))
        downloadLayout.addWidget(self.downloadButton)

        # main window
        self.statusBarStauts = QLabel()
        self.statusBarStauts.setMinimumWidth(80)
        self.statusBarStauts.setText("<font color=%s>%s</font>" %
                                     ("#1aac2d", tr("DownloadHint")))
        self.statusBar().addWidget(self.statusBarStauts)

        self.setWindowSize()
        self.MoveToCenter()
        self.setWindowTitle(parameters.appName + " V" +
                            str(helpAbout.versionMajor) + "." +
                            str(helpAbout.versionMinor))
        icon = QIcon()
        print("icon path:" + self.DataPath + "/" + parameters.appIcon)
        icon.addPixmap(QPixmap(self.DataPath + "/" + parameters.appIcon),
                       QIcon.Normal, QIcon.Off)
        self.setWindowIcon(icon)
        if sys.platform == "win32":
            ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(
                parameters.appName)

        self.show()
        self.progressbar.setGeometry(10, 0,
                                     self.downloadWidget.width() - 25, 40)
        print("config file path:", parameters.configFilePath)

    def initEvent(self):
        self.serialPortCombobox.clicked.connect(self.portComboboxClicked)
        self.errorSignal.connect(self.errorHint)
        self.hintSignal.connect(self.hint)
        self.downloadResultSignal.connect(self.downloadResult)
        self.showSerialComboboxSignal.connect(self.showCombobox)
        self.updateProgressSignal.connect(self.updateProgress)
        self.updateProgressPrintSignal.connect(self.updateProgressPrint)
        self.langButton.clicked.connect(self.langChange)
        self.skinButton.clicked.connect(self.skinChange)
        self.aboutButton.clicked.connect(self.showAbout)
        self.downloadButton.clicked.connect(self.download)

        self.addFileButton.clicked.connect(
            lambda: self.fileSelectLayout.addWidget(self.addFileSelectionItem(
            )[1]))
        self.packFilesButton.clicked.connect(self.packFiles)
        self.mergeBinButton.clicked.connect(self.mergeBin)

        self.myObject = MyClass(self)
        slotLambda = lambda: self.indexChanged_lambda(self.myObject)
        self.serialPortCombobox.currentIndexChanged.connect(slotLambda)

    def setFrameStrentch(self, mode):
        if mode == 0:
            self.frameLayout.setStretch(0, 1)
            self.frameLayout.setStretch(1, 3)
            self.frameLayout.setStretch(2, 3)
            self.frameLayout.setStretch(3, 1)
            self.frameLayout.setStretch(4, 1)
            self.frameLayout.setStretch(5, 1)
        else:
            self.frameLayout.setStretch(0, 0)
            self.frameLayout.setStretch(1, 0)
            self.frameLayout.setStretch(2, 1)
            self.frameLayout.setStretch(3, 1)
            self.frameLayout.setStretch(4, 1)
            self.frameLayout.setStretch(5, 1)

    # @QtCore.pyqtSlot(str)
    def indexChanged_lambda(self, obj):
        mainObj = obj.arg
        self.serialPortCombobox.setToolTip(
            mainObj.serialPortCombobox.currentText())

    def portComboboxClicked(self):
        self.detectSerialPort()

    def MoveToCenter(self):
        qr = self.frameGeometry()
        cp = QDesktopWidget().availableGeometry().center()
        qr.moveCenter(cp)
        self.move(qr.topLeft())

    def highlightFirmwarePath(self, item, firmware):
        if firmware:
            item[3].setProperty("class", "qLineEditHighlight")
            item[4].setText("0x00000")
        else:
            item[3].setProperty("class", "qLineEditNormal")
        self.frameWidget.style().unpolish(item[3])
        self.frameWidget.style().polish(item[3])
        self.frameWidget.update()

    def fileSelectShow(self,
                       item,
                       name,
                       addr=None,
                       firmware=None,
                       enable=True,
                       loadFirst=False):
        isKfpkg = False
        if self.isKfpkg(name):
            isKfpkg = True
        if not item:  # add item from param
            if loadFirst:
                item = self.fileSelectWidgets[0]
            else:
                item = self.addFileSelectionItem()
            if isKfpkg:
                self.highlightFirmwarePath(item, False)
                self.setFileSelectItemLayout(item, True)
            else:
                item[4].setText("0x%06x" % (addr))
                self.setFileSelectItemLayout(item, False)
                if self.isFileFirmware(name):
                    self.highlightFirmwarePath(item, True)
                    item[6] = True
                else:
                    self.highlightFirmwarePath(item, False)
                    item[6] = False
            item[3].setText(name)
            item[8].setChecked(enable)
            return

        if isKfpkg:
            self.setFileSelectItemLayout(item, True)
            self.highlightFirmwarePath(item, False)
            # disable other items
            for i in self.fileSelectWidgets:
                i[8].setChecked(False)
            # only enable this kfpkg
            item[8].setChecked(True)
        else:
            self.setFileSelectItemLayout(item, False)
            if self.isFileFirmware(name):
                self.highlightFirmwarePath(item, True)
                item[4].setText("0x00000")
            else:
                self.highlightFirmwarePath(item, False)
            # disable kfpkg file
            for i in self.fileSelectWidgets:
                if self.isKfpkg(i[3].text()):
                    i[8].setChecked(False)
            # enable this bin file
            item[8].setChecked(True)
        item[3].setText(name)

    # return: ("bin", [(file path, burn addr, add prefix, enable),...])
    #      or ("kfpkg", file path)
    #      or (None, msg)
    def getBurnFilesInfo(self):
        files = []
        fileType = ""
        for item in self.fileSelectWidgets:
            path = item[3].text().strip()
            enable = item[8].isChecked()
            try:
                addr = int(item[4].text(), 16)
            except Exception:
                addr = 0
            if not enable:
                continue
            if path == "" or not os.path.exists(path):
                return (None, tr("Line {}: ").format(
                    self.fileSelectWidgets.index(item) + 1) +
                        tr("File path error") + ":" + path)
            if self.isKfpkg(path):
                if fileType == "bin":
                    return (
                        None,
                        tr("Can not select kfpkg and bin files at the time"))
                fileType = "kfpkg"
                if len(files) != 0:
                    return (None, tr("Only support one kfpkg file"))
                files = path
            else:
                if fileType == "kfpkg":
                    return (
                        None,
                        tr("Can not select kfpkg and bin files at the time"))
                fileType = "bin"
                files.append((path, addr, item[6], enable))
        return (fileType, files)

    class KFPKG():
        def __init__(self):
            self.fileInfo = {"version": "0.1.0", "files": []}
            self.filePath = {}
            self.burnAddr = []

        def addFile(self, addr, path, prefix=False):
            if not os.path.exists(path):
                raise ValueError(tr("FilePathError"))
            if addr in self.burnAddr:
                raise ValueError(
                    tr("Burn dddr duplicate") + ":0x%06x" % (addr))
            f = {}
            f_name = os.path.split(path)[1]
            f["address"] = addr
            f["bin"] = f_name
            f["sha256Prefix"] = prefix
            self.fileInfo["files"].append(f)
            self.filePath[f_name] = path
            self.burnAddr.append(addr)

        def listDumps(self):
            kfpkg_json = json.dumps(self.fileInfo, indent=4)
            return kfpkg_json

        def listDump(self, path):
            with open(path, "w") as f:
                f.write(json.dumps(self.fileInfo, indent=4))

        def listLoads(self, kfpkgJson):
            self.fileInfo = json.loads(kfpkgJson)

        def listLload(self, path):
            with open(path) as f:
                self.fileInfo = json.load(f)

        def save(self, path):
            listName = os.path.join(tempfile.gettempdir(),
                                    "kflash_gui_tmp_list.json")
            self.listDump(listName)
            try:
                with zipfile.ZipFile(path, "w") as zip:
                    for name, path in self.filePath.items():
                        zip.write(path,
                                  arcname=name,
                                  compress_type=zipfile.ZIP_DEFLATED)
                    zip.write(listName,
                              arcname="flash-list.json",
                              compress_type=zipfile.ZIP_DEFLATED)
                    zip.close()
            except Exception as e:
                os.remove(listName)
                raise e
            os.remove(listName)

    def checkFilesAddrValid(self, fileType, files):
        if fileType == "bin":
            files.sort(key=lambda file: file[1])
            startAddr = -1
            fileSize = 0
            fileShortLast = ""
            count = 0
            for file, addr, firmware, enable in files:
                if not enable:
                    continue
                fileShort = ".../" + "/".join(file.split("/")[-2:])
                if startAddr + fileSize > addr:
                    return (
                        False, tr("File address error") +
                        ": {} {} 0x{:X}, {} {} {} [0x{:X},0x{:X}]".format(
                            fileShort, tr("start from"), addr, tr("but file"),
                            fileShortLast, tr("address range is"), startAddr,
                            startAddr + fileSize))
                fileSize = os.path.getsize(file)
                startAddr = addr
                fileShortLast = fileShort
                count += 1
            if count == 0:
                return (False, tr("No file selected"))
        return (True, "")

    def packFiles(self):
        if self.packing:
            self.hintSignal.emit(tr("Busy"), tr("Please wait, packing ..."))
            return
        self.packing = True

        fileType, files = self.getBurnFilesInfo()
        if not fileType:
            self.errorSignal.emit(tr("Error"), files)
            self.packing = False
            return

        if fileType == "kfpkg":
            self.errorSignal.emit(tr("Error"), tr("Can not pack kfpkg"))
            self.packing = False
            return

        ok, msg = self.checkFilesAddrValid(fileType, files)
        if not ok:
            self.errorSignal.emit(tr("Error"), msg)
            self.packing = False
            return

        # select saving path
        if not os.path.exists(self.saveKfpkDir):
            self.saveKfpkDir = os.getcwd()
        fileName_choose, filetype = QFileDialog.getSaveFileName(
            self, tr("Save File"), self.saveKfpkDir, "k210 packages (*.kfpkg)")
        if fileName_choose == "":
            # self.errorSignal.emit(tr("Error"), tr("File path error"))
            self.packing = False
            return
        if not self.isKfpkg(fileName_choose):
            fileName_choose += ".kfpkg"
        self.saveKfpkDir = os.path.split(fileName_choose)[0]

        # pack and save
        t = threading.Thread(target=self.packFileProccess,
                             args=(
                                 files,
                                 fileName_choose,
                             ))
        t.setDaemon(True)
        t.start()

    def packFileProccess(self, files, fileSaveName):
        # generate flash-list.json
        kfpkg = self.KFPKG()
        try:
            for path, addr, prefix, enable in files:
                if enable:
                    kfpkg.addFile(addr, path, prefix)
        except Exception as e:
            self.errorSignal.emit(tr("Error"),
                                  tr("Pack kfpkg fail") + ":" + str(e))
            self.packing = False
            return

        # write kfpkg file
        try:
            kfpkg.save(fileSaveName)
        except Exception as e:
            self.errorSignal.emit(tr("Error"),
                                  tr("Pack kfpkg fail") + ":" + str(e))
            self.packing = False
            return
        self.hintSignal.emit(tr("Success"), tr("Save kfpkg success"))
        self.packing = False

    def getBurnFilesInfoFromKfpkg(self, kfpkg):
        tempDir = tempfile.gettempdir()
        listFileName = "flash-list.json"
        try:
            zip = zipfile.ZipFile(kfpkg, mode="r")
            zip.extract(listFileName, tempDir)
            with open(tempDir + "/" + listFileName) as f:
                info = json.load(f)
            filesInfo = {}
            for fileInfo in info["files"]:
                filesInfo[fileInfo["bin"]] = [
                    fileInfo["address"], fileInfo["sha256Prefix"]
                ]
            print(filesInfo, zip.namelist())
            binFiles = zip.namelist()
            binFiles.remove(listFileName)
            for file in binFiles:
                zip.extract(file, tempDir)
                self.zipTempFiles.append(
                    (tempDir + "/" + file, filesInfo[file][0],
                     filesInfo[file][1], True))
            zip.close()
        except Exception as e:
            return (None, str(e))
        return (self.zipTempFiles, "")

    def cleanKfpkgTempFiles(self):
        tempDir = tempfile.gettempdir()
        try:
            for file in self.zipTempFiles:
                os.remove(file[0])
        except Exception:
            pass
        self.zipTempFiles = []

    def mergeBin(self):
        if self.packing:
            self.hintSignal.emit(tr("Busy"), tr("Please wait, packing ..."))
            return
        self.packing = True
        fileType, files = self.getBurnFilesInfo()
        if not fileType:
            self.errorSignal.emit(tr("Error"), files)
            self.cleanKfpkgTempFiles()
            self.packing = False
            return
        if fileType == "kfpkg":
            files, msg = self.getBurnFilesInfoFromKfpkg(files)
            fileType = "bin"
            if not files:
                self.errorSignal.emit(tr("Error"), msg)
                self.cleanKfpkgTempFiles()
                self.packing = False
                return

        ok, msg = self.checkFilesAddrValid(fileType, files)
        if not ok:
            self.errorSignal.emit(tr("Error"), msg)
            self.packing = False
            self.cleanKfpkgTempFiles()
            return

        # select saving path
        if not os.path.exists(self.saveKfpkDir):
            self.saveKfpkDir = os.getcwd()
        fileName_choose, filetype = QFileDialog.getSaveFileName(
            self, tr("Save File"), self.saveKfpkDir, "Binary file (*.bin)")
        if fileName_choose == "":
            # self.errorSignal.emit(tr("Error"), tr("File path error"))
            self.packing = False
            self.cleanKfpkgTempFiles()
            return
        if not fileName_choose.endswith(".bin"):
            fileName_choose += ".bin"
        self.saveKfpkDir = os.path.split(fileName_choose)[0]

        # pack and save
        t = threading.Thread(target=self.mergeBinProccess,
                             args=(
                                 files,
                                 fileName_choose,
                             ))
        t.setDaemon(True)
        t.start()

    def mergeBinProccess(self, files, fileSaveName):
        self.updateProgressPrintSignal.emit(tr("Merging, please wait ..."))
        files.sort(key=lambda file: file[1])
        bin = b''
        aesFlag = b'\x00'
        startAddrLast = files[0][1]
        fileSizeLast = 0
        if files[0][2]:  # firmware
            name = files[0][0]
            size = os.path.getsize(name)
            f = open(name, "rb")
            firmware = f.read()
            f.close()

            bin += aesFlag  # add aes key flag
            bin += struct.pack('I', size)  # add firmware length
            bin += firmware  # add firmware content
            sha256Hash = hashlib.sha256(bin).digest()
            bin += sha256Hash  # add parity

            startAddrLast = 0
            fileSizeLast = len(bin)
            files.remove(files[0])

        for file, addr, firmware, enable in files:
            if not enable:
                continue
            fillLen = addr - (startAddrLast + fileSizeLast)
            if fillLen > 0:  # fill 0xFF
                fill = bytearray([0xFF for i in range(fillLen)])
                bin += fill
            with open(file, "rb") as f:  # add bin file content
                bin += f.read()
            startAddrLast = addr
            fileSizeLast = os.path.getsize(file)
        with open(fileSaveName, "wb") as f:
            f.write(bin)
        self.updateProgressPrintSignal.emit(tr("Save merged bin file success"))
        self.hintSignal.emit(tr("Success"), tr("Save merged bin file success"))
        self.packing = False
        self.cleanKfpkgTempFiles()

    def selectFile(self, item):
        if self.packing:
            self.hintSignal.emit(tr("Busy"), tr("Please wait, packing ..."))
            return
        index = self.fileSelectWidgets.index(item)
        oldPath = item[3].text()
        if oldPath == "" and index > 0:
            oldPath = self.fileSelectWidgets[index - 1][3].text()
        if oldPath == "":
            oldPath = os.getcwd()
        fileName_choose, filetype = QFileDialog.getOpenFileName(
            self, tr("SelectFile"), oldPath,
            "All Files (*);;bin Files (*.bin);;k210 packages (*.kfpkg);;kmodel (*.kmodel);;encrypted kmodle(*.smodel)"
        )  # 设置文件扩展名过滤,用双分号间隔

        if fileName_choose == "":
            return
        if not self.isFileValid(fileName_choose):
            self.errorSignal.emit(tr("Error"), tr("File path error"))
            return
        self.fileSelectShow(item, fileName_choose)

    def errorHint(self, title, str):
        QMessageBox.critical(self, title, str)

    def hint(self, title, str):
        QMessageBox.information(self, title, str)

    def findSerialPort(self):
        self.port_list = list(serial.tools.list_ports.comports())
        return self.port_list

    def portChanged(self):
        self.serialPortCombobox.setCurrentIndex(0)
        self.serialPortCombobox.setToolTip(str(self.portList[0]))

    def detectSerialPort(self):
        if not self.isDetectSerialPort:
            self.isDetectSerialPort = True
            t = threading.Thread(target=self.detectSerialPortProcess)
            t.setDaemon(True)
            t.start()

    def showCombobox(self):
        self.serialPortCombobox.showPopup()

    def isKfpkg(self, name):
        if name.endswith(".kfpkg"):
            return True
        return False

    def isFileFirmware(self, name):
        isFirmware = False
        if name.endswith(".bin"):
            f = open(name, "rb")
            start_bytes = f.read(6)
            f.close()
            for flags in self.firmware_start_bytes:
                if flags in start_bytes:
                    isFirmware = True
                    break
        return isFirmware

    def isFileValid(self, name):
        if not os.path.exists(name):
            return False
        return True

    def detectSerialPortProcess(self):
        while (1):
            portList = self.findSerialPort()
            if len(portList) > 0:
                currText = self.serialPortCombobox.currentText()
                self.serialPortCombobox.clear()
                for i in portList:
                    showStr = str(i[0]) + " (" + str(i[1]) + ")"
                    self.serialPortCombobox.addItem(showStr)
                index = self.serialPortCombobox.findText(currText)
                if index >= 0:
                    self.serialPortCombobox.setCurrentIndex(index)
                else:
                    self.serialPortCombobox.setCurrentIndex(0)
                break
            time.sleep(1)
        self.showSerialComboboxSignal.emit()
        self.isDetectSerialPort = False

    def programExitSaveParameters(self):
        paramObj = paremeters_save.ParametersToSave()
        paramObj.board = self.boardCombobox.currentText()
        paramObj.burnPosition = self.burnPositionCombobox.currentText()
        paramObj.baudRate = self.serailBaudrateCombobox.currentIndex()
        paramObj.skin = self.param.skin
        paramObj.language = translation.current_lang
        for item in self.fileSelectWidgets:
            path = item[3].text()
            try:
                addr = int(item[4].text(), 16)
            except Exception:
                addr = 0
            fileInfo = (path, addr, item[6], item[8].isChecked())
            paramObj.files.append(fileInfo)
        if self.slowModeCombobox.currentIndex() == 0:
            paramObj.slowMode = True
        else:
            paramObj.slowMode = False
        paramObj.save(parameters.configFilePath)

    def programStartGetSavedParameters(self):
        paramObj = paremeters_save.ParametersToSave()
        paramObj.load(parameters.configFilePath)
        translation.setLanguage(paramObj.language)
        self.param = paramObj

    def updateFrameParams(self):
        pathLen = len(self.param.files)
        if pathLen != 0:
            if len(self.param.files[0]
                   ) != 4:  # [ (path, addr, prefix, enable), ...]
                return
            count = 0
            for path, addr, firmware, enable in self.param.files:
                firmware = None if (not firmware) else True
                if count == 0:
                    self.fileSelectShow(None,
                                        path,
                                        addr,
                                        firmware,
                                        enable=enable,
                                        loadFirst=True)
                else:
                    self.fileSelectShow(None,
                                        path,
                                        addr,
                                        firmware,
                                        enable=enable,
                                        loadFirst=False)
                count += 1
        self.boardCombobox.setCurrentText(self.param.board)
        self.burnPositionCombobox.setCurrentText(self.param.burnPosition)
        self.serailBaudrateCombobox.setCurrentIndex(self.param.baudRate)
        if self.param.slowMode:
            self.slowModeCombobox.setCurrentIndex(0)
        else:
            self.slowModeCombobox.setCurrentIndex(1)

    def closeEvent(self, event):
        try:
            self.programExitSaveParameters()
        finally:
            event.accept()

    def langChange(self):
        if self.param.language == translation.language_en:
            translation.setLanguage(translation.language_zh)
            lang = tr("Chinese language")
        else:
            translation.setLanguage(translation.language_en)
            lang = tr("English language")

        self.hint(
            tr("Hint"),
            tr("Language Changed to ") + lang + "\n" +
            tr("Reboot to take effect"))
        self.frameWidget.style().unpolish(self.downloadButton)
        self.frameWidget.style().polish(self.downloadButton)
        self.frameWidget.update()

    def skinChange(self):
        if self.param.skin == 1:  # light
            file = open(self.DataPath + '/assets/qss/style-dark.qss', "r")
            self.param.skin = 2
        else:  # elif self.param.skin == 2: # dark
            file = open(self.DataPath + '/assets/qss/style.qss', "r")
            self.param.skin = 1
        self.app.setStyleSheet(file.read().replace("$DataPath", self.DataPath))
        file.close()

    def showAbout(self):
        QMessageBox.information(
            self, tr("About"),
            "<h1 style='color:#f75a5a';margin=10px;>" + parameters.appName +
            '</h1><br><b style="color:#08c7a1;margin = 5px;">V' +
            str(helpAbout.versionMajor) + "." + str(helpAbout.versionMinor) +
            "." + str(helpAbout.versionDev) + "</b><br><br>" + helpAbout.date +
            "<br><br>" + tr("help str") + "<br><br>" + helpAbout.strAbout())

    def autoUpdateDetect(self):
        auto = autoUpdate.AutoUpdate()
        if auto.detectNewVersion():
            self.hintSignal.emit(
                tr("Upgrade"),
                tr("Upgrade available, please download new release in release page"
                   ))
            auto.OpenBrowser()

    def openDevManagement(self):
        os.system('start devmgmt.msc')

    def updateProgress(self, fileTypeStr, current, total, speedStr):
        currBurnPos = self.burnPositionCombobox.currentText()
        if currBurnPos == tr("SRAM") or currBurnPos == tr_en("SRAM"):
            fileTypeStr = tr("ToSRAM")
        percent = current / float(total) * 100
        hint = "<font color=%s>%s %s:</font>   <font color=%s> %.2f%%</font>   <font color=%s> %s</font>" % (
            "#ff7575", tr("Downloading"), fileTypeStr, "#2985ff", percent,
            "#1aac2d", speedStr)
        self.progressHint.setText(hint)
        self.progressbar.setValue(percent)

    def updateProgressPrint(self, str):
        self.statusBarStauts.setText(str)

    def kflash_py_printCallback(self, *args, **kwargs):
        # end = kwargs.pop('end', "\n")
        msg = ""
        for i in args:
            msg += str(i)
        msg.replace("\n", " ")
        self.updateProgressPrintSignal.emit(msg)

    def progress(self, fileTypeStr, current, total, speedStr):
        self.updateProgressSignal.emit(fileTypeStr, current, total, speedStr)

    def download(self):
        if self.packing:
            self.hintSignal.emit(tr("Busy"), tr("Please wait, packing ..."))
            return
        if self.burning:
            self.terminateBurn()
            return
        fileType, filesInfo = self.getBurnFilesInfo()
        if not fileType or not filesInfo:
            self.errorSignal.emit(tr("Error"), tr("File path error"))
            return

        self.burning = True
        # if not self.checkFileName(filename):
        #     self.errorSignal.emit(tr("Error"), tr("FilePathError"))
        #     self.burning = False
        #     return
        color = False
        board = "dan"
        boardText = self.boardCombobox.currentText()
        if boardText == parameters.SipeedMaixGo:
            board = "goE"
        elif boardText == parameters.SipeedMaixGoD:
            board = "goD"
        elif boardText == parameters.SipeedMaixduino:
            board = "maixduino"
        elif boardText == parameters.SipeedMaixBit:
            board = "bit"
        elif boardText == parameters.SipeedMaixBitMic:
            board = "bit_mic"
        elif boardText == parameters.KendryteKd233:
            board = "kd233"
        elif boardText == parameters.kendryteTrainer:
            board = "trainer"
        elif boardText == parameters.M5StickV:
            board = "goE"
        elif boardText == parameters.Auto:
            board = None

        sram = False
        if self.burnPositionCombobox.currentText()==tr("SRAM") or \
            self.burnPositionCombobox.currentText()==tr_en("SRAM"):
            sram = True
        try:
            baud = int(self.serailBaudrateCombobox.currentText())
        except Exception:
            self.errorSignal.emit(tr("Error"), tr("BaudrateError"))
            self.burning = False
            return
        dev = ""
        try:
            dev = self.serialPortCombobox.currentText().split()[0]
        except Exception:
            pass
        if dev == "":
            self.errorSignal.emit(tr("Error"), tr("PleaseSelectSerialPort"))
            self.burning = False
            return
        slow = self.slowModeCombobox.currentIndex() == 0
        # hide setting widgets
        self.setFrameStrentch(1)
        self.settingWidget.hide()
        self.progressbar.setValue(0)
        self.progressbar.setGeometry(10, 0,
                                     self.downloadWidget.width() - 25, 40)
        self.progressbarRootWidget.show()
        self.progressHint.show()
        self.downloadButton.setText(tr("Cancel"))
        self.downloadButton.setProperty("class", "redbutton")
        self.downloadButton.style().unpolish(self.downloadButton)
        self.downloadButton.style().polish(self.downloadButton)
        self.downloadButton.update()
        self.statusBarStauts.setText("<font color=%s>%s ...</font>" %
                                     ("#1aac2d", tr("Downloading")))
        hint = "<font color=%s>%s</font>" % ("#ff0d0d", tr("DownloadStart"))
        self.progressHint.setText(hint)
        # download
        self.burnThread = threading.Thread(target=self.flashBurnProcess,
                                           args=(dev, baud, board, sram,
                                                 fileType, filesInfo,
                                                 self.progress, color, slow))
        self.burnThread.setDaemon(True)
        self.burnThread.start()

    def flashBurnProcess(self, dev, baud, board, sram, fileType, files,
                         callback, color, slow):
        success = True
        errMsg = ""
        tmpFile = ""

        if fileType == "kfpkg":
            if sram:
                errMsg = tr("only support bin file when Download to SRAM")
                success = False
            else:
                filename = files
        else:  #generate kfpkg
            if sram:
                filename = files[0][0]
            else:
                tmpFile = os.path.join(tempfile.gettempdir(),
                                       "kflash_gui_tmp.kfpkg")
                kfpkg = self.KFPKG()
                try:
                    for path, addr, prefix, enable in files:
                        if enable:
                            kfpkg.addFile(addr, path, prefix)
                    kfpkg.save(tmpFile)
                    filename = os.path.abspath(tmpFile)
                except Exception as e:
                    try:
                        os.remove(tmpFile)
                    except Exception:
                        print("can not delete temp file:", tmpFile)
                    errMsg = tr("Pack kfpkg fail") + ":" + str(e)
                    success = False
        if success:
            try:
                if board:
                    self.kflash.process(terminal=False,
                                        dev=dev,
                                        baudrate=baud,
                                        board=board,
                                        sram=sram,
                                        file=filename,
                                        callback=callback,
                                        noansi=not color,
                                        slow_mode=slow)
                else:
                    self.kflash.process(terminal=False,
                                        dev=dev,
                                        baudrate=baud,
                                        sram=sram,
                                        file=filename,
                                        callback=callback,
                                        noansi=not color,
                                        slow_mode=slow)
            except Exception as e:
                errMsg = tr2(str(e))
                if str(e) != "Burn SRAM OK":
                    success = False
        if tmpFile != "":
            try:
                os.remove(filename)
            except Exception:
                print("Can not delete tmp file:", filename)
        if success:
            self.downloadResultSignal.emit(True, errMsg)
        else:
            self.downloadResultSignal.emit(False, errMsg)
        self.burning = False

    def downloadResult(self, success, msg):
        if success:
            self.hintSignal.emit(tr("Success"), tr("DownloadSuccess"))
            self.statusBarStauts.setText("<font color=%s>%s</font>" %
                                         ("#1aac2d", tr("DownloadSuccess")))
        else:
            if msg == tr("Cancel"):
                self.statusBarStauts.setText(
                    "<font color=%s>%s</font>" %
                    ("#ff1d1d", tr("DownloadCanceled")))
            else:
                msg = tr("ErrorSettingHint") + "\n\n" + msg
                self.errorSignal.emit(tr("Error"), msg)
                self.statusBarStauts.setText("<font color=%s>%s</font>" %
                                             ("#ff1d1d", tr("DownloadFail")))
            self.progressHint.setText("")
        self.downloadButton.setText(tr("Download"))
        self.downloadButton.setProperty("class", "normalbutton")
        self.downloadButton.style().unpolish(self.downloadButton)
        self.downloadButton.style().polish(self.downloadButton)
        self.downloadButton.update()
        self.setFrameStrentch(0)
        self.progressbarRootWidget.hide()
        self.progressHint.hide()
        self.settingWidget.show()
        self.burning = False

    def terminateBurn(self):
        hint = "<font color=%s>%s</font>" % ("#ff0d0d",
                                             tr("DownloadCanceling"))
        self.progressHint.setText(hint)
        self.kflash.kill()
Exemple #2
0
class MainWindow(QMainWindow):
    receiveUpdateSignal = pyqtSignal(str)
    errorSignal = pyqtSignal(str)
    showSerialComboboxSignal = pyqtSignal()
    setDisableSettingsSignal = pyqtSignal(bool)
    isDetectSerialPort = False
    receiveCount = 0
    sendCount = 0
    isScheduledSending = False
    DataPath = "./"
    isHideSettings = False
    isHideFunctinal = True
    app = None
    isWaveOpen = False

    def __init__(self, app):
        super().__init__()
        self.app = app
        pathDirList = sys.argv[0].replace("\\", "/").split("/")
        pathDirList.pop()
        self.DataPath = os.path.abspath("/".join(str(i) for i in pathDirList))
        if not os.path.exists(self.DataPath + "/" + parameters.strDataDirName):
            pathDirList.pop()
            self.DataPath = os.path.abspath("/".join(
                str(i) for i in pathDirList))
        self.DataPath = (self.DataPath + "/" +
                         parameters.strDataDirName).replace("\\", "/")
        self.initWindow()
        self.initTool()
        self.initEvent()
        self.programStartGetSavedParameters()

    def __del__(self):
        pass

    def initTool(self):
        self.com = serial.Serial()

    def initWindow(self):
        QToolTip.setFont(QFont('SansSerif', 10))
        # main layout
        frameWidget = QWidget()
        mainWidget = QSplitter(Qt.Horizontal)
        frameLayout = QVBoxLayout()
        self.settingWidget = QWidget()
        self.settingWidget.setProperty("class", "settingWidget")
        self.receiveSendWidget = QSplitter(Qt.Vertical)
        self.functionalWiget = QWidget()
        settingLayout = QVBoxLayout()
        sendReceiveLayout = QVBoxLayout()
        sendFunctionalLayout = QVBoxLayout()
        mainLayout = QHBoxLayout()
        self.settingWidget.setLayout(settingLayout)
        self.receiveSendWidget.setLayout(sendReceiveLayout)
        self.functionalWiget.setLayout(sendFunctionalLayout)
        mainLayout.addWidget(self.settingWidget)
        mainLayout.addWidget(self.receiveSendWidget)
        mainLayout.addWidget(self.functionalWiget)
        mainLayout.setStretch(0, 2)
        mainLayout.setStretch(1, 6)
        mainLayout.setStretch(2, 2)
        menuLayout = QHBoxLayout()
        mainWidget.setLayout(mainLayout)
        frameLayout.addLayout(menuLayout)
        frameLayout.addWidget(mainWidget)
        frameWidget.setLayout(frameLayout)
        self.setCentralWidget(frameWidget)

        # option layout
        self.settingsButton = QPushButton()
        self.skinButton = QPushButton("")
        # self.waveButton = QPushButton("")
        self.aboutButton = QPushButton()
        self.functionalButton = QPushButton()
        self.encodingCombobox = ComboBox()
        self.encodingCombobox.addItem("ASCII")
        self.encodingCombobox.addItem("UTF-8")
        self.encodingCombobox.addItem("UTF-16")
        self.encodingCombobox.addItem("GBK")
        self.encodingCombobox.addItem("GB2312")
        self.encodingCombobox.addItem("GB18030")
        self.settingsButton.setProperty("class", "menuItem1")
        self.skinButton.setProperty("class", "menuItem2")
        self.aboutButton.setProperty("class", "menuItem3")
        self.functionalButton.setProperty("class", "menuItem4")
        # self.waveButton.setProperty("class", "menuItem5")
        self.settingsButton.setObjectName("menuItem")
        self.skinButton.setObjectName("menuItem")
        self.aboutButton.setObjectName("menuItem")
        self.functionalButton.setObjectName("menuItem")
        # self.waveButton.setObjectName("menuItem")
        menuLayout.addWidget(self.settingsButton)
        menuLayout.addWidget(self.skinButton)
        # menuLayout.addWidget(self.waveButton)
        menuLayout.addWidget(self.aboutButton)
        menuLayout.addStretch(0)
        menuLayout.addWidget(self.encodingCombobox)
        menuLayout.addWidget(self.functionalButton)

        # widgets receive and send area
        self.receiveArea = QTextEdit()
        self.sendArea = QTextEdit()
        self.clearReceiveButtion = QPushButton(parameters.strClearReceive)
        self.sendButtion = QPushButton(parameters.strSend)
        self.sendHistory = ComboBox()
        sendWidget = QWidget()
        sendAreaWidgetsLayout = QHBoxLayout()
        sendWidget.setLayout(sendAreaWidgetsLayout)
        buttonLayout = QVBoxLayout()
        buttonLayout.addWidget(self.clearReceiveButtion)
        buttonLayout.addStretch(1)
        buttonLayout.addWidget(self.sendButtion)
        sendAreaWidgetsLayout.addWidget(self.sendArea)
        sendAreaWidgetsLayout.addLayout(buttonLayout)
        sendReceiveLayout.addWidget(self.receiveArea)
        sendReceiveLayout.addWidget(sendWidget)
        sendReceiveLayout.addWidget(self.sendHistory)
        sendReceiveLayout.setStretch(0, 7)
        sendReceiveLayout.setStretch(1, 2)
        sendReceiveLayout.setStretch(2, 1)

        # widgets serial settings
        serialSettingsGroupBox = QGroupBox(parameters.strSerialSettings)
        serialSettingsLayout = QGridLayout()
        serialReceiveSettingsLayout = QGridLayout()
        serialSendSettingsLayout = QGridLayout()
        serialPortLabek = QLabel(parameters.strSerialPort)
        serailBaudrateLabel = QLabel(parameters.strSerialBaudrate)
        serailBytesLabel = QLabel(parameters.strSerialBytes)
        serailParityLabel = QLabel(parameters.strSerialParity)
        serailStopbitsLabel = QLabel(parameters.strSerialStopbits)
        self.serialPortCombobox = ComboBox()
        self.serailBaudrateCombobox = ComboBox()
        self.serailBaudrateCombobox.addItem("9600")
        self.serailBaudrateCombobox.addItem("19200")
        self.serailBaudrateCombobox.addItem("38400")
        self.serailBaudrateCombobox.addItem("57600")
        self.serailBaudrateCombobox.addItem("115200")
        self.serailBaudrateCombobox.setCurrentIndex(4)
        self.serailBaudrateCombobox.setEditable(True)
        self.serailBytesCombobox = ComboBox()
        self.serailBytesCombobox.addItem("5")
        self.serailBytesCombobox.addItem("6")
        self.serailBytesCombobox.addItem("7")
        self.serailBytesCombobox.addItem("8")
        self.serailBytesCombobox.setCurrentIndex(3)
        self.serailParityCombobox = ComboBox()
        self.serailParityCombobox.addItem("None")
        self.serailParityCombobox.addItem("Odd")
        self.serailParityCombobox.addItem("Even")
        self.serailParityCombobox.addItem("Mark")
        self.serailParityCombobox.addItem("Space")
        self.serailParityCombobox.setCurrentIndex(0)
        self.serailStopbitsCombobox = ComboBox()
        self.serailStopbitsCombobox.addItem("1")
        self.serailStopbitsCombobox.addItem("1.5")
        self.serailStopbitsCombobox.addItem("2")
        self.serailStopbitsCombobox.setCurrentIndex(0)
        self.checkBoxRts = QCheckBox("rts")
        self.checkBoxDtr = QCheckBox("dtr")
        self.serialOpenCloseButton = QPushButton(parameters.strOpen)
        serialSettingsLayout.addWidget(serialPortLabek, 0, 0)
        serialSettingsLayout.addWidget(serailBaudrateLabel, 1, 0)
        serialSettingsLayout.addWidget(serailBytesLabel, 2, 0)
        serialSettingsLayout.addWidget(serailParityLabel, 3, 0)
        serialSettingsLayout.addWidget(serailStopbitsLabel, 4, 0)
        serialSettingsLayout.addWidget(self.serialPortCombobox, 0, 1)
        serialSettingsLayout.addWidget(self.serailBaudrateCombobox, 1, 1)
        serialSettingsLayout.addWidget(self.serailBytesCombobox, 2, 1)
        serialSettingsLayout.addWidget(self.serailParityCombobox, 3, 1)
        serialSettingsLayout.addWidget(self.serailStopbitsCombobox, 4, 1)
        serialSettingsLayout.addWidget(self.checkBoxRts, 5, 0, 1, 1)
        serialSettingsLayout.addWidget(self.checkBoxDtr, 5, 1, 1, 1)
        serialSettingsLayout.addWidget(self.serialOpenCloseButton, 6, 0, 1, 2)
        serialSettingsGroupBox.setLayout(serialSettingsLayout)
        settingLayout.addWidget(serialSettingsGroupBox)

        # serial receive settings
        serialReceiveSettingsGroupBox = QGroupBox(
            parameters.strSerialReceiveSettings)
        self.receiveSettingsAscii = QRadioButton(parameters.strAscii)
        self.receiveSettingsHex = QRadioButton(parameters.strHex)
        self.receiveSettingsAscii.setChecked(True)
        self.receiveSettingsAutoLinefeed = QCheckBox(
            parameters.strAutoLinefeed)
        self.receiveSettingsAutoLinefeedTime = QLineEdit(
            parameters.strAutoLinefeedTime)
        self.receiveSettingsAutoLinefeed.setMaximumWidth(75)
        self.receiveSettingsAutoLinefeedTime.setMaximumWidth(75)
        serialReceiveSettingsLayout.addWidget(self.receiveSettingsAscii, 1, 0,
                                              1, 1)
        serialReceiveSettingsLayout.addWidget(self.receiveSettingsHex, 1, 1, 1,
                                              1)
        serialReceiveSettingsLayout.addWidget(self.receiveSettingsAutoLinefeed,
                                              2, 0, 1, 1)
        serialReceiveSettingsLayout.addWidget(
            self.receiveSettingsAutoLinefeedTime, 2, 1, 1, 1)
        serialReceiveSettingsGroupBox.setLayout(serialReceiveSettingsLayout)
        settingLayout.addWidget(serialReceiveSettingsGroupBox)

        # serial send settings
        serialSendSettingsGroupBox = QGroupBox(
            parameters.strSerialSendSettings)
        self.sendSettingsAscii = QRadioButton(parameters.strAscii)
        self.sendSettingsHex = QRadioButton(parameters.strHex)
        self.sendSettingsAscii.setChecked(True)
        self.sendSettingsScheduledCheckBox = QCheckBox(parameters.strScheduled)
        self.sendSettingsScheduled = QLineEdit(parameters.strScheduledTime)
        self.sendSettingsScheduledCheckBox.setMaximumWidth(75)
        self.sendSettingsScheduled.setMaximumWidth(75)
        self.sendSettingsCFLF = QCheckBox(parameters.strCRLF)
        self.sendSettingsCFLF.setChecked(False)
        serialSendSettingsLayout.addWidget(self.sendSettingsAscii, 1, 0, 1, 1)
        serialSendSettingsLayout.addWidget(self.sendSettingsHex, 1, 1, 1, 1)
        serialSendSettingsLayout.addWidget(self.sendSettingsScheduledCheckBox,
                                           2, 0, 1, 1)
        serialSendSettingsLayout.addWidget(self.sendSettingsScheduled, 2, 1, 1,
                                           1)
        serialSendSettingsLayout.addWidget(self.sendSettingsCFLF, 3, 0, 1, 2)
        serialSendSettingsGroupBox.setLayout(serialSendSettingsLayout)
        settingLayout.addWidget(serialSendSettingsGroupBox)

        settingLayout.setStretch(0, 5)
        settingLayout.setStretch(1, 2)
        settingLayout.setStretch(2, 2)

        # right functional layout
        self.filePathWidget = QLineEdit()
        self.openFileButton = QPushButton("Open File")
        self.sendFileButton = QPushButton("Send File")
        self.clearHistoryButton = QPushButton("Clear History")
        self.addButton = QPushButton(parameters.strAdd)
        fileSendGroupBox = QGroupBox(parameters.strSendFile)
        fileSendGridLayout = QGridLayout()
        fileSendGridLayout.addWidget(self.filePathWidget, 0, 0, 1, 1)
        fileSendGridLayout.addWidget(self.openFileButton, 0, 1, 1, 1)
        fileSendGridLayout.addWidget(self.sendFileButton, 1, 0, 1, 2)
        fileSendGroupBox.setLayout(fileSendGridLayout)
        sendFunctionalLayout.addWidget(fileSendGroupBox)
        sendFunctionalLayout.addWidget(self.clearHistoryButton)
        sendFunctionalLayout.addWidget(self.addButton)
        sendFunctionalLayout.addStretch(1)
        self.isHideFunctinal = True
        self.hideFunctional()

        # main window
        self.statusBarStauts = QLabel()
        self.statusBarStauts.setMinimumWidth(80)
        self.statusBarStauts.setText("<font color=%s>%s</font>" %
                                     ("#008200", parameters.strReady))
        self.statusBarSendCount = QLabel(parameters.strSend + "(bytes): " +
                                         "0")
        self.statusBarReceiveCount = QLabel(parameters.strReceive +
                                            "(bytes): " + "0")
        self.statusBar().addWidget(self.statusBarStauts)
        self.statusBar().addWidget(self.statusBarSendCount, 2)
        self.statusBar().addWidget(self.statusBarReceiveCount, 3)
        # self.statusBar()

        self.resize(800, 500)
        self.MoveToCenter()
        self.setWindowTitle(parameters.appName + " V" +
                            str(helpAbout.versionMajor) + "." +
                            str(helpAbout.versionMinor))
        icon = QIcon()
        print("icon path:" + self.DataPath + "/" + parameters.appIcon)
        icon.addPixmap(QPixmap(self.DataPath + "/" + parameters.appIcon),
                       QIcon.Normal, QIcon.Off)
        self.setWindowIcon(icon)
        if sys.platform == "win32":
            ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(
                "comtool")
        self.show()
        print("config file path:", parameters.configFilePath)

    def initEvent(self):
        self.serialOpenCloseButton.clicked.connect(self.openCloseSerial)
        self.sendButtion.clicked.connect(self.sendData)
        self.receiveUpdateSignal.connect(self.updateReceivedDataDisplay)
        self.clearReceiveButtion.clicked.connect(self.clearReceiveBuffer)
        self.serialPortCombobox.clicked.connect(self.portComboboxClicked)
        self.sendSettingsHex.clicked.connect(self.onSendSettingsHexClicked)
        self.sendSettingsAscii.clicked.connect(self.onSendSettingsAsciiClicked)
        self.errorSignal.connect(self.errorHint)
        self.showSerialComboboxSignal.connect(self.showCombobox)
        # self.showBaudComboboxSignal.connect(self.showBaudCombobox)
        self.setDisableSettingsSignal.connect(self.setDisableSettings)
        self.sendHistory.currentIndexChanged.connect(
            self.sendHistoryIndexChanged)
        self.settingsButton.clicked.connect(self.showHideSettings)
        self.skinButton.clicked.connect(self.skinChange)
        self.aboutButton.clicked.connect(self.showAbout)
        self.openFileButton.clicked.connect(self.selectFile)
        self.sendFileButton.clicked.connect(self.sendFile)
        self.clearHistoryButton.clicked.connect(self.clearHistory)
        self.addButton.clicked.connect(self.functionAdd)
        self.functionalButton.clicked.connect(self.showHideFunctional)
        self.sendArea.currentCharFormatChanged.connect(
            self.sendAreaFontChanged)
        # self.waveButton.clicked.connect(self.openWaveDisplay)
        self.checkBoxRts.clicked.connect(self.rtsChanged)
        self.checkBoxDtr.clicked.connect(self.dtrChanged)

        self.myObject = MyClass(self)
        slotLambda = lambda: self.indexChanged_lambda(self.myObject)
        self.serialPortCombobox.currentIndexChanged.connect(slotLambda)

    # @QtCore.pyqtSlot(str)
    def indexChanged_lambda(self, obj):
        mainObj = obj.arg
        # print("item changed:",mainObj.serialPortCombobox.currentText())
        self.serialPortCombobox.setToolTip(
            mainObj.serialPortCombobox.currentText())

    def openCloseSerialProcess(self):
        try:
            if self.com.is_open:
                self.receiveProgressStop = True
                self.com.close()
                self.setDisableSettingsSignal.emit(False)
            else:
                try:
                    self.com.baudrate = int(
                        self.serailBaudrateCombobox.currentText())
                    self.com.port = self.serialPortCombobox.currentText(
                    ).split(" ")[0]
                    self.com.bytesize = int(
                        self.serailBytesCombobox.currentText())
                    self.com.parity = self.serailParityCombobox.currentText(
                    )[0]
                    self.com.stopbits = float(
                        self.serailStopbitsCombobox.currentText())
                    self.com.timeout = None
                    if self.checkBoxRts.isChecked():
                        self.com.rts = False
                    else:
                        self.com.rts = True
                    if self.checkBoxDtr.isChecked():
                        self.com.dtr = False
                    else:
                        self.com.dtr = True
                    self.com.open()
                    # print("open success")
                    # print(self.com)
                    self.setDisableSettingsSignal.emit(True)
                    self.receiveProcess = threading.Thread(
                        target=self.receiveData)
                    self.receiveProcess.setDaemon(True)
                    self.receiveProcess.start()
                except Exception as e:
                    self.com.close()
                    self.receiveProgressStop = True
                    self.errorSignal.emit(parameters.strOpenFailed + "\n" +
                                          str(e))
                    self.setDisableSettingsSignal.emit(False)
        except Exception as e:
            print(e)

    def setDisableSettings(self, disable):
        if disable:
            self.serialOpenCloseButton.setText(parameters.strClose)
            self.statusBarStauts.setText("<font color=%s>%s</font>" %
                                         ("#008200", parameters.strReady))
            self.serialPortCombobox.setDisabled(True)
            self.serailBaudrateCombobox.setDisabled(True)
            self.serailParityCombobox.setDisabled(True)
            self.serailStopbitsCombobox.setDisabled(True)
            self.serailBytesCombobox.setDisabled(True)
            self.serialOpenCloseButton.setDisabled(False)
        else:
            self.serialOpenCloseButton.setText(parameters.strOpen)
            self.statusBarStauts.setText("<font color=%s>%s</font>" %
                                         ("#f31414", parameters.strClosed))
            self.serialPortCombobox.setDisabled(False)
            self.serailBaudrateCombobox.setDisabled(False)
            self.serailParityCombobox.setDisabled(False)
            self.serailStopbitsCombobox.setDisabled(False)
            self.serailBytesCombobox.setDisabled(False)
            self.programExitSaveParameters()

    def openCloseSerial(self):
        t = threading.Thread(target=self.openCloseSerialProcess)
        t.setDaemon(True)
        t.start()

    def rtsChanged(self):
        if self.checkBoxRts.isChecked():
            self.com.setRTS(False)
        else:
            self.com.setRTS(True)

    def dtrChanged(self):
        if self.checkBoxDtr.isChecked():
            self.com.setDTR(False)
        else:
            self.com.setDTR(True)

    def portComboboxClicked(self):
        self.detectSerialPort()

    def getSendData(self):
        data = self.sendArea.toPlainText()
        if self.sendSettingsCFLF.isChecked():
            data = data.replace("\n", "\r\n")
        if self.sendSettingsHex.isChecked():
            if self.sendSettingsCFLF.isChecked():
                data = data.replace("\r\n", " ")
            else:
                data = data.replace("\n", " ")
            data = self.hexStringB2Hex(data)
            if data == -1:
                self.errorSignal.emit(parameters.strWriteFormatError)
                return -1
        else:
            data = data.encode(self.encodingCombobox.currentText(), "ignore")
        return data

    def sendData(self):
        try:
            if self.com.is_open:
                data = self.getSendData()
                if data == -1:
                    return
                # print(self.sendArea.toPlainText())
                # print("send:",data)
                self.sendCount += len(data)
                self.com.write(data)
                data = self.sendArea.toPlainText()
                self.sendHistoryFindDelete(data)
                self.sendHistory.insertItem(0, data)
                self.sendHistory.setCurrentIndex(0)
                self.receiveUpdateSignal.emit("")
                # scheduled send
                if self.sendSettingsScheduledCheckBox.isChecked():
                    if not self.isScheduledSending:
                        t = threading.Thread(target=self.scheduledSend)
                        t.setDaemon(True)
                        t.start()
        except Exception as e:
            self.errorSignal.emit(parameters.strWriteError)
            # print(e)

    def scheduledSend(self):
        self.isScheduledSending = True
        while self.sendSettingsScheduledCheckBox.isChecked():
            self.sendData()
            try:
                time.sleep(
                    int(self.sendSettingsScheduled.text().strip()) / 1000)
            except Exception:
                self.errorSignal.emit(parameters.strTimeFormatError)
        self.isScheduledSending = False

    def receiveData(self):
        self.receiveProgressStop = False
        self.timeLastReceive = 0
        while (not self.receiveProgressStop):
            try:
                # length = self.com.in_waiting
                length = max(1, min(2048, self.com.in_waiting))
                bytes = self.com.read(length)
                if bytes != None:

                    # if self.isWaveOpen:
                    #     self.wave.displayData(bytes)
                    self.receiveCount += len(bytes)
                    if self.receiveSettingsAutoLinefeed.isChecked():
                        if time.time() - self.timeLastReceive > int(
                                self.receiveSettingsAutoLinefeedTime.text(
                                )) / 1000:
                            if self.sendSettingsCFLF.isChecked():
                                self.receiveUpdateSignal.emit("\r\n")
                            else:
                                self.receiveUpdateSignal.emit("\n")
                            self.timeLastReceive = time.time()
                    if self.receiveSettingsHex.isChecked():
                        strReceived = self.asciiB2HexString(bytes)
                        self.receiveUpdateSignal.emit(strReceived)
                    else:
                        self.receiveUpdateSignal.emit(
                            bytes.decode(self.encodingCombobox.currentText(),
                                         "ignore"))
            except Exception as e:
                # print("receiveData error")
                # if self.com.is_open and not self.serialPortCombobox.isEnabled():
                #     self.openCloseSerial()
                #     self.serialPortCombobox.clear()
                #     self.detectSerialPort()
                if 'multiple access' in str(e):
                    self.errorSignal.emit(
                        "device disconnected or multiple access on port?")
                break
            # time.sleep(0.009)

    def updateReceivedDataDisplay(self, str):
        if str != "":
            curScrollValue = self.receiveArea.verticalScrollBar().value()
            self.receiveArea.moveCursor(QTextCursor.End)
            endScrollValue = self.receiveArea.verticalScrollBar().value()
            self.receiveArea.insertPlainText(str)
            if curScrollValue < endScrollValue:
                self.receiveArea.verticalScrollBar().setValue(curScrollValue)
            else:
                self.receiveArea.moveCursor(QTextCursor.End)
        self.statusBarSendCount.setText("%s(bytes):%d" %
                                        (parameters.strSend, self.sendCount))
        self.statusBarReceiveCount.setText(
            "%s(bytes):%d" % (parameters.strReceive, self.receiveCount))

    def onSendSettingsHexClicked(self):

        data = self.sendArea.toPlainText().replace("\n", "\r\n")
        data = self.asciiB2HexString(data.encode())
        self.sendArea.clear()
        self.sendArea.insertPlainText(data)

    def onSendSettingsAsciiClicked(self):
        try:
            data = self.sendArea.toPlainText().replace("\n", " ").strip()
            self.sendArea.clear()
            if data != "":
                data = self.hexStringB2Hex(data).decode(
                    self.encodingCombobox.currentText(), 'ignore')
                self.sendArea.insertPlainText(data)
        except Exception as e:
            # QMessageBox.information(self,parameters.strWriteFormatError,parameters.strWriteFormatError)
            print("format error")

    def sendHistoryIndexChanged(self):
        self.sendArea.clear()
        self.sendArea.insertPlainText(self.sendHistory.currentText())

    def clearReceiveBuffer(self):
        self.receiveArea.clear()
        self.receiveCount = 0
        self.sendCount = 0
        self.receiveUpdateSignal.emit(None)

    def MoveToCenter(self):
        qr = self.frameGeometry()
        cp = QDesktopWidget().availableGeometry().center()
        qr.moveCenter(cp)
        self.move(qr.topLeft())

    def errorHint(self, str):
        QMessageBox.information(self, str, str)

    def closeEvent(self, event):

        reply = QMessageBox.question(self, 'Sure To Quit?',
                                     "Are you sure to quit?",
                                     QMessageBox.Yes | QMessageBox.No,
                                     QMessageBox.No)
        if reply == QMessageBox.Yes:
            self.com.close()
            self.receiveProgressStop = True
            self.programExitSaveParameters()
            event.accept()
        else:
            event.ignore()

    def findSerialPort(self):
        self.port_list = list(serial.tools.list_ports.comports())
        return self.port_list

    def portChanged(self):
        self.serialPortCombobox.setCurrentIndex(0)
        self.serialPortCombobox.setToolTip(str(self.portList[0]))

    def detectSerialPort(self):
        if not self.isDetectSerialPort:
            self.isDetectSerialPort = True
            t = threading.Thread(target=self.detectSerialPortProcess)
            t.setDaemon(True)
            t.start()

    def showCombobox(self):
        self.serialPortCombobox.showPopup()

    def detectSerialPortProcess(self):
        while (1):
            portList = self.findSerialPort()
            if len(portList) > 0:
                currText = self.serialPortCombobox.currentText()
                self.serialPortCombobox.clear()
                for i in portList:
                    showStr = str(i[0]) + " " + str(i[1])
                    if i[0].startswith("/dev/cu.Bluetooth-Incoming-Port"):
                        continue
                    self.serialPortCombobox.addItem(showStr)
                index = self.serialPortCombobox.findText(currText)
                if index >= 0:
                    self.serialPortCombobox.setCurrentIndex(index)
                else:
                    self.serialPortCombobox.setCurrentIndex(0)
                break
            time.sleep(1)
        self.showSerialComboboxSignal.emit()
        self.isDetectSerialPort = False

    def sendHistoryFindDelete(self, str):
        self.sendHistory.removeItem(self.sendHistory.findText(str))

    def asciiB2HexString(self, strB):
        strHex = binascii.b2a_hex(strB).upper()
        return re.sub(r"(?<=\w)(?=(?:\w\w)+$)", " ", strHex.decode()) + " "

    def hexStringB2Hex(self, hexString):
        dataList = hexString.split(" ")
        j = 0
        for i in dataList:
            if len(i) > 2:
                return -1
            elif len(i) == 1:
                dataList[j] = "0" + i
            j += 1
        data = "".join(dataList)
        try:
            data = bytes.fromhex(data)
        except Exception:
            return -1
        # print(data)
        return data

    def programExitSaveParameters(self):
        paramObj = parameters.ParametersToSave()
        paramObj.baudRate = self.serailBaudrateCombobox.currentIndex()
        paramObj.dataBytes = self.serailBytesCombobox.currentIndex()
        paramObj.parity = self.serailParityCombobox.currentIndex()
        paramObj.stopBits = self.serailStopbitsCombobox.currentIndex()
        paramObj.skin = self.param.skin
        if self.receiveSettingsHex.isChecked():
            paramObj.receiveAscii = False
        if not self.receiveSettingsAutoLinefeed.isChecked():
            paramObj.receiveAutoLinefeed = False
        else:
            paramObj.receiveAutoLinefeed = True
        paramObj.receiveAutoLindefeedTime = self.receiveSettingsAutoLinefeedTime.text(
        )
        if self.sendSettingsHex.isChecked():
            paramObj.sendAscii = False
        if not self.sendSettingsScheduledCheckBox.isChecked():
            paramObj.sendScheduled = False
        paramObj.sendScheduledTime = self.sendSettingsScheduled.text()
        if not self.sendSettingsCFLF.isChecked():
            paramObj.useCRLF = False
        paramObj.sendHistoryList.clear()
        for i in range(0, self.sendHistory.count()):
            paramObj.sendHistoryList.append(self.sendHistory.itemText(i))
        if self.checkBoxRts.isChecked():
            paramObj.rts = 1
        else:
            paramObj.rts = 0
        if self.checkBoxDtr.isChecked():
            paramObj.dtr = 1
        else:
            paramObj.dtr = 0
        paramObj.encodingIndex = self.encodingCombobox.currentIndex()
        f = open(parameters.configFilePath, "wb")
        f.truncate()
        pickle.dump(paramObj, f)
        pickle.dump(paramObj.sendHistoryList, f)
        f.close()

    def programStartGetSavedParameters(self):
        paramObj = parameters.ParametersToSave()
        try:
            f = open(parameters.configFilePath, "rb")
            paramObj = pickle.load(f)
            paramObj.sendHistoryList = pickle.load(f)
            f.close()
        except Exception as e:
            f = open(parameters.configFilePath, "wb")
            f.close()
        self.serailBaudrateCombobox.setCurrentIndex(paramObj.baudRate)
        self.serailBytesCombobox.setCurrentIndex(paramObj.dataBytes)
        self.serailParityCombobox.setCurrentIndex(paramObj.parity)
        self.serailStopbitsCombobox.setCurrentIndex(paramObj.stopBits)
        if paramObj.receiveAscii == False:
            self.receiveSettingsHex.setChecked(True)
        if paramObj.receiveAutoLinefeed == False:
            self.receiveSettingsAutoLinefeed.setChecked(False)
        else:
            self.receiveSettingsAutoLinefeed.setChecked(True)
        self.receiveSettingsAutoLinefeedTime.setText(
            paramObj.receiveAutoLindefeedTime)
        if paramObj.sendAscii == False:
            self.sendSettingsHex.setChecked(True)
        if paramObj.sendScheduled == False:
            self.sendSettingsScheduledCheckBox.setChecked(False)
        else:
            self.sendSettingsScheduledCheckBox.setChecked(True)
        self.sendSettingsScheduled.setText(paramObj.sendScheduledTime)
        if paramObj.useCRLF == False:
            self.sendSettingsCFLF.setChecked(False)
        else:
            self.sendSettingsCFLF.setChecked(True)
        for i in range(0, len(paramObj.sendHistoryList)):
            str = paramObj.sendHistoryList[i]
            self.sendHistory.addItem(str)
        if paramObj.rts == 0:
            self.checkBoxRts.setChecked(False)
        else:
            self.checkBoxRts.setChecked(True)
        if paramObj.dtr == 0:
            self.checkBoxDtr.setChecked(False)
        else:
            self.checkBoxDtr.setChecked(True)
        self.encodingCombobox.setCurrentIndex(paramObj.encodingIndex)
        self.param = paramObj

    def keyPressEvent(self, event):
        if event.key() == Qt.Key_Control:
            self.keyControlPressed = True
        elif event.key() == Qt.Key_Return or event.key() == Qt.Key_Enter:
            if self.keyControlPressed:
                self.sendData()
        elif event.key() == Qt.Key_L:
            if self.keyControlPressed:
                self.sendArea.clear()
        elif event.key() == Qt.Key_K:
            if self.keyControlPressed:
                self.receiveArea.clear()

    def keyReleaseEvent(self, event):
        if event.key() == Qt.Key_Control:
            self.keyControlPressed = False

    def sendAreaFontChanged(self, font):
        print("font changed")

    def functionAdd(self):
        QMessageBox.information(self, "On the way", "On the way")

    def showHideSettings(self):
        if self.isHideSettings:
            self.showSettings()
            self.isHideSettings = False
        else:
            self.hideSettings()
            self.isHideSettings = True

    def showSettings(self):
        self.settingWidget.show()
        self.settingsButton.setStyleSheet(
            parameters.strStyleShowHideButtonLeft.replace(
                "$DataPath", self.DataPath))

    def hideSettings(self):
        self.settingWidget.hide()
        self.settingsButton.setStyleSheet(
            parameters.strStyleShowHideButtonRight.replace(
                "$DataPath", self.DataPath))

    def showHideFunctional(self):
        if self.isHideFunctinal:
            self.showFunctional()
            self.isHideFunctinal = False
        else:
            self.hideFunctional()
            self.isHideFunctinal = True

    def showFunctional(self):
        self.functionalWiget.show()
        self.functionalButton.setStyleSheet(
            parameters.strStyleShowHideButtonRight.replace(
                "$DataPath", self.DataPath))

    def hideFunctional(self):
        self.functionalWiget.hide()
        self.functionalButton.setStyleSheet(
            parameters.strStyleShowHideButtonLeft.replace(
                "$DataPath", self.DataPath))

    def skinChange(self):
        if self.param.skin == 1:  # light
            file = open(self.DataPath + '/assets/qss/style-dark.qss', "r")
            self.param.skin = 2
        else:  # elif self.param.skin == 2: # dark
            file = open(self.DataPath + '/assets/qss/style.qss', "r")
            self.param.skin = 1
        self.app.setStyleSheet(file.read().replace("$DataPath", self.DataPath))

    def showAbout(self):
        QMessageBox.information(
            self, "About",
            "<h1 style='color:#f75a5a';margin=10px;>" + parameters.appName +
            '</h1><br><b style="color:#08c7a1;margin = 5px;">V' +
            str(helpAbout.versionMajor) + "." + str(helpAbout.versionMinor) +
            "." + str(helpAbout.versionDev) + "</b><br><br>" + helpAbout.date +
            "<br><br>" + helpAbout.strAbout())

    def selectFile(self):
        oldPath = self.filePathWidget.text()
        if oldPath == "":
            oldPath = os.getcwd()
        fileName_choose, filetype = QFileDialog.getOpenFileName(
            self, "SelectFile", oldPath, "All Files (*);;")

        if fileName_choose == "":
            return
        self.filePathWidget.setText(fileName_choose)

    def sendFile(self):
        filename = self.filePathWidget.text()
        if not os.path.exists(filename):
            self.errorSignal.emit("File path error\npath:%s" % (filename))
            return
        try:
            f = open(filename, "rb")
        except Exception as e:
            self.errorSignal.emit("Open file %s failed! \n %s" %
                                  (filename, str(e)))
            return
        self.com.write(f.read())  #TODO: optimize send in new thread
        f.close()

    def clearHistory(self):
        self.param.sendHistoryList.clear()
        self.sendHistory.clear()
        self.errorSignal.emit("History cleared!")

    def autoUpdateDetect(self):
        auto = autoUpdate.AutoUpdate()
        if auto.detectNewVersion():
            auto.OpenBrowser()

    def openDevManagement(self):
        os.system('start devmgmt.msc')
Exemple #3
0
class Plugin(Plugin_Base):
    '''
        call sequence:
            set vars like hintSignal, hintSignal
            onInit
            onWidget
            onUiInitDone
                send
                onReceived
    '''
    # vars set by caller
    send = None              # send(data_bytes=None, file_path=None)
    hintSignal = None       # hintSignal.emit(title, msg)
    configGlobal = {}
    # other vars
    connParent = "main"
    connChilds = []
    id = "dbg"
    name = _("Send Receive")
    #
    receiveUpdateSignal = pyqtSignal(str, list, str) # head, content, encoding
    receiveProgressStop = False
    receivedData = []
    lock = threading.Lock()
    sendRecord = []
    lastColor = None
    lastBg = None
    defaultColor = None
    defaultBg = None
    help = '''{}<br>
<b style="color:#ef5350;"><kbd>F11</kbd></b>: {}<br>
<b style="color:#ef5350;"><kbd>Ctrl+Enter</kbd></b>: {}<br>
<b style="color:#ef5350;"><kbd>Ctrl+L</kbd></b>: {}<br>
<b style="color:#ef5350;"><kbd>Ctrl+K</kbd></b>: {}<br>
'''.format(
        _('Shortcut:'),
        _('Full screen'),
        _('Send data'),
        _('Clear Send Area'),
        _('Clear Receive Area')
    )

    def onInit(self, config):
        super().onInit(config)
        self.keyControlPressed = False
        self.isScheduledSending = False
        self.config = config
        default = {
            "version": 1,
            "receiveAscii" : True,
            "receiveAutoLinefeed" : False,
            "receiveAutoLindefeedTime" : 200,
            "sendAscii" : True,
            "sendScheduled" : False,
            "sendScheduledTime" : 300,
            "sendAutoNewline": False,
            "useCRLF" : True,
            "showTimestamp" : False,
            "recordSend" : False,
            "saveLogPath" : "",
            "saveLog" : False,
            "color" : False,
            "sendEscape" : False,
            "customSendItems" : [],
            "sendHistoryList" : [],
        }
        for k in default:
            if not k in self.config:
                self.config[k] = default[k]

    def onWidgetMain(self, parent):
        self.mainWidget = QSplitter(Qt.Vertical)
        # widgets receive and send area
        self.receiveArea = QTextEdit()
        font = QFont('Menlo,Consolas,Bitstream Vera Sans Mono,Courier New,monospace, Microsoft YaHei', 10)
        self.receiveArea.setFont(font)
        self.receiveArea.setLineWrapMode(QTextEdit.NoWrap)
        self.sendArea = QTextEdit()
        self.sendArea.setLineWrapMode(QTextEdit.NoWrap)
        self.sendArea.setAcceptRichText(False)
        self.clearReceiveButtion = QPushButton("")
        utils_ui.setButtonIcon(self.clearReceiveButtion, "mdi6.broom")
        self.sendButton = QPushButton("")
        utils_ui.setButtonIcon(self.sendButton, "fa.send")
        self.sendHistory = ComboBox()
        sendWidget = QWidget()
        sendAreaWidgetsLayout = QHBoxLayout()
        sendAreaWidgetsLayout.setContentsMargins(0,4,0,0)
        sendWidget.setLayout(sendAreaWidgetsLayout)
        buttonLayout = QVBoxLayout()
        buttonLayout.addWidget(self.clearReceiveButtion)
        buttonLayout.addStretch(1)
        buttonLayout.addWidget(self.sendButton)
        sendAreaWidgetsLayout.addWidget(self.sendArea)
        sendAreaWidgetsLayout.addLayout(buttonLayout)
        self.mainWidget.addWidget(self.receiveArea)
        self.mainWidget.addWidget(sendWidget)
        self.mainWidget.addWidget(self.sendHistory)
        self.mainWidget.setStretchFactor(0, 7)
        self.mainWidget.setStretchFactor(1, 2)
        self.mainWidget.setStretchFactor(2, 1)
        # event
        self.sendButton.clicked.connect(self.onSendData)
        self.clearReceiveButtion.clicked.connect(self.clearReceiveBuffer)
        self.receiveUpdateSignal.connect(self.updateReceivedDataDisplay)
        self.sendHistory.activated.connect(self.onSendHistoryIndexChanged)

        return self.mainWidget

    def onWidgetSettings(self, parent):
        # serial receive settings
        layout = QVBoxLayout()
        serialReceiveSettingsLayout = QGridLayout()
        serialReceiveSettingsGroupBox = QGroupBox(_("Receive Settings"))
        self.receiveSettingsAscii = QRadioButton(_("ASCII"))
        self.receiveSettingsAscii.setToolTip(_("Show recived data as visible format, select decode method at top right corner"))
        self.receiveSettingsHex = QRadioButton(_("HEX"))
        self.receiveSettingsHex.setToolTip(_("Show recived data as hex format"))
        self.receiveSettingsAscii.setChecked(True)
        self.receiveSettingsAutoLinefeed = QCheckBox(_("Auto\nLinefeed\nms"))
        self.receiveSettingsAutoLinefeed.setToolTip(_("Auto linefeed after interval, unit: ms"))
        self.receiveSettingsAutoLinefeedTime = QLineEdit("200")
        self.receiveSettingsAutoLinefeedTime.setProperty("class", "smallInput")
        self.receiveSettingsAutoLinefeedTime.setToolTip(_("Auto linefeed after interval, unit: ms"))
        self.receiveSettingsAutoLinefeed.setMaximumWidth(75)
        self.receiveSettingsAutoLinefeedTime.setMaximumWidth(75)
        self.receiveSettingsTimestamp = QCheckBox(_("Timestamp"))
        self.receiveSettingsTimestamp.setToolTip(_("Add timestamp before received data, will automatically enable auto line feed"))
        self.receiveSettingsColor = QCheckBox(_("Color"))
        self.receiveSettingsColor.setToolTip(_("Enable unix terminal color support, e.g. \\33[31;43mhello\\33[0m"))
        serialReceiveSettingsLayout.addWidget(self.receiveSettingsAscii,1,0,1,1)
        serialReceiveSettingsLayout.addWidget(self.receiveSettingsHex,1,1,1,1)
        serialReceiveSettingsLayout.addWidget(self.receiveSettingsAutoLinefeed, 2, 0, 1, 1)
        serialReceiveSettingsLayout.addWidget(self.receiveSettingsAutoLinefeedTime, 2, 1, 1, 1)
        serialReceiveSettingsLayout.addWidget(self.receiveSettingsTimestamp, 3, 0, 1, 1)
        serialReceiveSettingsLayout.addWidget(self.receiveSettingsColor, 3, 1, 1, 1)
        serialReceiveSettingsGroupBox.setLayout(serialReceiveSettingsLayout)
        serialReceiveSettingsGroupBox.setAlignment(Qt.AlignHCenter)
        layout.addWidget(serialReceiveSettingsGroupBox)

        # serial send settings
        serialSendSettingsLayout = QGridLayout()
        serialSendSettingsGroupBox = QGroupBox(_("Send Settings"))
        self.sendSettingsAscii = QRadioButton(_("ASCII"))
        self.sendSettingsHex = QRadioButton(_("HEX"))
        self.sendSettingsAscii.setToolTip(_("Get send data as visible format, select encoding method at top right corner"))
        self.sendSettingsHex.setToolTip(_("Get send data as hex format, e.g. hex '31 32 33' equal to ascii '123'"))
        self.sendSettingsAscii.setChecked(True)
        self.sendSettingsScheduledCheckBox = QCheckBox(_("Timed Send\nms"))
        self.sendSettingsScheduledCheckBox.setToolTip(_("Timed send, unit: ms"))
        self.sendSettingsScheduled = QLineEdit("300")
        self.sendSettingsScheduled.setProperty("class", "smallInput")
        self.sendSettingsScheduled.setToolTip(_("Timed send, unit: ms"))
        self.sendSettingsScheduledCheckBox.setMaximumWidth(75)
        self.sendSettingsScheduled.setMaximumWidth(75)
        self.sendSettingsCRLF = QCheckBox(_("<CRLF>"))
        self.sendSettingsCRLF.setToolTip(_("Select to send \\r\\n instead of \\n"))
        self.sendSettingsCRLF.setChecked(False)
        self.sendSettingsRecord = QCheckBox(_("Record"))
        self.sendSettingsRecord.setToolTip(_("Record send data"))
        self.sendSettingsEscape= QCheckBox(_("Escape"))
        self.sendSettingsEscape.setToolTip(_("Enable escape characters support like \\t \\r \\n \\x01 \\001"))
        self.sendSettingsAppendNewLine= QCheckBox(_("Newline"))
        self.sendSettingsAppendNewLine.setToolTip(_("Auto add new line when send"))
        serialSendSettingsLayout.addWidget(self.sendSettingsAscii,1,0,1,1)
        serialSendSettingsLayout.addWidget(self.sendSettingsHex,1,1,1,1)
        serialSendSettingsLayout.addWidget(self.sendSettingsScheduledCheckBox, 2, 0, 1, 1)
        serialSendSettingsLayout.addWidget(self.sendSettingsScheduled, 2, 1, 1, 1)
        serialSendSettingsLayout.addWidget(self.sendSettingsCRLF, 3, 0, 1, 1)
        serialSendSettingsLayout.addWidget(self.sendSettingsAppendNewLine, 3, 1, 1, 1)
        serialSendSettingsLayout.addWidget(self.sendSettingsEscape, 4, 0, 1, 2)
        serialSendSettingsLayout.addWidget(self.sendSettingsEscape, 4, 0, 1, 2)
        serialSendSettingsLayout.addWidget(self.sendSettingsRecord, 4, 1, 1, 1)
        serialSendSettingsGroupBox.setLayout(serialSendSettingsLayout)
        layout.addWidget(serialSendSettingsGroupBox)

        widget = QWidget()
        widget.setLayout(layout)
        layout.setContentsMargins(0,0,0,0)
        # event
        self.receiveSettingsTimestamp.clicked.connect(self.onTimeStampClicked)
        self.receiveSettingsAutoLinefeed.clicked.connect(self.onAutoLinefeedClicked)
        self.receiveSettingsAscii.clicked.connect(lambda : self.bindVar(self.receiveSettingsAscii, self.config, "receiveAscii"))
        self.receiveSettingsHex.clicked.connect(lambda : self.bindVar(self.receiveSettingsHex, self.config, "receiveAscii", invert = True))
        self.sendSettingsHex.clicked.connect(self.onSendSettingsHexClicked)
        self.sendSettingsAscii.clicked.connect(self.onSendSettingsAsciiClicked)
        self.sendSettingsRecord.clicked.connect(self.onRecordSendClicked)
        self.sendSettingsAppendNewLine.clicked.connect(lambda: self.bindVar(self.sendSettingsAppendNewLine, self.config, "sendAutoNewline"))
        self.sendSettingsEscape.clicked.connect(lambda: self.bindVar(self.sendSettingsEscape, self.config, "sendEscape"))
        self.sendSettingsCRLF.clicked.connect(lambda: self.bindVar(self.sendSettingsCRLF, self.config, "useCRLF"))
        self.receiveSettingsColor.clicked.connect(self.onSetColorChanged)
        self.receiveSettingsAutoLinefeedTime.textChanged.connect(lambda: self.bindVar(self.receiveSettingsAutoLinefeedTime, self.config, "receiveAutoLindefeedTime", vtype=int, vErrorMsg=_("Auto line feed value error, must be integer"), emptyDefault = "200"))
        self.sendSettingsScheduled.textChanged.connect(lambda: self.bindVar(self.sendSettingsScheduled, self.config, "sendScheduledTime", vtype=int, vErrorMsg=_("Timed send value error, must be integer"), emptyDefault = "300"))
        self.sendSettingsScheduledCheckBox.clicked.connect(lambda: self.bindVar(self.sendSettingsScheduledCheckBox, self.config, "sendScheduled"))
        return widget


    def onWidgetFunctional(self, parent):
        sendFunctionalLayout = QVBoxLayout()
        sendFunctionalLayout.setContentsMargins(0,0,0,0)
        # right functional layout
        self.filePathWidget = QLineEdit()
        self.openFileButton = QPushButton(_("Open File"))
        self.sendFileButton = QPushButton(_("Send File"))
        self.clearHistoryButton = QPushButton(_("Clear History"))
        self.addButton = QPushButton("")
        utils_ui.setButtonIcon(self.addButton, "fa.plus")
        self.fileSendGroupBox = QGroupBox(_("Sendding File"))
        fileSendGridLayout = QGridLayout()
        fileSendGridLayout.addWidget(self.filePathWidget, 0, 0, 1, 1)
        fileSendGridLayout.addWidget(self.openFileButton, 0, 1, 1, 1)
        fileSendGridLayout.addWidget(self.sendFileButton, 1, 0, 1, 2)
        self.fileSendGroupBox.setLayout(fileSendGridLayout)
        self.logFileGroupBox = QGroupBox(_("Save log"))
        # cumtom send zone
        #   groupbox
        customSendGroupBox = QGroupBox(_("Cutom send"))
        customSendItemsLayout0 = QVBoxLayout()
        customSendItemsLayout0.setContentsMargins(0,8,0,0)
        customSendGroupBox.setLayout(customSendItemsLayout0)
        #   scroll

        self.customSendScroll = QScrollArea()
        self.customSendScroll.setMinimumHeight(parameters.customSendItemHeight + 20)
        self.customSendScroll.setWidgetResizable(True)
        self.customSendScroll.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
        #   add scroll to groupbox
        customSendItemsLayout0.addWidget(self.customSendScroll)
        #   wrapper widget
        cutomSendItemsWraper = QWidget()
        customSendItemsLayoutWrapper = QVBoxLayout()
        customSendItemsLayoutWrapper.setContentsMargins(0,0,0,0)
        cutomSendItemsWraper.setLayout(customSendItemsLayoutWrapper)
        #    custom items
        customItems = QWidget()
        self.customSendItemsLayout = QVBoxLayout()
        self.customSendItemsLayout.setContentsMargins(0,0,0,0)
        customItems.setLayout(self.customSendItemsLayout)
        customSendItemsLayoutWrapper.addWidget(customItems)
        customSendItemsLayoutWrapper.addWidget(self.addButton)
        #   set wrapper widget
        self.customSendScroll.setWidget(cutomSendItemsWraper)
        self.customSendScroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        #
        logFileLayout = QHBoxLayout()
        self.saveLogCheckbox = QCheckBox()
        self.logFilePath = QLineEdit()
        self.logFileBtn = QPushButton(_("Log path"))
        logFileLayout.addWidget(self.saveLogCheckbox)
        logFileLayout.addWidget(self.logFilePath)
        logFileLayout.addWidget(self.logFileBtn)
        self.logFileGroupBox.setLayout(logFileLayout)
        sendFunctionalLayout.addWidget(self.logFileGroupBox)
        sendFunctionalLayout.addWidget(self.fileSendGroupBox)
        sendFunctionalLayout.addWidget(self.clearHistoryButton)
        sendFunctionalLayout.addWidget(customSendGroupBox)
        sendFunctionalLayout.addStretch(1)
        self.funcWidget = QWidget()
        self.funcWidget.setLayout(sendFunctionalLayout)
        # event
        self.sendFileButton.clicked.connect(self.sendFile)
        self.saveLogCheckbox.clicked.connect(self.setSaveLog)
        self.logFileBtn.clicked.connect(self.selectLogFile)
        self.openFileButton.clicked.connect(self.selectFile)
        self.addButton.clicked.connect(self.customSendAdd)
        self.clearHistoryButton.clicked.connect(self.clearHistory)
        self.funcParent = parent
        return self.funcWidget

    def onWidgetStatusBar(self, parent):
        self.statusBar = statusBar(rxTxCount=True)
        return self.statusBar

    def onUiInitDone(self):
        paramObj = self.config
        self.receiveSettingsHex.setChecked(not paramObj["receiveAscii"])
        self.receiveSettingsAutoLinefeed.setChecked(paramObj["receiveAutoLinefeed"])
        try:
            interval = int(paramObj["receiveAutoLindefeedTime"])
            paramObj["receiveAutoLindefeedTime"] = interval
        except Exception:
            interval = parameters.Parameters.receiveAutoLindefeedTime
        self.receiveSettingsAutoLinefeedTime.setText(str(interval) if interval > 0 else str(parameters.Parameters.receiveAutoLindefeedTime))
        self.receiveSettingsTimestamp.setChecked(paramObj["showTimestamp"])
        self.sendSettingsHex.setChecked(not paramObj["sendAscii"])
        self.sendSettingsScheduledCheckBox.setChecked(paramObj["sendScheduled"])
        try:
            interval = int(paramObj["sendScheduledTime"])
            paramObj["sendScheduledTime"] = interval
        except Exception:
            interval = parameters.Parameters.sendScheduledTime
        self.sendSettingsScheduled.setText(str(interval) if interval > 0 else str(parameters.Parameters.sendScheduledTime))
        self.sendSettingsCRLF.setChecked(paramObj["useCRLF"])
        self.sendSettingsAppendNewLine.setChecked(paramObj["sendAutoNewline"])
        self.sendSettingsRecord.setChecked(paramObj["recordSend"])
        self.sendSettingsEscape.setChecked(paramObj["sendEscape"])
        for i in range(0, len(paramObj["sendHistoryList"])):
            text = paramObj["sendHistoryList"][i]
            self.sendHistory.addItem(text)
        self.logFilePath.setText(paramObj["saveLogPath"])
        self.logFilePath.setToolTip(paramObj["saveLogPath"])
        self.saveLogCheckbox.setChecked(paramObj["saveLog"])
        self.receiveSettingsColor.setChecked(paramObj["color"])
        # send items
        for text in paramObj["customSendItems"]:
            self.insertSendItem(text, load=True)

        self.receiveProcess = threading.Thread(target=self.receiveDataProcess)
        self.receiveProcess.setDaemon(True)
        self.receiveProcess.start()

    def onSendSettingsHexClicked(self):
        self.config["sendAscii"] = False
        data = self.sendArea.toPlainText().replace("\n","\r\n")
        data = utils.bytes_to_hex_str(data.encode())
        self.sendArea.clear()
        self.sendArea.insertPlainText(data)

    def onSendSettingsAsciiClicked(self):
        self.config["sendAscii"] = True
        try:
            data = self.sendArea.toPlainText().replace("\n"," ").strip()
            self.sendArea.clear()
            if data != "":
                data = utils.hex_str_to_bytes(data).decode(self.configGlobal["encoding"],'ignore')
                self.sendArea.insertPlainText(data)
        except Exception as e:
            # QMessageBox.information(self,self.strings.strWriteFormatError,self.strings.strWriteFormatError)
            print("format error")

    def onAutoLinefeedClicked(self):
        if (self.config["showTimestamp"] or self.config["recordSend"]) and not self.receiveSettingsAutoLinefeed.isChecked():
            self.receiveSettingsAutoLinefeed.setChecked(True)
            self.hintSignal.emit("warning", _("Warning"), _("linefeed always on if timestamp or record send is on"))
        self.config["receiveAutoLinefeed"] = self.receiveSettingsAutoLinefeed.isChecked()

    def onTimeStampClicked(self):
        self.config["showTimestamp"] = self.receiveSettingsTimestamp.isChecked()
        if self.config["showTimestamp"]:
            self.config["receiveAutoLinefeed"] = True
            self.receiveSettingsAutoLinefeed.setChecked(True)

    def onRecordSendClicked(self):
        self.config["recordSend"] = self.sendSettingsRecord.isChecked()
        if self.config["recordSend"]:
            self.config["receiveAutoLinefeed"] = True
            self.receiveSettingsAutoLinefeed.setChecked(True)

    def onEscapeSendClicked(self):
        self.config["sendEscape"] = self.sendSettingsEscape.isChecked()

    def onSetColorChanged(self):
        self.config["color"] = self.receiveSettingsColor.isChecked()

    def onSendHistoryIndexChanged(self, idx):
        self.sendArea.clear()
        self.sendArea.insertPlainText(self.sendHistory.currentText())

    def clearHistory(self):
        self.config["sendHistoryList"].clear()
        self.sendHistory.clear()
        self.hintSignal.emit("info", _("OK"), _("History cleared!"))


    def onSent(self, ok, msg, length, path):
        if ok:
            self.statusBar.addTx(length)
        else:
            self.hintSignal.emit("error", _("Error"), _("Send data failed!") + " " + msg)

    def onSentFile(self, ok, msg, length, path):
        print("file sent {}, path: {}".format('ok' if ok else 'fail', path))
        if ok:
            self.sendFileButton.setText(_("Send file"))
            self.sendFileButton.setDisabled(False)
            self.statusBar.addTx(length)
        else:
            self.hintSignal.emit("error", _("Error"), _("Send file failed!") + " " + msg)

    def setSaveLog(self):
        if self.saveLogCheckbox.isChecked():
            self.config["saveLog"] = True
        else:
            self.config["saveLog"] = False

    def selectFile(self):
        oldPath = self.filePathWidget.text()
        if oldPath=="":
            oldPath = os.getcwd()
        fileName_choose, filetype = QFileDialog.getOpenFileName(self.mainWidget,
                                    _("Select file"),
                                    oldPath,
                                    _("All Files (*)"))

        if fileName_choose == "":
            return
        self.filePathWidget.setText(fileName_choose)
        self.filePathWidget.setToolTip(fileName_choose)

    def selectLogFile(self):
        oldPath = self.logFilePath.text()
        if oldPath=="":
            oldPath = os.getcwd()
        fileName_choose, filetype = QFileDialog.getSaveFileName(self.mainWidget,
                                    _("Select file"),
                                    os.path.join(oldPath, "comtool.log"),
                                    _("Log file (*.log);;txt file (*.txt);;All Files (*)"))

        if fileName_choose == "":
            return
        self.logFilePath.setText(fileName_choose)
        self.logFilePath.setToolTip(fileName_choose)
        self.config["saveLogPath"] = fileName_choose

    def onLog(self, text):
        if self.config["saveLogPath"]:
            with open(self.config["saveLogPath"], "a+", encoding=self.configGlobal["encoding"], newline="\n") as f:
                f.write(text)

    def onKeyPressEvent(self, event):
        if event.key() == Qt.Key_Control:
            self.keyControlPressed = True
        elif event.key() == Qt.Key_Return or event.key()==Qt.Key_Enter:
            if self.keyControlPressed:
                self.onSendData()
        elif event.key() == Qt.Key_L:
            if self.keyControlPressed:
                self.sendArea.clear()
        elif event.key() == Qt.Key_K:
            if self.keyControlPressed:
                self.receiveArea.clear()

    def onKeyReleaseEvent(self, event):
        if event.key() == Qt.Key_Control:
            self.keyControlPressed = False

    def insertSendItem(self, text="", load = False):
        itemsNum = self.customSendItemsLayout.count() + 1
        height = parameters.customSendItemHeight * (itemsNum + 1) + 20
        topHeight = self.fileSendGroupBox.height() + self.logFileGroupBox.height() + 100
        if height + topHeight > self.funcParent.height():
            height = self.funcParent.height() - topHeight
        if height < 0:
            height = self.funcParent.height() // 3
        self.customSendScroll.setMinimumHeight(height)
        item = QWidget()
        layout = QHBoxLayout()
        layout.setContentsMargins(0,0,0,0)
        item.setLayout(layout)
        cmd = QLineEdit(text)
        send = QPushButton("")
        utils_ui.setButtonIcon(send, "fa.send")
        cmd.setToolTip(text)
        send.setToolTip(text)
        cmd.textChanged.connect(lambda: self.onCustomItemChange(self.customSendItemsLayout.indexOf(item), cmd, send))
        send.setProperty("class", "smallBtn")
        send.clicked.connect(lambda: self.sendCustomItem(self.config["customSendItems"][self.customSendItemsLayout.indexOf(item)]))
        delete = QPushButton("")
        utils_ui.setButtonIcon(delete, "fa.close")
        delete.setProperty("class", "deleteBtn")
        layout.addWidget(cmd)
        layout.addWidget(send)
        layout.addWidget(delete)
        delete.clicked.connect(lambda: self.deleteSendItem(self.customSendItemsLayout.indexOf(item), item))
        self.customSendItemsLayout.addWidget(item)
        if not load:
            self.config["customSendItems"].append("")

    def deleteSendItem(self, idx, item):
        item.setParent(None)
        self.config["customSendItems"].pop(idx)
        itemsNum = self.customSendItemsLayout.count()
        height = parameters.customSendItemHeight * (itemsNum + 1) + 20
        topHeight = self.fileSendGroupBox.height() + self.logFileGroupBox.height() + 100
        if height + topHeight > self.funcParent.height():
            height = self.funcParent.height() - topHeight
        self.customSendScroll.setMinimumHeight(height)

    def onCustomItemChange(self, idx, edit, send):
        text = edit.text()
        edit.setToolTip(text)
        send.setToolTip(text)
        self.config["customSendItems"][idx] = text

    def sendCustomItem(self, text):
        self.onSendData(data = text)

    def customSendAdd(self):
        self.insertSendItem()

    def getSendData(self, data=None) -> bytes:
        if data is None:
            data = self.sendArea.toPlainText()
        return self.parseSendData(data, self.configGlobal["encoding"], self.config["useCRLF"], not self.config["sendAscii"], self.config["sendEscape"])

    def sendFile(self):
        filename = self.filePathWidget.text()
        if not os.path.exists(filename):
            self.hintSignal.emit("error", _("Error"), _("File path error\npath") + ":%s" %(filename))
            return
        if not self.isConnected():
            self.hintSignal.emit("warning", _("Warning"), _("Connect first please"))
        else:
            self.sendFileButton.setDisabled(True)
            self.sendFileButton.setText(_("Sending file"))
            self.send(file_path=filename, callback = lambda ok, msg, length, path: self.onSentFile(ok, msg, length, path))


    def scheduledSend(self):
        self.isScheduledSending = True
        while self.config["sendScheduled"]:
            self.onSendData()
            try:
                time.sleep(self.config["sendScheduledTime"]/1000)
            except Exception:
                self.hintSignal.emit("error", _("Error"), _("Time format error"))
        self.isScheduledSending = False

    def sendData(self, data_bytes = None):
        try:
            if self.isConnected():
                if not data_bytes or type(data_bytes) == str:
                    data = self.getSendData(data_bytes)
                else:
                    data = data_bytes
                if not data:
                    return
                if self.config["sendAutoNewline"]:
                    data += b"\r\n" if self.config["useCRLF"] else b"\n"
                # record send data
                if self.config["recordSend"]:
                    head = '=> '
                    if self.config["showTimestamp"]:
                        head += '[{}] '.format(utils.datetime_format_ms(datetime.now()))
                    isHexStr, sendStr, sendStrsColored = self.bytes2String(data, not self.config["receiveAscii"], encoding=self.configGlobal["encoding"])
                    if isHexStr:
                        sendStr = sendStr.upper()
                        sendStrsColored= sendStr
                        head += "[HEX] "
                    if self.config["useCRLF"]:
                        head = "\r\n" + head
                    else:
                        head = "\n" + head
                    if head.strip() != '=>':
                        head = '{}: '.format(head.rstrip())
                    self.receiveUpdateSignal.emit(head, [sendStrsColored], self.configGlobal["encoding"])
                    self.sendRecord.insert(0, head + sendStr)
                self.send(data_bytes=data, callback = self.onSent)
                if data_bytes:
                    data = str(data_bytes)
                else:
                    data = self.sendArea.toPlainText()
                self.sendHistoryFindDelete(data)
                self.sendHistory.insertItem(0,data)
                self.sendHistory.setCurrentIndex(0)
                try:
                    idx = self.config["sendHistoryList"].index(data)
                    self.config["sendHistoryList"].pop(idx)
                except Exception:
                     pass
                self.config["sendHistoryList"].insert(0, data)

                # scheduled send
                if self.config["sendScheduled"]:
                    if not self.isScheduledSending:
                        t = threading.Thread(target=self.scheduledSend)
                        t.setDaemon(True)
                        t.start()
        except Exception as e:
            import traceback
            traceback.print_exc()
            print("[Error] sendData: ", e)
            self.hintSignal.emit("error", _("Error"), _("Send Error") + str(e))
            # print(e)

    def onSendData(self, call=True, data=None):
        try:
            self.sendData(data)
        except Exception as e:
            print("[Error] onSendData: ", e)
            self.hintSignal.emit("error", _("Error"), _("get data error") + ": " + str(e))

    def updateReceivedDataDisplay(self, head : str, datas : list, encoding):
        if datas:
            curScrollValue = self.receiveArea.verticalScrollBar().value()
            self.receiveArea.moveCursor(QTextCursor.End)
            endScrollValue = self.receiveArea.verticalScrollBar().value()
            cursor = self.receiveArea.textCursor()
            format = cursor.charFormat()
            font = QFont('Menlo,Consolas,Bitstream Vera Sans Mono,Courier New,monospace, Microsoft YaHei', 10)
            format.setFont(font)
            if not self.defaultColor:
                self.defaultColor = format.foreground()
            if not self.defaultBg:
                self.defaultBg = format.background()
            if head:
                format.setForeground(self.defaultColor)
                cursor.setCharFormat(format)
                format.setBackground(self.defaultBg)
                cursor.setCharFormat(format)
                cursor.insertText(head)
            for data in datas:
                if type(data) == str:
                    self.receiveArea.insertPlainText(data)
                elif type(data) == list:
                    for color, bg, text in data:
                        if color:
                            format.setForeground(QColor(color))
                            cursor.setCharFormat(format)
                        else:
                            format.setForeground(self.defaultColor)
                            cursor.setCharFormat(format)
                        if bg:
                            format.setBackground(QColor(bg))
                            cursor.setCharFormat(format)
                        else:
                            format.setBackground(self.defaultBg)
                            cursor.setCharFormat(format)
                        cursor.insertText(text)
                else: # bytes
                    self.receiveArea.insertPlainText(data.decode(encoding=encoding, errors="ignore"))
            if curScrollValue < endScrollValue:
                self.receiveArea.verticalScrollBar().setValue(curScrollValue)
            else:
                self.receiveArea.moveCursor(QTextCursor.End)

    def sendHistoryFindDelete(self,str):
        self.sendHistory.removeItem(self.sendHistory.findText(str))

    def _getColorByfmt(self, fmt:bytes):
        colors = {
            b"0": None,
            b"30": "#000000",
            b"31": "#f44336",
            b"32": "#4caf50",
            b"33": "#ffa000",
            b"34": "#2196f3",
            b"35": "#e85aad",
            b"36": "#26c6da",
            b"37": "#a1887f",
        }
        bgs = {
            b"0": None,
            b"40": "#000000",
            b"41": "#f44336",
            b"42": "#4caf50",
            b"43": "#ffa000",
            b"44": "#2196f3",
            b"45": "#e85aad",
            b"46": "#26c6da",
            b"47": "#a1887f",
        }
        fmt = fmt[2:-1].split(b";")
        color = colors[b'0']
        bg = bgs[b'0']
        for cmd in fmt:
            if cmd in colors:
                color = colors[cmd]
            if cmd in bgs:
                bg = bgs[cmd]
        return color, bg

    def _texSplitByColor(self, text:bytes):
        remain = b''
        ignoreCodes = [rb'\x1b\[\?.*?h', rb'\x1b\[\?.*?l']
        text = text.replace(b"\x1b[K", b"")
        for code in ignoreCodes:
            colorFmt = re.findall(code, text)
            for fmt in colorFmt:
                text = text.replace(fmt, b"")
        colorFmt = re.findall(rb'\x1b\[.*?m', text)
        if text.endswith(b"\x1b"): # ***\x1b
            text = text[:-1]
            remain = b'\x1b'
        elif text.endswith(b"\x1b["): # ***\x1b[
            text = text[:-2]
            remain = b'\x1b['
        else: # ****\x1b[****, ****\x1b[****;****m
            idx = -2
            idx_remain = -1
            while 1:
                idx = text.find(b"\x1b[", len(text) - 10 + idx + 2) # \x1b[00;00m]
                if idx < 0:
                    break
                remain = text[idx:]
                idx_remain = idx
            if len(remain) > 0:
                match = re.findall(rb'\x1b\[.*?m', remain)  # ****\x1b[****;****m***
                if len(match) > 0: # have full color format
                    remain = b''
                else:
                    text = text[:idx_remain]
        plaintext = text
        for fmt in colorFmt:
            plaintext = plaintext.replace(fmt, b"")
        colorStrs = []
        if colorFmt:
            p = 0
            for fmt in colorFmt:
                idx = text[p:].index(fmt)
                if idx != 0:
                    colorStrs.append([self.lastColor, self.lastBg, text[p:p+idx]])
                    p += idx
                self.lastColor, self.lastBg = self._getColorByfmt(fmt)
                p += len(fmt)
            colorStrs.append([self.lastColor, self.lastBg, text[p:]])
        else:
            colorStrs = [[self.lastColor, self.lastBg, text]]
        return plaintext, colorStrs, remain

    def getColoredText(self, data_bytes, decoding=None):
        plainText, coloredText, remain = self._texSplitByColor(data_bytes)
        if decoding:
            plainText = plainText.decode(encoding=decoding, errors="ignore")
            decodedColoredText = []
            for color, bg, text in coloredText:
                decodedColoredText.append([color, bg, text.decode(encoding=decoding, errors="ignore")])
            coloredText = decodedColoredText
        return plainText, coloredText, remain

    def bytes2String(self, data : bytes, showAsHex : bool, encoding="utf-8"):
        isHexString = False
        dataColored = None
        if showAsHex:
            return True, utils.hexlify(data, ' ').decode(encoding=encoding), dataColored
        try:
            dataPlain, dataColore, remain = self.getColoredText(data, self.configGlobal["encoding"])
            if remain:
                dataPlain += remain.decode(encoding=self.configGlobal["encoding"], errors="ignore")
        except Exception:
            dataPlain = utils.hexlify(data, ' ').decode(encoding=encoding)
            isHexString = True
        return isHexString, dataPlain, dataColored

    def clearReceiveBuffer(self):
        self.receiveArea.clear()
        self.statusBar.clear()

    def onReceived(self, data : bytes):
        self.receivedData.append(data)
        self.statusBar.addRx(len(data))
        self.lock.release()

    def receiveDataProcess(self):
        self.receiveProgressStop = False
        timeLastReceive = 0
        new_line = True
        logData = None
        buffer = b''
        remain = b''
        while(not self.receiveProgressStop):
            logData = None
            head = ""
            self.lock.acquire()
            new = b"".join(self.receivedData)
            buffer += new
            self.receivedData = []
            # timeout, add new line
            if time.time() - timeLastReceive> self.config["receiveAutoLindefeedTime"]:
                if self.config["showTimestamp"] or self.config["receiveAutoLinefeed"]:
                    if self.config["useCRLF"]:
                        head += "\r\n"
                    else:
                        head += "\n"
                    new_line = True
            data = ""
            # have data in buffer
            if len(buffer) > 0:
                hexstr = False
                # show as hex, just show
                if not self.config["receiveAscii"]:
                    data = utils.bytes_to_hex_str(buffer)
                    colorData = data
                    buffer = b''
                    hexstr = True
                # show as string, and don't need to render color
                elif not self.config["color"]:
                    data = buffer.decode(encoding=self.configGlobal["encoding"], errors="ignore")
                    colorData = data
                    buffer = b''
                # show as string, and need to render color, wait for \n or until timeout to ensure color flag in buffer
                else:
                    if time.time() - timeLastReceive >  self.config["receiveAutoLindefeedTime"] or b'\n' in buffer:
                        data, colorData, remain = self.getColoredText(buffer, self.configGlobal["encoding"])
                        buffer = remain
                # add time receive head
                # get data from buffer, now render
                if data:
                    # add time header, head format(send receive '123' for example):
                    # '123'  '[2021-12-20 11:02:08.02.754]: 123' '=> 12' '<= 123'
                    # '=> [2021-12-20 11:02:34.02.291]: 123' '<= [2021-12-20 11:02:40.02.783]: 123'
                    # '<= [2021-12-20 11:03:25.03.320] [HEX]: 31 32 33 ' '=> [2021-12-20 11:03:27.03.319] [HEX]: 31 32 33'
                    if new_line:
                        timeNow = '[{}] '.format(utils.datetime_format_ms(datetime.now()))
                        if self.config["recordSend"]:
                            head += "<= "
                        if self.config["showTimestamp"]:
                            head += timeNow
                            head = '{} '.format(head.rstrip())
                        if hexstr:
                            head += "[HEX] "
                        if (self.config["recordSend"] or self.config["showTimestamp"]) and not head.endswith("<= "):
                            head = head[:-1] + ": "
                        new_line = False
                    self.receiveUpdateSignal.emit(head, [colorData], self.configGlobal["encoding"])
                    logData = head + data
            if len(new) > 0:
                timeLastReceive = time.time()

            while len(self.sendRecord) > 0:
                self.onLog(self.sendRecord.pop())
            if logData:
                self.onLog(logData)
Exemple #4
0
class MainWindow(QMainWindow):
    errorSignal = pyqtSignal(str, str)
    hintSignal = pyqtSignal(str, str)
    updateProgressSignal = pyqtSignal(str, int, int, str)
    updateProgressPrintSignal = pyqtSignal(str)
    showSerialComboboxSignal = pyqtSignal()
    downloadResultSignal = pyqtSignal(bool, str)
    DataPath = "./"
    app = None

    def __init__(self,app):
        super().__init__()
        self.app = app
        self.programStartGetSavedParameters()
        self.initVar()
        self.initWindow()
        self.initEvent()
        self.updateFrameParams()

    def __del__(self):
        pass

    def initVar(self):
        self.burning = False
        self.isDetectSerialPort = False
        self.DataPath = parameters.dataPath
        self.kflash = KFlash(print_callback=self.kflash_py_printCallback)
        self.saveKfpkDir = ""

    def setWindowSize(self, w=520, h=550):
        self.resize(w, h)

    def initWindow(self):
        QToolTip.setFont(QFont('SansSerif', 10))
        # main layout
        self.frameWidget = QWidget()
        mainWidget = QSplitter(Qt.Horizontal)
        self.frameLayout = QVBoxLayout()
        self.settingWidget = QWidget()
        settingLayout = QVBoxLayout()
        self.settingWidget.setProperty("class","settingWidget")
        mainLayout = QVBoxLayout()
        self.settingWidget.setLayout(settingLayout)
        mainLayout.addWidget(self.settingWidget)
        mainLayout.setStretch(0,2)
        menuLayout = QHBoxLayout()
        
        self.progressHint = QLabel()
        self.progressHint.hide()

        self.progressbarRootWidget = QWidget()
        progressbarLayout = QVBoxLayout()
        self.progressbarRootWidget.setProperty("class","progressbarWidget")
        self.progressbarRootWidget.setLayout(progressbarLayout)
        
        self.downloadWidget = QWidget()
        downloadLayout = QVBoxLayout()
        self.downloadWidget.setProperty("class","downloadWidget")
        self.downloadWidget.setLayout(downloadLayout)

        mainWidget.setLayout(mainLayout)
        # menu
        # -----
        # settings and others
        # -----
        # progress bar
        # -----
        # download button
        # -----
        # status bar
        self.frameLayout.addLayout(menuLayout)
        self.frameLayout.addWidget(mainWidget)
        self.frameLayout.addWidget(self.progressHint)
        self.frameLayout.addWidget(self.progressbarRootWidget)
        self.frameLayout.addWidget(self.downloadWidget)
        self.frameWidget.setLayout(self.frameLayout)
        self.setCentralWidget(self.frameWidget)
        self.setFrameStrentch(1)

        # option layout
        self.langButton = QPushButton()
        self.skinButton = QPushButton()
        self.aboutButton = QPushButton()
        self.langButton.setProperty("class", "menuItemLang")
        self.skinButton.setProperty("class", "menuItem2")
        self.aboutButton.setProperty("class", "menuItem3")
        self.langButton.setObjectName("menuItem")
        self.skinButton.setObjectName("menuItem")
        self.aboutButton.setObjectName("menuItem")
        menuLayout.addWidget(self.langButton)
        menuLayout.addWidget(self.skinButton)
        menuLayout.addWidget(self.aboutButton)
        menuLayout.addStretch(0)
        
        # widgets file select
        self.fileSelectGroupBox = QGroupBox(tr("SelectFile"))
        settingLayout.addWidget(self.fileSelectGroupBox)
        self.fileSelectLayout = QVBoxLayout()
        self.fileSelectGroupBox.setLayout(self.fileSelectLayout)
        oneFilePathWidget = QWidget()
        oneFilePathWidgetLayout = QHBoxLayout()
        oneFilePathWidget.setLayout(oneFilePathWidgetLayout)
        filePathWidget = QLineEdit()
        openFileButton = QPushButton(tr("OpenFile"))
        oneFilePathWidgetLayout.addWidget(filePathWidget)
        oneFilePathWidgetLayout.addWidget(openFileButton)
        oneFilePathWidgetLayout.setStretch(0, 3)
        oneFilePathWidgetLayout.setStretch(1, 1)
        self.fileSelectLayout.addWidget(oneFilePathWidget)
        self.fileSelectWidgets = [["kfpkg", oneFilePathWidget, oneFilePathWidgetLayout, filePathWidget, None, openFileButton]]
                  # for "button": ["button", addoneWidget, addoneWidgetLayout, addFileButton, packFileButton]
                  # for "bin":    ["bin", oneFilePathWidget, oneFilePathWidgetLayout, filePathWidget, fileBurnAddrWidget, openFileButton, fileBurnEncCheckbox]
        # widgets board select
        boardSettingsGroupBox = QGroupBox(tr("BoardSettings"))
        settingLayout.addWidget(boardSettingsGroupBox)
        boardSettingsLayout = QGridLayout()
        boardSettingsGroupBox.setLayout(boardSettingsLayout)
        self.boardLabel = QLabel(tr("Board"))
        self.boardCombobox = ComboBox()
        self.boardCombobox.addItem(parameters.SipeedMaixDock)
        self.boardCombobox.addItem(parameters.SipeedMaixBit)
        self.boardCombobox.addItem(parameters.SipeedMaixBitMic)
        self.boardCombobox.addItem(parameters.SipeedMaixduino)
        self.boardCombobox.addItem(parameters.SipeedMaixGo)
        self.boardCombobox.addItem(parameters.SipeedMaixGoD)
        self.boardCombobox.addItem(parameters.KendryteKd233)
        self.boardCombobox.addItem(parameters.kendryteTrainer)
        self.boardCombobox.addItem(parameters.Auto)
        self.burnPositionLabel = QLabel(tr("BurnTo"))
        self.burnPositionCombobox = ComboBox()
        self.burnPositionCombobox.addItem(tr("Flash"))
        self.burnPositionCombobox.addItem(tr("SRAM"))
        boardSettingsLayout.addWidget(self.boardLabel, 0, 0)
        boardSettingsLayout.addWidget(self.boardCombobox, 0, 1)
        boardSettingsLayout.addWidget(self.burnPositionLabel, 1, 0)
        boardSettingsLayout.addWidget(self.burnPositionCombobox, 1, 1)

        # widgets serial settings
        serialSettingsGroupBox = QGroupBox(tr("SerialSettings"))
        serialSettingsLayout = QGridLayout()
        serialPortLabek = QLabel(tr("SerialPort"))
        serailBaudrateLabel = QLabel(tr("SerialBaudrate"))
        slowModeLabel = QLabel(tr("Speed mode"))
        self.serialPortCombobox = ComboBox()
        self.serailBaudrateCombobox = ComboBox()
        self.serailBaudrateCombobox.addItem("115200")
        self.serailBaudrateCombobox.addItem("921600")
        self.serailBaudrateCombobox.addItem("1500000")
        self.serailBaudrateCombobox.addItem("2000000")
        self.serailBaudrateCombobox.addItem("3500000")
        self.serailBaudrateCombobox.addItem("4000000")
        self.serailBaudrateCombobox.addItem("4500000")
        self.serailBaudrateCombobox.setCurrentIndex(1)
        self.serailBaudrateCombobox.setEditable(True)
        self.slowModeCombobox = ComboBox()
        self.slowModeCombobox.addItem(tr("Slow mode"))
        self.slowModeCombobox.addItem(tr("Fast mode"))
        
        serialSettingsLayout.addWidget(serialPortLabek,0,0)
        serialSettingsLayout.addWidget(serailBaudrateLabel, 1, 0)
        serialSettingsLayout.addWidget(slowModeLabel, 2, 0)
        serialSettingsLayout.addWidget(self.serialPortCombobox, 0, 1)
        serialSettingsLayout.addWidget(self.serailBaudrateCombobox, 1, 1)
        serialSettingsLayout.addWidget(self.slowModeCombobox, 2, 1)
        serialSettingsGroupBox.setLayout(serialSettingsLayout)
        settingLayout.addWidget(serialSettingsGroupBox)

        # set stretch
        settingLayout.setStretch(0,1)
        settingLayout.setStretch(1,1)
        settingLayout.setStretch(2,2)

        # widgets progress bar
        
        self.progressbar = QProgressBar(self.progressbarRootWidget)
        self.progressbar.setValue(0)
        self.progressbarRootWidget.hide()

        # widgets download area
        self.downloadButton = QPushButton(tr("Download"))
        downloadLayout.addWidget(self.downloadButton)

        # main window
        self.statusBarStauts = QLabel()
        self.statusBarStauts.setMinimumWidth(80)
        self.statusBarStauts.setText("<font color=%s>%s</font>" %("#1aac2d", tr("DownloadHint")))
        self.statusBar().addWidget(self.statusBarStauts)

        self.setWindowSize()
        self.MoveToCenter()
        self.setWindowTitle(parameters.appName+" V"+str(helpAbout.versionMajor)+"."+str(helpAbout.versionMinor))
        icon = QIcon()
        print("icon path:"+self.DataPath+"/"+parameters.appIcon)
        icon.addPixmap(QPixmap(self.DataPath+"/"+parameters.appIcon), QIcon.Normal, QIcon.Off)
        self.setWindowIcon(icon)
        if sys.platform == "win32":
            ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(parameters.appName)
        
        self.show()
        self.progressbar.setGeometry(10, 0, self.downloadWidget.width()-25, 40)
        print("config file path:", parameters.configFilePath)

    def initEvent(self):
        self.serialPortCombobox.clicked.connect(self.portComboboxClicked)
        self.errorSignal.connect(self.errorHint)
        self.hintSignal.connect(self.hint)
        self.downloadResultSignal.connect(self.downloadResult)
        self.showSerialComboboxSignal.connect(self.showCombobox)
        self.updateProgressSignal.connect(self.updateProgress)
        self.updateProgressPrintSignal.connect(self.updateProgressPrint)
        self.langButton.clicked.connect(self.langChange)
        self.skinButton.clicked.connect(self.skinChange)
        self.aboutButton.clicked.connect(self.showAbout)
        self.downloadButton.clicked.connect(self.download)
        self.fileSelectWidget_Button(0).clicked.connect(lambda:self.selectFile(self.fileSelectWidget_Path(0)))

        self.myObject=MyClass(self)
        slotLambda = lambda: self.indexChanged_lambda(self.myObject)
        self.serialPortCombobox.currentIndexChanged.connect(slotLambda)

    def setFrameStrentch(self, mode):
        if mode == 0:
            self.frameLayout.setStretch(0,1)
            self.frameLayout.setStretch(1,3)
            self.frameLayout.setStretch(2,3)
            self.frameLayout.setStretch(3,1)
            self.frameLayout.setStretch(4,1)
            self.frameLayout.setStretch(5,1)
        else:
            self.frameLayout.setStretch(0,0)
            self.frameLayout.setStretch(1,0)
            self.frameLayout.setStretch(2,1)
            self.frameLayout.setStretch(3,1)
            self.frameLayout.setStretch(4,1)
            self.frameLayout.setStretch(5,1)
    
    def fileSelectWidget_Type(self, index):
        return self.fileSelectWidgets[index][0]

    def fileSelectWidget_Widget(self, index):
        return self.fileSelectWidgets[index][1]
    
    def fileSelectWidget_Layout(self, index):
        return self.fileSelectWidgets[index][2]

    def fileSelectWidget_Path(self, index):
        return self.fileSelectWidgets[index][3]

    def fileSelectWidget_Addr(self, index):
        return self.fileSelectWidgets[index][4]
    
    def fileSelectWidget_Button(self, index):
        return self.fileSelectWidgets[index][5]
    
    def fileSelectWidget_Prefix(self, index):
        return self.fileSelectWidgets[index][6]
    
    def fileSelectWidget_Close(self, index):
        return self.fileSelectWidgets[index][7]

    # @QtCore.pyqtSlot(str)
    def indexChanged_lambda(self, obj):
        mainObj = obj.arg
        self.serialPortCombobox.setToolTip(mainObj.serialPortCombobox.currentText())

    def portComboboxClicked(self):
        self.detectSerialPort()

    def MoveToCenter(self):
        qr = self.frameGeometry()
        cp = QDesktopWidget().availableGeometry().center()
        qr.moveCenter(cp)
        self.move(qr.topLeft())
    def removeFileSelection(self, button):
        index = -1
        for i in range(len(self.fileSelectWidgets)):
            if len(self.fileSelectWidgets[i]) >= 8:
                if self.fileSelectWidget_Close(i) == button:
                    index = i
        print(index)
        if index == -1:
            return
        if len(self.fileSelectWidgets) > 2:
            self.fileSelectWidget_Button(index).clicked.disconnect()
            self.fileSelectWidget_Close(index).clicked.disconnect()
            self.fileSelectWidget_Widget(index).setParent(None)
            self.fileSelectWidgets.remove(self.fileSelectWidgets[index])
        if len(self.fileSelectWidgets) == 2:
            self.fileSelectWidget_Close(0).clicked.disconnect()
            self.fileSelectWidget_Close(0).setParent(None)
            self.fileSelectWidgets[0].remove(self.fileSelectWidget_Close(0))
        self.downloadWidget.resize(self.downloadWidget.width(), 58)
        self.setWindowSize(self.width())

    def addAddFileWidget(self):
        if len(self.fileSelectWidgets) == 2:
            removeButton0 = QPushButton()
            removeButton0.setProperty("class", "remove_file_selection")
            self.fileSelectWidgets[0][2].addWidget(removeButton0)
            self.fileSelectWidgets[0].append(removeButton0)
            removeButton0.clicked.connect(lambda:self.removeFileSelection(removeButton0))
        oneFilePathWidget = QWidget()
        oneFilePathWidgetLayout = QHBoxLayout()
        oneFilePathWidget.setLayout(oneFilePathWidgetLayout)
        filePathWidget = QLineEdit()
        fileBurnAddrWidget = QLineEdit("0x00000")
        fileBurnEncCheckbox = QCheckBox(tr("Prefix"))
        openFileButton = QPushButton(tr("OpenFile"))
        removeButton = QPushButton()
        removeButton.setProperty("class", "remove_file_selection")
        oneFilePathWidgetLayout.addWidget(filePathWidget)
        oneFilePathWidgetLayout.addWidget(fileBurnAddrWidget)
        oneFilePathWidgetLayout.addWidget(fileBurnEncCheckbox)
        oneFilePathWidgetLayout.addWidget(openFileButton)
        oneFilePathWidgetLayout.addWidget(removeButton)
        oneFilePathWidgetLayout.setStretch(0, 4)
        oneFilePathWidgetLayout.setStretch(1, 2)
        oneFilePathWidgetLayout.setStretch(2, 1)
        oneFilePathWidgetLayout.setStretch(3, 2)
        # oneFilePathWidgetLayout.setStretch(4, 1)
        index = len(self.fileSelectWidgets)-1
        self.fileSelectWidgets.insert(index, ["bin", oneFilePathWidget, oneFilePathWidgetLayout, filePathWidget, fileBurnAddrWidget, openFileButton, fileBurnEncCheckbox, removeButton])
        self.fileSelectLayout.insertWidget(index, oneFilePathWidget)
        openFileButton.clicked.connect(lambda:self.selectFile(filePathWidget))
        removeButton.clicked.connect(lambda:self.removeFileSelection(removeButton))

    def fileSelectShowKfpkg(self, index, name):
        if index==0 and self.fileSelectWidget_Type(0) == "kfpkg": #only one kgpkg before
            self.fileSelectWidget_Path(index).setText(name)
        else:# have bin file before, remove all and add one for kfpkg
            for i in range(len(self.fileSelectWidgets)):
                if self.fileSelectWidget_Type(i)=="button":
                    self.fileSelectWidgets[i][3].clicked.disconnect()
                    self.fileSelectWidgets[i][4].clicked.disconnect()
                else:
                    self.fileSelectWidget_Button(i).clicked.disconnect()
                # self.fileSelectLayout.removeWidget(self.fileSelectWidget_Widget(i))
                self.fileSelectWidget_Widget(i).setParent(None)
            self.fileSelectWidgets.clear()
            oneFilePathWidget = QWidget()
            oneFilePathWidgetLayout = QHBoxLayout()
            oneFilePathWidget.setLayout(oneFilePathWidgetLayout)
            filePathWidget = QLineEdit()
            openFileButton = QPushButton(tr("OpenFile"))
            oneFilePathWidgetLayout.addWidget(filePathWidget)
            oneFilePathWidgetLayout.addWidget(openFileButton)
            oneFilePathWidgetLayout.setStretch(0, 3)
            oneFilePathWidgetLayout.setStretch(1, 1)
            self.fileSelectLayout.addWidget(oneFilePathWidget)
            self.fileSelectWidgets.append(["kfpkg", oneFilePathWidget, oneFilePathWidgetLayout, filePathWidget, None, openFileButton])
            openFileButton.clicked.connect(lambda:self.selectFile(filePathWidget))
            filePathWidget.setText(name)
            # TODO: resize window

    def fileSelectShowBin(self, index, name, addr=None, prefix=None, prefixAuto=False, closeButton=False ):
        if index==0 and self.fileSelectWidget_Type(0) == "kfpkg": #only one kgpkg before
            self.fileSelectWidget_Button(index).clicked.disconnect()
            # self.fileSelectLayout.removeWidget(self.fileSelectWidget_Widget(index))
            self.fileSelectWidget_Widget(index).setParent(None)
            self.fileSelectWidgets.clear()
            oneFilePathWidget = QWidget()
            oneFilePathWidgetLayout = QHBoxLayout()
            oneFilePathWidget.setLayout(oneFilePathWidgetLayout)
            filePathWidget = QLineEdit()
            fileBurnAddrWidget = QLineEdit("0x00000")
            fileBurnEncCheckbox = QCheckBox(tr("Prefix"))
            openFileButton = QPushButton(tr("OpenFile"))
            if closeButton:
                removeButton = QPushButton()
                removeButton.setProperty("class", "remove_file_selection")
            oneFilePathWidgetLayout.addWidget(filePathWidget)
            oneFilePathWidgetLayout.addWidget(fileBurnAddrWidget)
            oneFilePathWidgetLayout.addWidget(fileBurnEncCheckbox)
            oneFilePathWidgetLayout.addWidget(openFileButton)
            if closeButton:
                oneFilePathWidgetLayout.addWidget(removeButton)
            oneFilePathWidgetLayout.setStretch(0, 4)
            oneFilePathWidgetLayout.setStretch(1, 2)
            oneFilePathWidgetLayout.setStretch(2, 1)
            oneFilePathWidgetLayout.setStretch(3, 2)
            # oneFilePathWidgetLayout.setStretch(4, 1)
            self.fileSelectLayout.addWidget(oneFilePathWidget)
            openFileButton.clicked.connect(lambda:self.selectFile(filePathWidget))
            if closeButton:
                self.fileSelectWidgets.append(["bin", oneFilePathWidget, oneFilePathWidgetLayout, filePathWidget, fileBurnAddrWidget, openFileButton, fileBurnEncCheckbox, removeButton])
                removeButton.clicked.connect(lambda:self.removeFileSelection(removeButton))
                print(removeButton)
            else:
                self.fileSelectWidgets.append(["bin", oneFilePathWidget, oneFilePathWidgetLayout, filePathWidget, fileBurnAddrWidget, openFileButton, fileBurnEncCheckbox])
            # add ADD button
            addoneWidget = QWidget()
            addoneWidgetLayout = QHBoxLayout()
            addoneWidget.setLayout(addoneWidgetLayout)
            addFileButton = QPushButton(tr("Add File"))
            packFileButton = QPushButton(tr("Pack to kfpkg"))
            addoneWidgetLayout.addWidget(addFileButton)
            addoneWidgetLayout.addWidget(packFileButton)
            self.fileSelectLayout.addWidget(addoneWidget)
            self.fileSelectWidgets.append(["button", addoneWidget, addoneWidgetLayout, addFileButton, packFileButton])
            addFileButton.clicked.connect(self.addAddFileWidget)
            packFileButton.clicked.connect(self.packFile)

        self.fileSelectWidget_Path(index).setText(name)

        if prefixAuto:
            if name.endswith(".bin"):
                self.fileSelectWidget_Prefix(index).setChecked(True)
            else:
                self.fileSelectWidget_Prefix(index).setChecked(False)
        elif prefix:
            self.fileSelectWidget_Prefix(index).setChecked(True)
        if addr:
                self.fileSelectWidget_Addr(index).setText("0x%06x" %(addr))

    # return: ("kfpkg", [(file path, burn addr, add prefix),...])
    #      or ("bin", file path)
    #      or (None, None)
    def getBurnFilesInfo(self):
        files = []
        if self.fileSelectWidgets[0][0] == "kfpkg":
            path = self.fileSelectWidget_Path(0).text().strip()
            if path=="" or not os.path.exists(path):
                self.errorSignal.emit(tr("Error"), tr("Line {}: ").format(i+1)+tr("File path error")+":"+path)
                return (None, None)
            return ("kfpkg", path)
        for i in range(len(self.fileSelectWidgets)):
            if self.fileSelectWidgets[i][0] == "bin":
                path = self.fileSelectWidget_Path(i).text().strip()
                if path=="":
                    continue
                if not os.path.exists(path):
                    self.errorSignal.emit(tr("Error"), tr("Line {}: ").format(i+1)+tr("File path error")+":"+path)
                    return (None, None)
                try:
                    addr = int(self.fileSelectWidgets[i][4].text(), 16)
                except Exception:
                    self.errorSignal.emit(tr("Error"), tr("Line {}: ").format(i+1)+tr("Address error")+self.fileSelectWidgets[i][4].text())
                    return (None, None)
                files.append( (path, addr, self.fileSelectWidgets[i][6].isChecked()) )
        return ("bin", files)

    class KFPKG():
        def __init__(self):
            self.fileInfo = {"version": "0.1.0", "files": []}
            self.filePath = {}
            self.burnAddr = []
        
        def addFile(self, addr, path, prefix=False):
            if not os.path.exists(path):
                raise ValueError(tr("FilePathError"))
            if addr in self.burnAddr:
                raise ValueError(tr("Burn dddr duplicate")+":0x%06x" %(addr))
            f = {}
            f_name = os.path.split(path)[1]
            f["address"] = addr
            f["bin"] = f_name
            f["sha256Prefix"] = prefix
            self.fileInfo["files"].append(f)
            self.filePath[f_name] = path
            self.burnAddr.append(addr)

        def listDumps(self):
            kfpkg_json = json.dumps(self.fileInfo, indent=4)
            return kfpkg_json

        def listDump(self, path):
            with open(path, "w") as f:
                f.write(json.dumps(self.fileInfo, indent=4))

        def listLoads(self, kfpkgJson):
            self.fileInfo = json.loads(kfpkgJson)

        def listLload(self, path):
            with open(path) as f:
                self.fileInfo = json.load(f)

        def save(self, path):
            listName = os.path.join(tempfile.gettempdir(), "kflash_gui_tmp_list.json")
            self.listDump(listName)
            try:
                with zipfile.ZipFile(path, "w") as zip:
                    for name,path in self.filePath.items():
                        zip.write(path, arcname=name, compress_type=zipfile.ZIP_LZMA)
                    zip.write(listName, arcname="flash-list.json", compress_type=zipfile.ZIP_LZMA)
                    zip.close()
            except Exception as e:
                os.remove(listName)
                raise e
            os.remove(listName)

    def packFile(self):
        # generate flash-list.json
        fileType, files = self.getBurnFilesInfo()
        if not fileType or not files or fileType=="kfpkg":
            self.errorSignal.emit(tr("Error"), tr("File path error"))
            return
        kfpkg = self.KFPKG()
        try:
            for path, addr, prefix in files:
                kfpkg.addFile(addr, path, prefix)
        except Exception as e:
            self.errorSignal.emit(tr("Error"), tr("Pack kfpkg fail")+":"+str(e))
            return
        # select saving path
        if not os.path.exists(self.saveKfpkDir):
            self.saveKfpkDir = os.getcwd()
        fileName_choose, filetype = QFileDialog.getSaveFileName(self,  
                                    tr("Save File"),  
                                    self.saveKfpkDir,
                                    "k210 packages (*.kfpkg)")

        if fileName_choose == "":
            self.errorSignal.emit(tr("Error"), tr("File path error"))
            return
        if not fileName_choose.endswith(".kfpkg"):
            fileName_choose += ".kfpkg"
        self.saveKfpkDir = os.path.split(fileName_choose)[0]
        # print("save to ", fileName_choose)
        
        # write kfpkg file
        try:
            kfpkg.save(fileName_choose)
        except Exception as e:
            self.errorSignal.emit(tr("Error"), tr("Pack kfpkg fail")+":"+str(e))
            return
        self.hintSignal.emit(tr("Success"), tr("Save kfpkg success"))

    def selectFile(self, pathobj):
        index = -1
        for i in range(len(self.fileSelectWidgets)):
            if len(self.fileSelectWidgets[i]) >= 4:
                if pathobj == self.fileSelectWidget_Path(i):
                    index = i
        if index == -1:
            return
        tmp = index
        while tmp>=0:
            oldPath = self.fileSelectWidget_Path(tmp).text()
            if oldPath != "":
                break
            tmp -= 1
        if oldPath=="":
            oldPath = os.getcwd()
        fileName_choose, filetype = QFileDialog.getOpenFileName(self,  
                                    tr("SelectFile"),  
                                    oldPath,
                                    "All Files (*);;bin Files (*.bin);;k210 packages (*.kfpkg);;kmodel (*.kmodel);;encrypted kmodle(*.smodel)")   # 设置文件扩展名过滤,用双分号间隔

        if fileName_choose == "":
            return
        if not self.isFileValid(fileName_choose):
            self.errorSignal.emit(tr("Error"), tr("File path error"))
            return
        if self.isKfpkg(fileName_choose):
            self.fileSelectShowKfpkg(index, fileName_choose)
        else:
            self.fileSelectShowBin(index, fileName_choose, prefixAuto=True, closeButton=False)

    def errorHint(self, title, str):
        QMessageBox.critical(self, title, str)
    
    def hint(self, title, str):
        QMessageBox.information(self, title, str)

    def findSerialPort(self):
        self.port_list = list(serial.tools.list_ports.comports())
        return self.port_list

    def portChanged(self):
        self.serialPortCombobox.setCurrentIndex(0)
        self.serialPortCombobox.setToolTip(str(self.portList[0]))

    def detectSerialPort(self):
        if not self.isDetectSerialPort:
            self.isDetectSerialPort = True
            t = threading.Thread(target=self.detectSerialPortProcess)
            t.setDaemon(True)
            t.start()

    def showCombobox(self):
        self.serialPortCombobox.showPopup()

    def isKfpkg(self, name):
        if name.endswith(".kfpkg"):
            return True
        return False

    def isFileValid(self, name):
        if not os.path.exists(name):
            return False
        return True

    def detectSerialPortProcess(self):
        while(1):
            portList = self.findSerialPort()
            if len(portList)>0:
                currText = self.serialPortCombobox.currentText()
                self.serialPortCombobox.clear()
                for i in portList:
                    showStr = str(i[0])+" ("+str(i[1])+")"
                    self.serialPortCombobox.addItem(showStr)
                index = self.serialPortCombobox.findText(currText)
                if index>=0:
                    self.serialPortCombobox.setCurrentIndex(index)
                else:
                    self.serialPortCombobox.setCurrentIndex(0)
                break
            time.sleep(1)
        self.showSerialComboboxSignal.emit()
        self.isDetectSerialPort = False

    def programExitSaveParameters(self):
        paramObj = paremeters_save.ParametersToSave()
        paramObj.board    = self.boardCombobox.currentText()
        paramObj.burnPosition = self.burnPositionCombobox.currentText()
        paramObj.baudRate = self.serailBaudrateCombobox.currentIndex()
        paramObj.skin = self.param.skin
        paramObj.language = translation.current_lang
        path = self.fileSelectWidget_Path(0).text()
        if path.endswith(".kfpkg"):
            paramObj.files.append(path)
        else:
            for i in range(len(self.fileSelectWidgets)):
                try:
                    addr = int(self.fileSelectWidget_Addr(i).text(),16)
                except Exception:
                    continue
                paramObj.files.append( (self.fileSelectWidget_Path(i).text(), addr, self.fileSelectWidget_Prefix(i).isChecked()) )
        if self.slowModeCombobox.currentIndex()==0:
            paramObj.slowMode = True
        else:
            paramObj.slowMode = False
        paramObj.save(parameters.configFilePath)

    def programStartGetSavedParameters(self):
        paramObj = paremeters_save.ParametersToSave()
        paramObj.load(parameters.configFilePath)
        translation.setLanguage(paramObj.language)
        self.param = paramObj

    def updateFrameParams(self):
        pathLen = len(self.param.files)
        if pathLen == 1 and type(self.param.files[0])==str and self.param.files[0].endswith(".kfpkg"):
            self.fileSelectWidget_Path(0).setText(self.param.files[0])
        elif pathLen != 0:
            index = 0
            for path, addr, prefix  in self.param.files:
                prefix = None if (not prefix) else True
                if index!=0:
                    self.addAddFileWidget()
                if pathLen > 1 and index != 0:
                    closeButton = True
                else:
                    closeButton = False
                self.fileSelectShowBin(index, path, addr, prefix, closeButton=closeButton)
                index += 1
        self.boardCombobox.setCurrentText(self.param.board)
        self.burnPositionCombobox.setCurrentText(self.param.burnPosition)
        self.serailBaudrateCombobox.setCurrentIndex(self.param.baudRate)
        if self.param.slowMode:
            self.slowModeCombobox.setCurrentIndex(0)
        else:
            self.slowModeCombobox.setCurrentIndex(1)

    def closeEvent(self, event):
        try:
            self.programExitSaveParameters()
        finally:
            event.accept()

    def langChange(self):
        if self.param.language == translation.language_en:
            translation.setLanguage(translation.language_zh)
            lang = tr("Chinese language")
        else:
            translation.setLanguage(translation.language_en)
            lang = tr("English language")
        
        self.hint(tr("Hint"), tr("Language Changed to ") + lang + "\n"+ tr("Reboot to take effect"))
        self.frameWidget.style().unpolish(self.downloadButton)
        self.frameWidget.style().polish(self.downloadButton)
        self.frameWidget.update()

    def skinChange(self):
        if self.param.skin == 1: # light
            file = open(self.DataPath + '/assets/qss/style-dark.qss', "r")
            self.param.skin = 2
        else: # elif self.param.skin == 2: # dark
            file = open(self.DataPath + '/assets/qss/style.qss', "r")
            self.param.skin = 1
        self.app.setStyleSheet(file.read().replace("$DataPath", self.DataPath))

    def showAbout(self):
        QMessageBox.information(self, tr("About"),"<h1 style='color:#f75a5a';margin=10px;>"+parameters.appName+
                                '</h1><br><b style="color:#08c7a1;margin = 5px;">V'+str(helpAbout.versionMajor)+"."+
                                str(helpAbout.versionMinor)+"."+str(helpAbout.versionDev)+
                                "</b><br><br>"+helpAbout.date+"<br><br>"+helpAbout.strAbout())

    def autoUpdateDetect(self):
        auto = autoUpdate.AutoUpdate()
        if auto.detectNewVersion():
            auto.OpenBrowser()

    def openDevManagement(self):
        os.system('start devmgmt.msc')

    def updateProgress(self, fileTypeStr, current, total, speedStr):
        currBurnPos = self.burnPositionCombobox.currentText()
        if currBurnPos == tr("SRAM") or currBurnPos == tr_en("SRAM"):
            fileTypeStr = tr("ToSRAM")
        percent = current/float(total)*100
        hint = "<font color=%s>%s %s:</font>   <font color=%s> %.2f%%</font>   <font color=%s> %s</font>" %("#ff7575", tr("Downloading"), fileTypeStr, "#2985ff", percent, "#1aac2d", speedStr)
        self.progressHint.setText(hint)
        self.progressbar.setValue(percent)
    
    def updateProgressPrint(self, str):
        self.statusBarStauts.setText(str)

    def kflash_py_printCallback(self, *args, end = "\n"):
        msg = ""
        for i in args:
            msg += str(i)
        msg.replace("\n", " ")
        self.updateProgressPrintSignal.emit(msg)

    def progress(self, fileTypeStr, current, total, speedStr):
        self.updateProgressSignal.emit(fileTypeStr, current, total, speedStr)

    def download(self):
        if self.burning:
            self.terminateBurn()
            return
        tmpFile = ""
        fileType, filesInfo = self.getBurnFilesInfo()
        if not fileType or not filesInfo:
            self.errorSignal.emit(tr("Error"), tr("File path error"))
            return
        if fileType == "kfpkg":
            filename = filesInfo
        else:#generate kfpkg
            tmpFile = os.path.join(tempfile.gettempdir(), "kflash_gui_tmp.kfpkg")
            kfpkg = self.KFPKG()
            try:
                for path, addr, prefix in filesInfo:
                    kfpkg.addFile(addr, path, prefix)
                kfpkg.save(tmpFile)
            except Exception as e:
                os.remove(tmpFile)
                self.errorSignal.emit(tr("Error"), tr("Pack kfpkg fail")+":"+str(e))
                return
            filename = os.path.abspath(tmpFile)
        
        self.burning = True
        # if not self.checkFileName(filename):
        #     self.errorSignal.emit(tr("Error"), tr("FilePathError"))
        #     self.burning = False
        #     return
        color = False
        board = "dan"
        boardText = self.boardCombobox.currentText()
        if boardText == parameters.SipeedMaixGo:
            board = "goE"
        elif boardText == parameters.SipeedMaixGoD:
            board = "goD"
        elif boardText == parameters.SipeedMaixduino:
            board = "maixduino"
        elif boardText == parameters.SipeedMaixBit:
            board = "bit"
        elif boardText == parameters.SipeedMaixBitMic:
            board = "bit_mic"
        elif boardText == parameters.KendryteKd233:
            board = "kd233"
        elif boardText == parameters.kendryteTrainer:
            board = "trainer"
        elif boardText == parameters.Auto:
            board = None

        sram = False
        if self.burnPositionCombobox.currentText()==tr("SRAM") or \
            self.burnPositionCombobox.currentText()==tr_en("SRAM"):
            sram = True
        try:
            baud = int(self.serailBaudrateCombobox.currentText())
        except Exception:
            self.errorSignal.emit(tr("Error"), tr("BaudrateError"))
            self.burning = False
            return
        dev = ""
        try:
            dev  = self.serialPortCombobox.currentText().split()[0]
        except Exception:
            pass
        if dev=="":
            self.errorSignal.emit(tr("Error"), tr("PleaseSelectSerialPort"))
            self.burning = False
            return
        slow = self.slowModeCombobox.currentIndex()==0
        # hide setting widgets
        self.setFrameStrentch(1)
        self.settingWidget.hide()
        self.progressbar.setValue(0)
        self.progressbar.setGeometry(10, 0, self.downloadWidget.width()-25, 40)
        self.progressbarRootWidget.show()
        self.progressHint.show()
        self.downloadButton.setText(tr("Cancel"))
        self.downloadButton.setProperty("class", "redbutton")
        self.downloadButton.style().unpolish(self.downloadButton)
        self.downloadButton.style().polish(self.downloadButton)
        self.downloadButton.update()
        self.statusBarStauts.setText("<font color=%s>%s ...</font>" %("#1aac2d", tr("Downloading")))
        hint = "<font color=%s>%s</font>" %("#ff0d0d", tr("DownloadStart"))
        self.progressHint.setText(hint)
        # download
        self.burnThread = threading.Thread(target=self.flashBurnProcess, args=(dev, baud, board, sram, filename, self.progress, tmpFile!="", color, slow))
        self.burnThread.setDaemon(True)
        self.burnThread.start()

    def flashBurnProcess(self, dev, baud, board, sram, filename, callback, cleanFile, color, slow):
        success = True
        errMsg = ""
        try:
            if board:
                self.kflash.process(terminal=False, dev=dev, baudrate=baud, board=board, sram = sram, file=filename, callback=callback, noansi=not color, slow_mode=slow)
            else:
                self.kflash.process(terminal=False, dev=dev, baudrate=baud, sram = sram, file=filename, callback=callback, noansi=not color, slow_mode=slow)
        except Exception as e:
            errMsg = str(e)
            if str(e) != "Burn SRAM OK":
                success = False
        if cleanFile:
            os.remove(filename)
        if success:
            self.downloadResultSignal.emit(True, errMsg)
        else:
            self.downloadResultSignal.emit(False, errMsg)
            

    def downloadResult(self, success, msg):
        if success:
            self.hintSignal.emit(tr("Success"), tr("DownloadSuccess"))
            self.statusBarStauts.setText("<font color=%s>%s</font>" %("#1aac2d", tr("DownloadSuccess")))
        else:
            if msg == "Cancel":
                self.statusBarStauts.setText("<font color=%s>%s</font>" %("#ff1d1d", tr("DownloadCanceled")))
            else:
                msg = tr("ErrorSettingHint") + "\n\n"+msg
                self.errorSignal.emit(tr("Error"), msg)
                self.statusBarStauts.setText("<font color=%s>%s</font>" %("#ff1d1d", tr("DownloadFail")))
            self.progressHint.setText("")
        self.downloadButton.setText(tr("Download"))
        self.downloadButton.setProperty("class", "normalbutton")
        self.downloadButton.style().unpolish(self.downloadButton)
        self.downloadButton.style().polish(self.downloadButton)
        self.downloadButton.update()
        self.setFrameStrentch(0)
        self.progressbarRootWidget.hide()
        self.progressHint.hide()
        self.settingWidget.show()
        self.burning = False

    def terminateBurn(self):
        hint = "<font color=%s>%s</font>" %("#ff0d0d", tr("DownloadCanceling"))
        self.progressHint.setText(hint)
        self.kflash.kill()
Exemple #5
0
class Serial(COMM):
    '''
        call sequence:
            onInit
            onWidget
            onUiInitDone
                isConnected
                send
            getConfig
    '''
    id = "serial"
    name = _("Serial")
    showSerialComboboxSignal = pyqtSignal(list)
    showSwitchSignal = pyqtSignal(ConnectionStatus)

    def onInit(self, config):
        self.com = serial.Serial()
        self.config = config
        default = {
            "port": None,
            "baudrate": 115200,
            "bytesize": 8,
            "parity": "None",
            "stopbits": "1",
            "flowcontrol": "None",
            "rts": False,
            "dtr": False,
        }
        for k in default:
            if not k in self.config:
                self.config[k] = default[k]
        self.widgetConfMap = {
            "port": None,
            "baudrate": None,
            "bytesize": None,
            "parity": None,
            "stopbits": None,
            "flowcontrol": None,
            "rts": None,
            "dtr": None,
        }
        self.isOpened = False
        self.busy = False
        self.status = ConnectionStatus.CLOSED
        self.isDetectSerialPort = False
        self.widget = None
        self.baudrateCustomStr = _("Custom, input baudrate")

    def disconnect(self):
        if self.isConnected():
            self.openCloseSerial()

    def onDel(self):
        if self.isConnected():
            self.openCloseSerial()

    def __del__(self):
        try:
            self.com.close()
            self.status = ConnectionStatus.CLOSED
            time.sleep(
                0.05
            )  # wait for child threads, not wait also ok, cause child threads are daemon
        except Exception:
            pass

    def getConfig(self):
        '''
            get config, dict type
        '''
        return self.config

    def onUiInitDone(self):
        for key in self.config:
            self.setSerialConfig(key, self.widgetConfMap[key],
                                 self.config[key])
        self.detectSerialPort()

    def onWidget(self):
        self.widget = QWidget()
        serialSettingsLayout = QGridLayout()
        serialPortLabek = QLabel(_("Port"))
        serailBaudrateLabel = QLabel(_("Baudrate"))
        serailBytesLabel = QLabel(_("DataBytes"))
        serailParityLabel = QLabel(_("Parity"))
        serailStopbitsLabel = QLabel(_("Stopbits"))
        serialFlowControlLabel = QLabel(_("Flow control"))
        self.serialPortCombobox = ComboBox()
        self.serailBaudrateCombobox = ComboBox()
        for baud in parameters.defaultBaudrates:
            self.serailBaudrateCombobox.addItem(str(baud))
        self.serailBaudrateCombobox.addItem(self.baudrateCustomStr)
        self.serailBaudrateCombobox.setCurrentIndex(5)
        self.serailBaudrateCombobox.setEditable(True)
        self.serailBytesCombobox = ComboBox()
        self.serailBytesCombobox.addItem("5")
        self.serailBytesCombobox.addItem("6")
        self.serailBytesCombobox.addItem("7")
        self.serailBytesCombobox.addItem("8")
        self.serailBytesCombobox.setCurrentIndex(3)
        self.serailParityCombobox = ComboBox()
        self.serailParityCombobox.addItem("None")
        self.serailParityCombobox.addItem("Odd")
        self.serailParityCombobox.addItem("Even")
        self.serailParityCombobox.addItem("Mark")
        self.serailParityCombobox.addItem("Space")
        self.serailParityCombobox.setCurrentIndex(0)
        self.serailStopbitsCombobox = ComboBox()
        self.serailStopbitsCombobox.addItem("1")
        self.serailStopbitsCombobox.addItem("1.5")
        self.serailStopbitsCombobox.addItem("2")
        self.serailStopbitsCombobox.setCurrentIndex(0)
        self.serialFlowControlCombobox = ComboBox()
        self.serialFlowControlCombobox.addItem("None")
        self.serialFlowControlCombobox.addItem("XON/XOFF")
        self.serialFlowControlCombobox.addItem("RTS/CTS")
        self.serialFlowControlCombobox.addItem("DSR/DTR")
        self.serialFlowControlCombobox.setCurrentIndex(0)
        self.checkBoxRTS = QCheckBox("rts")
        self.checkBoxDTR = QCheckBox("dtr")
        self.checkBoxRTS.setToolTip(
            _("Check to enable(usually output low level)"))
        self.checkBoxDTR.setToolTip(
            _("Check to enable(usually output low level)"))
        self.serialOpenCloseButton = QPushButton(_("OPEN"))
        serialSettingsLayout.addWidget(serialPortLabek, 0, 0)
        serialSettingsLayout.addWidget(serailBaudrateLabel, 1, 0)
        serialSettingsLayout.addWidget(serailBytesLabel, 2, 0)
        serialSettingsLayout.addWidget(serailParityLabel, 3, 0)
        serialSettingsLayout.addWidget(serailStopbitsLabel, 4, 0)
        serialSettingsLayout.addWidget(serialFlowControlLabel, 5, 0)
        serialSettingsLayout.addWidget(self.serialPortCombobox, 0, 1)
        serialSettingsLayout.addWidget(self.serailBaudrateCombobox, 1, 1)
        serialSettingsLayout.addWidget(self.serailBytesCombobox, 2, 1)
        serialSettingsLayout.addWidget(self.serailParityCombobox, 3, 1)
        serialSettingsLayout.addWidget(self.serailStopbitsCombobox, 4, 1)
        serialSettingsLayout.addWidget(self.serialFlowControlCombobox, 5, 1)
        serialSettingsLayout.addWidget(self.checkBoxRTS, 6, 0, 1, 1)
        serialSettingsLayout.addWidget(self.checkBoxDTR, 6, 1, 1, 1)
        serialSettingsLayout.addWidget(self.serialOpenCloseButton, 7, 0, 1, 2)
        self.widget.setLayout(serialSettingsLayout)
        self.widgetConfMap["port"] = self.serialPortCombobox
        self.widgetConfMap["baudrate"] = self.serailBaudrateCombobox
        self.widgetConfMap["bytesize"] = self.serailBytesCombobox
        self.widgetConfMap["parity"] = self.serailParityCombobox
        self.widgetConfMap["stopbits"] = self.serailStopbitsCombobox
        self.widgetConfMap["flowcontrol"] = self.serialFlowControlCombobox
        self.widgetConfMap["rts"] = self.checkBoxRTS
        self.widgetConfMap["dtr"] = self.checkBoxDTR
        self.initEvet()
        return self.widget

    def initEvet(self):
        self.serialPortCombobox.clicked.connect(self.detectSerialPort)
        self.showSerialComboboxSignal.connect(self.showCombobox)
        self.serialPortCombobox.currentIndexChanged.connect(
            lambda: self.onSerialConfigChanged("port", self.serialPortCombobox,
                                               str))
        self.serailBaudrateCombobox.currentIndexChanged.connect(
            lambda: self.onSerialConfigChanged("baudrate",
                                               self.serailBaudrateCombobox,
                                               int,
                                               caller="index change"))
        self.serailBaudrateCombobox.editTextChanged.connect(
            lambda: self.onSerialConfigChanged("baudrate",
                                               self.serailBaudrateCombobox,
                                               int,
                                               caller="text change"))
        self.serailBytesCombobox.currentIndexChanged.connect(
            lambda: self.onSerialConfigChanged("bytesize", self.
                                               serailBytesCombobox, int))
        self.serailParityCombobox.currentIndexChanged.connect(
            lambda: self.onSerialConfigChanged("parity", self.
                                               serailParityCombobox, str))
        self.serailStopbitsCombobox.currentIndexChanged.connect(
            lambda: self.onSerialConfigChanged("stopbits", self.
                                               serailStopbitsCombobox, str))
        self.serialFlowControlCombobox.currentIndexChanged.connect(
            lambda: self.onSerialConfigChanged("flowcontrol", self.
                                               serialFlowControlCombobox, str))
        self.checkBoxRTS.clicked.connect(
            lambda: self.onSerialConfigChanged("rts", self.checkBoxRTS, bool))
        self.checkBoxDTR.clicked.connect(
            lambda: self.onSerialConfigChanged("dtr", self.checkBoxDTR, bool))
        self.serialOpenCloseButton.clicked.connect(self.openCloseSerial)
        self.showSwitchSignal.connect(self.showSwitch)

    def onSerialConfigChanged(self, conf_type, obj, value_type, caller=""):
        if conf_type == "port":
            obj.setToolTip(obj.currentText())
            newPort = obj.currentText().split(" ")[0]
            if newPort and not self.isDetectSerialPort and (
                    newPort != self.config["port"] or not self.com.port):
                self.config["port"] = newPort
                print("-- set to new port:", self.config["port"])
                try:
                    self.com.port = self.config["port"]
                except Exception as e:
                    msg = _("Open Failed") + "\n" + str(e)
                    self.status = ConnectionStatus.CLOSED
                    self.onConnectionStatus.emit(self.status, msg)
                    self.showSwitchSignal.emit(self.status)
        elif conf_type in ["baudrate", "bytesize", "parity", "stopbits"]:
            # custom baudrate input
            text = obj.currentText()
            if conf_type == "baudrate" and ((not text)
                                            or text == self.baudrateCustomStr):
                self.serailBaudrateCombobox.clearEditText()
                return
            self.config[conf_type] = value_type(text.split(" ")[0])
            print("-- set serial {} to {}".format(conf_type,
                                                  self.config[conf_type]))
            if conf_type == "parity":
                self.com.__setattr__(conf_type, self.config[conf_type][0])
            elif conf_type == "stopbits":
                self.com.__setattr__(conf_type, float(self.config[conf_type]))
            else:
                self.com.__setattr__(conf_type, self.config[conf_type])
        elif conf_type == "flowcontrol":
            self.config[conf_type] = value_type(
                obj.currentText().split(" ")[0])
            if self.config[conf_type] == "XON/XOFF":
                self.com.xonxoff = True
            else:
                self.com.xonxoff = False
            if self.config[conf_type] == "RTS/CTS":
                self.com.rtscts = True
            else:
                self.com.rtscts = False
            if self.config[conf_type] == "DSR/DTR":
                self.com.dsrdtr = True
            else:
                self.com.dsrdtr = False
        elif conf_type in ["rts", "dtr"]:
            self.config[conf_type] = obj.isChecked()
            self.com.__setattr__(conf_type, self.config[conf_type])

    def setSerialConfig(self, conf_type, obj, value):
        def getCommboboxItems(obj):
            values = []
            for i in range(len(obj)):
                values.append(obj.itemText(i))
            return values

        if conf_type == "port":
            values = getCommboboxItems(obj)
            idx = 0
            try:
                idx = values.index(str(value))
            except Exception:
                # print(f"-- set {obj} index {idx} error, value {value}, items {values}")
                pass
            obj.setCurrentIndex(idx)
        elif conf_type in ["baudrate", "bytesize", "parity", "stopbits"]:
            values = getCommboboxItems(obj)
            idx = 0
            try:
                idx = values.index(str(value))
            except Exception:
                print(
                    f"-- set {obj} index {idx} error, value {value}, items {values}"
                )
            obj.setCurrentIndex(idx)
            if conf_type == "parity":
                value = value[0]
            elif conf_type == "stopbits":
                value = float(value)
            self.com.__setattr__(conf_type, value)
            if conf_type == "baudrate":
                self.oneByteTime = 1 / (
                    self.com.baudrate /
                    (self.com.bytesize + 2 + self.com.stopbits)
                )  # 1 byte use time
        elif conf_type == "flowcontrol":
            values = getCommboboxItems(obj)
            idx = 0
            try:
                idx = values.index(str(value))
            except Exception:
                print(
                    f"-- set {obj} index {idx} error, value {value}, items {values}"
                )
            obj.setCurrentIndex(idx)
            if value == "XON/XOFF":
                self.com.xonxoff = True
            else:
                self.com.xonxoff = False
            if value == "RTS/CTS":
                self.com.rtscts = True
            else:
                self.com.rtscts = False
            if value == "DSR/DTR":
                self.com.dsrdtr = True
            else:
                self.com.dsrdtr = False
        elif conf_type in ["rts", "dtr"]:
            obj.setChecked(value)
            self.com.__setattr__(conf_type, value)

    def openCloseSerial(self):
        if self.busy:
            return
        self.busy = True
        if self.serialOpenCloseButton.text() == _("OPEN"):
            self.isOpened = False
        else:
            self.isOpened = True
        t = threading.Thread(target=self.openCloseSerialProcess)
        t.setDaemon(True)
        t.start()

    def openCloseSerialProcess(self):
        if self.isOpened:
            print("-- close serial")
            try:
                # set status first to prevent auto reconnect
                self.status = ConnectionStatus.CLOSED
                self.com.close()
            except Exception:
                pass
            self.onConnectionStatus.emit(self.status, "")
            self.showSwitchSignal.emit(self.status)
        else:
            try:
                print("-- open serial")
                self.onConnectionStatus.emit(ConnectionStatus.CONNECTING, "")
                self.com.open()
                self.status = ConnectionStatus.CONNECTED
                self.onConnectionStatus.emit(self.status, "")
                self.showSwitchSignal.emit(self.status)
                self.receiveProcess = threading.Thread(
                    target=self.receiveDataProcess)
                self.receiveProcess.setDaemon(True)
                self.receiveProcess.start()
            except Exception as e:
                try:
                    self.com.close()
                except Exception:
                    pass
                msg = _("Open Failed") + "\n" + str(e)
                self.hintSignal.emit("error", _("Error"), msg)
                self.status = ConnectionStatus.CLOSED
                self.onConnectionStatus.emit(self.status, msg)
                self.showSwitchSignal.emit(self.status)
        self.busy = False

    def detectSerialPort(self):
        if not self.isDetectSerialPort:
            self.isDetectSerialPort = True
            t = threading.Thread(target=self.detectSerialPortProcess)
            t.setDaemon(True)
            t.start()

    def detectSerialPortProcess(self):
        items = []
        while 1:
            portList = self.findSerialPort()
            if len(portList) > 0:
                for p in portList:
                    showStr = "{} {} - {}".format(p.device, p.name,
                                                  p.description)
                    if p.manufacturer:
                        showStr += ' - {}'.format(p.manufacturer)
                    if p.pid:
                        showStr += ' - pid(0x{:04X})'.format(p.pid)
                    if p.vid:
                        showStr += ' - vid(0x{:04X})'.format(p.vid)
                    if p.serial_number:
                        showStr += ' - v{}'.format(p.serial_number)
                    if p.device.startswith("/dev/cu.Bluetooth-Incoming-Port"):
                        continue
                    items.append(showStr)
                break
            time.sleep(0.5)
        self.showSerialComboboxSignal.emit(items)

    # @pyqtSlot(list)
    def showCombobox(self, items):
        set = -1
        self.serialPortCombobox.clear()
        for item in items:
            self.serialPortCombobox.addItem(item)
            if self.config["port"]:
                index = self.serialPortCombobox.findText(
                    self.config["port"], Qt.MatchContains)
                if index >= 0:
                    set = index
        self.serialPortCombobox.showPopup()
        self.isDetectSerialPort = False
        if set <= 0:
            # set to first port in list
            self.onSerialConfigChanged("port", self.serialPortCombobox, str)
        else:
            self.serialPortCombobox.setCurrentIndex(set)

    # @pyqtSlot(ConnectionStatus)
    def showSwitch(self, status):
        if status == ConnectionStatus.CLOSED:
            self.serialOpenCloseButton.setText(_("OPEN"))
            self.serialOpenCloseButton.setProperty("class", "")
        elif status == ConnectionStatus.CONNECTED:
            self.serialOpenCloseButton.setText(_("CLOSE"))
            self.serialOpenCloseButton.setProperty("class", "")
        else:
            self.serialOpenCloseButton.setText(_("CLOSE"))
            self.serialOpenCloseButton.setProperty("class", "warning")
        self.updateStyle(self.serialOpenCloseButton)

    def updateStyle(self, widget):
        self.widget.style().unpolish(widget)
        self.widget.style().polish(widget)
        self.widget.update()

    def findSerialPort(self):
        self.port_list = list(serial.tools.list_ports.comports())
        return self.port_list

    def portExits(self, port):
        ports = self.findSerialPort()
        devices = []
        for p in ports:
            devices.append(p.device)
        if port in devices:
            return True
        return False

    def receiveDataProcess(self):
        waitingReconnect = False
        self.com.timeout = 0.001
        buffer = b''
        t = 0
        while self.status != ConnectionStatus.CLOSED:
            if waitingReconnect:
                if self.portExits(self.com.port):
                    try:
                        self.onConnectionStatus.emit(
                            ConnectionStatus.CONNECTING, "")
                        self.com.open()
                        print("-- reopen serial")
                        waitingReconnect = False
                        self.onConnectionStatus.emit(
                            ConnectionStatus.CONNECTED, _("Reconnected"))
                        self.showSwitchSignal.emit(ConnectionStatus.CONNECTED)
                        continue
                    except Exception as e:
                        pass
                time.sleep(0.01)
                continue
            try:
                length = max(1, self.com.in_waiting)
                data = self.com.read(length)
                if data:
                    t = time.time()
                    if length == 1 and not buffer:  # just start receive
                        buffer += data
                        continue
                    buffer += data
                if buffer and (time.time() - t > self.oneByteTime *
                               2):  # no new data in next frame
                    try:
                        self.onReceived(buffer)
                    except Exception as e:
                        print("-- error in onReceived callback:", e)
                    buffer = b''
            except Exception as e:
                if (self.status != ConnectionStatus.CLOSED):
                    # close as fast as we can to release port
                    try:
                        self.com.close()
                    except Exception:
                        pass
                    waitingReconnect = True
                    self.onConnectionStatus.emit(ConnectionStatus.LOSE,
                                                 _("Connection lose!"))
                    self.showSwitchSignal.emit(ConnectionStatus.LOSE)

    def send(self, data: bytes):
        self.com.write(data)

    def isConnected(self):
        return self.status == ConnectionStatus.CONNECTED
class MainWindow(QMainWindow):
    errorSignal = pyqtSignal(str, str)
    hintSignal = pyqtSignal(str, str)
    updateProgressSignal = pyqtSignal(str, int, int, str)
    updateProgressPrintSignal = pyqtSignal(str)
    showSerialComboboxSignal = pyqtSignal()
    downloadResultSignal = pyqtSignal(bool, str)
    DataPath = "./"
    app = None

    def __init__(self, app):
        super().__init__()
        self.app = app
        self.programStartGetSavedParameters()
        self.initVar()
        self.initWindow()
        self.updateFrameParams()
        self.initEvent()

    def __del__(self):
        pass

    def initVar(self):
        self.burning = False
        self.isDetectSerialPort = False
        self.DataPath = parameters.dataPath
        self.kflash = KFlash(print_callback=self.kflash_py_printCallback)

    def initWindow(self):
        QToolTip.setFont(QFont('SansSerif', 10))
        # main layout
        self.frameWidget = QWidget()
        mainWidget = QSplitter(Qt.Horizontal)
        self.frameLayout = QVBoxLayout()
        self.settingWidget = QWidget()
        settingLayout = QVBoxLayout()
        self.settingWidget.setProperty("class", "settingWidget")
        mainLayout = QVBoxLayout()
        self.settingWidget.setLayout(settingLayout)
        mainLayout.addWidget(self.settingWidget)
        mainLayout.setStretch(0, 2)
        menuLayout = QHBoxLayout()

        self.progressHint = QLabel()
        self.progressHint.hide()

        self.progressbarRootWidget = QWidget()
        progressbarLayout = QVBoxLayout()
        self.progressbarRootWidget.setProperty("class", "progressbarWidget")
        self.progressbarRootWidget.setLayout(progressbarLayout)

        self.downloadWidget = QWidget()
        downloadLayout = QVBoxLayout()
        self.downloadWidget.setProperty("class", "downloadWidget")
        self.downloadWidget.setLayout(downloadLayout)

        mainWidget.setLayout(mainLayout)
        # menu
        # -----
        # settings and others
        # -----
        # progress bar
        # -----
        # download button
        # -----
        # status bar
        self.frameLayout.addLayout(menuLayout)
        self.frameLayout.addWidget(mainWidget)
        self.frameLayout.addWidget(self.progressHint)
        self.frameLayout.addWidget(self.progressbarRootWidget)
        self.frameLayout.addWidget(self.downloadWidget)
        self.frameWidget.setLayout(self.frameLayout)
        self.setCentralWidget(self.frameWidget)
        self.setFrameStrentch(0)

        # option layout
        self.langButton = QPushButton()
        self.skinButton = QPushButton()
        self.aboutButton = QPushButton()
        self.langButton.setProperty("class", "menuItemLang")
        self.skinButton.setProperty("class", "menuItem2")
        self.aboutButton.setProperty("class", "menuItem3")
        self.langButton.setObjectName("menuItem")
        self.skinButton.setObjectName("menuItem")
        self.aboutButton.setObjectName("menuItem")
        menuLayout.addWidget(self.langButton)
        menuLayout.addWidget(self.skinButton)
        menuLayout.addWidget(self.aboutButton)
        menuLayout.addStretch(0)

        # widgets file select
        fileSelectGroupBox = QGroupBox(tr("SelectFile"))
        settingLayout.addWidget(fileSelectGroupBox)
        fileSelectLayout = QHBoxLayout()
        fileSelectGroupBox.setLayout(fileSelectLayout)
        self.filePathWidget = QLineEdit()
        self.openFileButton = QPushButton(tr("OpenFile"))
        fileSelectLayout.addWidget(self.filePathWidget)
        fileSelectLayout.addWidget(self.openFileButton)

        # widgets board select
        boardSettingsGroupBox = QGroupBox(tr("BoardSettings"))
        settingLayout.addWidget(boardSettingsGroupBox)
        boardSettingsLayout = QGridLayout()
        boardSettingsGroupBox.setLayout(boardSettingsLayout)
        self.boardLabel = QLabel(tr("Board"))
        self.boardCombobox = ComboBox()
        self.boardCombobox.addItem(parameters.SipeedMaixDock)
        self.boardCombobox.addItem(parameters.SipeedMaixBit)
        self.boardCombobox.addItem(parameters.SipeedMaixBitMic)
        self.boardCombobox.addItem(parameters.SipeedMaixduino)
        self.boardCombobox.addItem(parameters.SipeedMaixGo)
        self.boardCombobox.addItem(parameters.SipeedMaixGoD)
        self.boardCombobox.addItem(parameters.KendryteKd233)
        self.boardCombobox.addItem(parameters.kendryteTrainer)
        self.boardCombobox.addItem(parameters.Auto)
        self.burnPositionLabel = QLabel(tr("BurnTo"))
        self.burnPositionCombobox = ComboBox()
        self.burnPositionCombobox.addItem(tr("Flash"))
        self.burnPositionCombobox.addItem(tr("SRAM"))
        boardSettingsLayout.addWidget(self.boardLabel, 0, 0)
        boardSettingsLayout.addWidget(self.boardCombobox, 0, 1)
        boardSettingsLayout.addWidget(self.burnPositionLabel, 1, 0)
        boardSettingsLayout.addWidget(self.burnPositionCombobox, 1, 1)

        # widgets serial settings
        serialSettingsGroupBox = QGroupBox(tr("SerialSettings"))
        serialSettingsLayout = QGridLayout()
        serialPortLabek = QLabel(tr("SerialPort"))
        serailBaudrateLabel = QLabel(tr("SerialBaudrate"))
        self.serialPortCombobox = ComboBox()
        self.serailBaudrateCombobox = ComboBox()
        self.serailBaudrateCombobox.addItem("115200")
        self.serailBaudrateCombobox.addItem("921600")
        self.serailBaudrateCombobox.addItem("1500000")
        self.serailBaudrateCombobox.addItem("2000000")
        self.serailBaudrateCombobox.addItem("3500000")
        self.serailBaudrateCombobox.addItem("4000000")
        self.serailBaudrateCombobox.addItem("4500000")
        self.serailBaudrateCombobox.setCurrentIndex(1)
        self.serailBaudrateCombobox.setEditable(True)

        serialSettingsLayout.addWidget(serialPortLabek, 0, 0)
        serialSettingsLayout.addWidget(serailBaudrateLabel, 1, 0)
        serialSettingsLayout.addWidget(self.serialPortCombobox, 0, 1)
        serialSettingsLayout.addWidget(self.serailBaudrateCombobox, 1, 1)
        serialSettingsGroupBox.setLayout(serialSettingsLayout)
        settingLayout.addWidget(serialSettingsGroupBox)

        # set stretch
        settingLayout.setStretch(0, 1)
        settingLayout.setStretch(1, 1)
        settingLayout.setStretch(2, 2)

        # widgets progress bar

        self.progressbar = QProgressBar(self.progressbarRootWidget)
        self.progressbar.setGeometry(10, 0, 400, 40)
        self.progressbar.setValue(0)
        self.progressbarRootWidget.hide()

        # widgets download area
        self.downloadButton = QPushButton(tr("Download"))
        downloadLayout.addWidget(self.downloadButton)

        # main window
        self.statusBarStauts = QLabel()
        self.statusBarStauts.setMinimumWidth(80)
        self.statusBarStauts.setText("<font color=%s>%s</font>" %
                                     ("#1aac2d", tr("DownloadHint")))
        self.statusBar().addWidget(self.statusBarStauts)

        self.resize(400, 550)
        self.MoveToCenter()
        self.setWindowTitle(parameters.appName + " V" +
                            str(helpAbout.versionMajor) + "." +
                            str(helpAbout.versionMinor))
        icon = QIcon()
        print("icon path:" + self.DataPath + "/" + parameters.appIcon)
        icon.addPixmap(QPixmap(self.DataPath + "/" + parameters.appIcon),
                       QIcon.Normal, QIcon.Off)
        self.setWindowIcon(icon)
        if sys.platform == "win32":
            ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(
                parameters.appName)
        self.show()
        print("config file path:",
              os.getcwd() + "/" + parameters.configFilePath)

    def initEvent(self):
        self.serialPortCombobox.clicked.connect(self.portComboboxClicked)
        self.errorSignal.connect(self.errorHint)
        self.hintSignal.connect(self.hint)
        self.downloadResultSignal.connect(self.downloadResult)
        self.showSerialComboboxSignal.connect(self.showCombobox)
        self.updateProgressSignal.connect(self.updateProgress)
        self.updateProgressPrintSignal.connect(self.updateProgressPrint)
        self.langButton.clicked.connect(self.langChange)
        self.skinButton.clicked.connect(self.skinChange)
        self.aboutButton.clicked.connect(self.showAbout)
        self.openFileButton.clicked.connect(self.selectFile)
        self.downloadButton.clicked.connect(self.download)

        self.myObject = MyClass(self)
        slotLambda = lambda: self.indexChanged_lambda(self.myObject)
        self.serialPortCombobox.currentIndexChanged.connect(slotLambda)

    def setFrameStrentch(self, mode):
        if mode == 0:
            self.frameLayout.setStretch(0, 1)
            self.frameLayout.setStretch(1, 3)
            self.frameLayout.setStretch(2, 3)
            self.frameLayout.setStretch(3, 1)
            self.frameLayout.setStretch(4, 1)
            self.frameLayout.setStretch(5, 1)
        else:
            self.frameLayout.setStretch(0, 0)
            self.frameLayout.setStretch(1, 0)
            self.frameLayout.setStretch(2, 1)
            self.frameLayout.setStretch(3, 1)
            self.frameLayout.setStretch(4, 1)
            self.frameLayout.setStretch(5, 1)

    # @QtCore.pyqtSlot(str)
    def indexChanged_lambda(self, obj):
        mainObj = obj.arg
        self.serialPortCombobox.setToolTip(
            mainObj.serialPortCombobox.currentText())

    def portComboboxClicked(self):
        self.detectSerialPort()

    def MoveToCenter(self):
        qr = self.frameGeometry()
        cp = QDesktopWidget().availableGeometry().center()
        qr.moveCenter(cp)
        self.move(qr.topLeft())

    def selectFile(self):
        oldPath = self.filePathWidget.text()
        if oldPath == "":
            oldPath = os.getcwd()
        fileName_choose, filetype = QFileDialog.getOpenFileName(
            self, tr("SelectFile"), oldPath,
            "All Files (*);;bin Files (*.bin);;k210 packages (*.kfpkg)"
        )  # 设置文件扩展名过滤,用双分号间隔

        if fileName_choose == "":
            return
        if not self.checkFileName(fileName_choose):
            self.errorSignal.emit(tr("Error"), tr("FileTypeError"))
            return
        self.filePathWidget.setText(fileName_choose)

    def errorHint(self, title, str):
        QMessageBox.critical(self, title, str)

    def hint(self, title, str):
        QMessageBox.information(self, title, str)

    def findSerialPort(self):
        self.port_list = list(serial.tools.list_ports.comports())
        return self.port_list

    def portChanged(self):
        self.serialPortCombobox.setCurrentIndex(0)
        self.serialPortCombobox.setToolTip(str(self.portList[0]))

    def detectSerialPort(self):
        if not self.isDetectSerialPort:
            self.isDetectSerialPort = True
            t = threading.Thread(target=self.detectSerialPortProcess)
            t.setDaemon(True)
            t.start()

    def showCombobox(self):
        self.serialPortCombobox.showPopup()

    def checkFileName(self, name):
        if not name.endswith(".bin") and not name.endswith(".kfpkg"):
            return False
        if not os.path.exists(name):
            return False
        return True

    def detectSerialPortProcess(self):
        while (1):
            portList = self.findSerialPort()
            if len(portList) > 0:
                currText = self.serialPortCombobox.currentText()
                self.serialPortCombobox.clear()
                for i in portList:
                    showStr = str(i[0]) + " (" + str(i[1]) + ")"
                    self.serialPortCombobox.addItem(showStr)
                index = self.serialPortCombobox.findText(currText)
                if index >= 0:
                    self.serialPortCombobox.setCurrentIndex(index)
                else:
                    self.serialPortCombobox.setCurrentIndex(0)
                break
            time.sleep(1)
        self.showSerialComboboxSignal.emit()
        self.isDetectSerialPort = False

    def programExitSaveParameters(self):
        paramObj = paremeters_save.ParametersToSave()
        paramObj.filePath = self.filePathWidget.text()
        paramObj.board = self.boardCombobox.currentText()
        paramObj.burnPosition = self.burnPositionCombobox.currentText()
        paramObj.baudRate = self.serailBaudrateCombobox.currentIndex()
        paramObj.skin = self.param.skin
        paramObj.language = translation.current_lang
        f = open(parameters.configFilePath, "wb")
        f.truncate()
        pickle.dump(paramObj, f)
        f.close()

    def programStartGetSavedParameters(self):
        paramObj = paremeters_save.ParametersToSave()
        try:
            f = open(parameters.configFilePath, "rb")
            paramObj = pickle.load(f)
            f.close()
        except Exception as e:
            f = open(parameters.configFilePath, "wb")
            f.close()
        translation.setLanguage(paramObj.language)
        self.param = paramObj

    def updateFrameParams(self):
        self.filePathWidget.setText(self.param.filePath)
        self.boardCombobox.setCurrentText(self.param.board)
        self.burnPositionCombobox.setCurrentText(self.param.burnPosition)
        self.serailBaudrateCombobox.setCurrentIndex(self.param.baudRate)

    def closeEvent(self, event):
        self.programExitSaveParameters()

    def langChange(self):
        if self.param.language == translation.language_en:
            translation.setLanguage(translation.language_zh)
            lang = tr("Chinese language")
        else:
            translation.setLanguage(translation.language_en)
            lang = tr("English language")

        self.hint(
            tr("Hint"),
            tr("Language Changed to ") + lang + "\n" +
            tr("Reboot to take effect"))
        self.frameWidget.style().unpolish(self.downloadButton)
        self.frameWidget.style().polish(self.downloadButton)
        self.frameWidget.update()

    def skinChange(self):
        if self.param.skin == 1:  # light
            file = open(self.DataPath + '/assets/qss/style-dark.qss', "r")
            self.param.skin = 2
        else:  # elif self.param.skin == 2: # dark
            file = open(self.DataPath + '/assets/qss/style.qss', "r")
            self.param.skin = 1
        self.app.setStyleSheet(file.read().replace("$DataPath", self.DataPath))

    def showAbout(self):
        QMessageBox.information(
            self, tr("About"),
            "<h1 style='color:#f75a5a';margin=10px;>" + parameters.appName +
            '</h1><br><b style="color:#08c7a1;margin = 5px;">V' +
            str(helpAbout.versionMajor) + "." + str(helpAbout.versionMinor) +
            "." + str(helpAbout.versionDev) + "</b><br><br>" + helpAbout.date +
            "<br><br>" + helpAbout.strAbout())

    def autoUpdateDetect(self):
        auto = autoUpdate.AutoUpdate()
        if auto.detectNewVersion():
            auto.OpenBrowser()

    def openDevManagement(self):
        os.system('start devmgmt.msc')

    def updateProgress(self, fileTypeStr, current, total, speedStr):
        currBurnPos = self.burnPositionCombobox.currentText()
        if currBurnPos == tr("SRAM") or currBurnPos == tr_en("SRAM"):
            fileTypeStr = tr("ToSRAM")
        percent = current / float(total) * 100
        hint = "<font color=%s>%s %s:</font>   <font color=%s> %.2f%%</font>   <font color=%s> %s</font>" % (
            "#ff7575", tr("Downloading"), fileTypeStr, "#2985ff", percent,
            "#1aac2d", speedStr)
        self.progressHint.setText(hint)
        self.progressbar.setValue(percent)

    def updateProgressPrint(self, str):
        self.statusBarStauts.setText(str)

    def kflash_py_printCallback(self, *args, end="\n"):
        msg = ""
        for i in args:
            msg += str(i)
        msg.replace("\n", " ")
        self.updateProgressPrintSignal.emit(msg)

    def progress(self, fileTypeStr, current, total, speedStr):
        self.updateProgressSignal.emit(fileTypeStr, current, total, speedStr)

    def download(self):
        if self.burning:
            self.terminateBurn()
            return

        self.burning = True
        filename = self.filePathWidget.text()
        if not self.checkFileName(filename):
            self.errorSignal.emit(tr("Error"), tr("FilePathError"))
            self.burning = False
            return
        color = False
        board = "dan"
        boardText = self.boardCombobox.currentText()
        if boardText == parameters.SipeedMaixGo:
            board = "goE"
        elif boardText == parameters.SipeedMaixGoD:
            board = "goD"
        elif boardText == parameters.SipeedMaixduino:
            board = "maixduino"
        elif boardText == parameters.SipeedMaixBit:
            board = "bit"
        elif boardText == parameters.SipeedMaixBitMic:
            board = "bit_mic"
        elif boardText == parameters.KendryteKd233:
            board = "kd233"
        elif boardText == parameters.kendryteTrainer:
            board = "trainer"
        elif boardText == parameters.Auto:
            board = None

        sram = False
        if self.burnPositionCombobox.currentText()==tr("SRAM") or \
            self.burnPositionCombobox.currentText()==tr_en("SRAM"):
            sram = True
        try:
            baud = int(self.serailBaudrateCombobox.currentText())
        except Exception:
            self.errorSignal.emit(tr("Error"), tr("BaudrateError"))
            self.burning = False
            return
        dev = self.serialPortCombobox.currentText().split()[0]
        if dev == "":
            self.errorSignal.emit(tr("Error"), tr("PleaseSelectSerialPort"))
            self.burning = False
            return
        # hide setting widgets
        self.setFrameStrentch(1)
        self.settingWidget.hide()
        self.progressbar.setValue(0)
        self.progressbarRootWidget.show()
        self.progressHint.show()
        self.downloadButton.setText(tr("Cancel"))
        self.downloadButton.setProperty("class", "redbutton")
        self.downloadButton.style().unpolish(self.downloadButton)
        self.downloadButton.style().polish(self.downloadButton)
        self.downloadButton.update()
        self.statusBarStauts.setText("<font color=%s>%s ...</font>" %
                                     ("#1aac2d", tr("Downloading")))
        hint = "<font color=%s>%s</font>" % ("#ff0d0d", tr("DownloadStart"))
        self.progressHint.setText(hint)
        # download
        self.burnThread = threading.Thread(target=self.flashBurnProcess,
                                           args=(
                                               dev,
                                               baud,
                                               board,
                                               sram,
                                               filename,
                                               self.progress,
                                               color,
                                           ))
        self.burnThread.setDaemon(True)
        self.burnThread.start()

    def flashBurnProcess(self, dev, baud, board, sram, filename, callback,
                         color):
        success = True
        errMsg = ""
        try:
            if board:
                self.kflash.process(terminal=False,
                                    dev=dev,
                                    baudrate=baud,
                                    board=board,
                                    sram=sram,
                                    file=filename,
                                    callback=callback,
                                    noansi=not color)
            else:
                self.kflash.process(terminal=False,
                                    dev=dev,
                                    baudrate=baud,
                                    sram=sram,
                                    file=filename,
                                    callback=callback,
                                    noansi=not color)
        except Exception as e:
            errMsg = str(e)
            if str(e) != "Burn SRAM OK":
                success = False
        if success:
            self.downloadResultSignal.emit(True, errMsg)
        else:
            self.downloadResultSignal.emit(False, errMsg)

    def downloadResult(self, success, msg):
        if success:
            self.hintSignal.emit(tr("Success"), tr("DownloadSuccess"))
            self.statusBarStauts.setText("<font color=%s>%s</font>" %
                                         ("#1aac2d", tr("DownloadSuccess")))
        else:
            if msg == "Cancel":
                self.statusBarStauts.setText(
                    "<font color=%s>%s</font>" %
                    ("#ff1d1d", tr("DownloadCanceled")))
            else:
                msg = tr("ErrorSettingHint") + "\n\n" + msg
                self.errorSignal.emit(tr("Error"), msg)
                self.statusBarStauts.setText("<font color=%s>%s</font>" %
                                             ("#ff1d1d", tr("DownloadFail")))
            self.progressHint.setText("")
        self.downloadButton.setText(tr("Download"))
        self.downloadButton.setProperty("class", "normalbutton")
        self.downloadButton.style().unpolish(self.downloadButton)
        self.downloadButton.style().polish(self.downloadButton)
        self.downloadButton.update()
        self.setFrameStrentch(0)
        self.progressbarRootWidget.hide()
        self.progressHint.hide()
        self.settingWidget.show()
        self.burning = False

    def terminateBurn(self):
        hint = "<font color=%s>%s</font>" % ("#ff0d0d",
                                             tr("DownloadCanceling"))
        self.progressHint.setText(hint)
        self.kflash.kill()