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)
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')
class Plugin(Plugin_Base): ''' call sequence: set vars like hintSignal, hintSignal onInit onWidget onUiInitDone send onReceived getConfig ''' # vars set by caller isConnected = lambda: False send = lambda x, y: None # send(data_bytes=None, file_path=None, callback=lambda ok,msg:None) hintSignal = None # hintSignal.emit(type(error, warning, info), title, msg) configGlobal = {} # other vars connParent = "dbg" # parent id connChilds = [] # children ids id = "protocol" name = _("protocol") enabled = False # user enabled this plugin active = False # using this plugin showReceiveDataSignal = pyqtSignal(str) def __init__(self): super().__init__() if not self.id: raise ValueError(f"var id of Plugin {self} should be set") def onInit(self, config): ''' init params, DO NOT take too long time in this func ''' default = { "version": 1, "sendAscii": True, "useCRLF": False, "sendEscape": True, "code": defaultProtocols.copy(), "currCode": "default", "customSendItems": [{ "text": "hello", "remark": "hello", "icon": "fa5.hand-paper" }, { "text": "\\x01\\x03\\x03\\x03\\x03\\x01", "remark": "pre", "icon": "ei.arrow-left", "shortcut": [[16777234, "Left"]] }, { "text": "\\x01\\x04\\x04\\x04\\x04\\x01", "remark": "next", "icon": "ei.arrow-right", "shortcut": [[16777236, "Right"]] }, { "text": "\\x01\\x01\\x01\\x01\\x01\\x01", "remark": "ok", "icon": "fa.circle-o", "shortcut": [[16777220, "Return"]] }, { "text": "\\x01\\x02\\x02\\x02\\x02\\x01", "remark": "ret", "icon": "ei.return-key", "shortcut": [[16777216, "Esc"]] }] } self.config = config for k in default: if not k in self.config: self.config[k] = default[k] self.editingDefaults = False self.codeGlobals = { "unpack": unpack, "pack": pack, "crc": crc, "encoding": self.configGlobal["encoding"], "print": self.print } self.encodeMethod = lambda x: x self.decodeMethod = lambda x: x self.pressedKeys = [] self.keyModeClickTime = 0 def print(self, *args, **kw_args): end = "\n" start = "[MSG]: " if "end" in kw_args: end = kw_args["end"] if "start" in kw_args: start = kw_args["start"] string = start + " ".join(map(str, args)) + end self.showReceiveDataSignal.emit(string) class ModeButton(QPushButton): onFocusIn = pyqtSignal(QFocusEvent) onFocusOut = pyqtSignal(QFocusEvent) def __init__(self, text, eventFilter, parent=None) -> None: super().__init__(text, parent) self.installEventFilter(eventFilter) def focusInEvent(self, event): self.onFocusIn.emit(event) def focusOutEvent(self, event): self.onFocusOut.emit(event) def onWidgetMain(self, parent): self.mainWidget = QSplitter(Qt.Vertical) self.receiveWidget = TextEdit() font = QFont( 'Menlo,Consolas,Bitstream Vera Sans Mono,Courier New,monospace, Microsoft YaHei', 10) self.receiveWidget.setFont(font) self.receiveWidget.setLineWrapMode(TextEdit.NoWrap) self.clearBtn = QPushButton("") self.keyBtneventFilter = self.ModeButtonEventFilter( self.onModeBtnKeyPressEvent, self.onModeBtnKeyReleaseEvent) self.keyModeBtn = self.ModeButton(_("Key mode"), self.keyBtneventFilter) layoutClearMode = QHBoxLayout() layoutClearMode.addWidget(self.clearBtn) layoutClearMode.addWidget(self.keyModeBtn) clearModeWidget = QWidget() clearModeWidget.setLayout(layoutClearMode) utils_ui.setButtonIcon(self.clearBtn, "mdi6.broom") self.addButton = QPushButton("") utils_ui.setButtonIcon(self.addButton, "fa.plus") self.customSendScroll = QScrollArea() self.customSendScroll.setMinimumHeight( parameters.customSendItemHeight + 20) self.customSendScroll.setWidgetResizable(True) self.customSendScroll.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.customSendScroll.setHorizontalScrollBarPolicy( Qt.ScrollBarAlwaysOff) cutomSendItemsWraper = QWidget() self.customSendScroll.setWidget(cutomSendItemsWraper) # wrapper widget cutomSendItemsWraper0 = QWidget() cutomSendItemsWraper0.setProperty("class", "scrollbar2") layout0 = QVBoxLayout() layout0.setContentsMargins(0, 8, 0, 0) cutomSendItemsWraper0.setLayout(layout0) layout0.addWidget(self.customSendScroll) customSendItemsLayoutWrapper = QVBoxLayout() customSendItemsLayoutWrapper.setContentsMargins(0, 0, 0, 0) cutomSendItemsWraper.setLayout(customSendItemsLayoutWrapper) # items container self.customItems = QWidget() self.customSendItemsLayout = QVBoxLayout() self.customSendItemsLayout.setContentsMargins(0, 0, 0, 0) self.customItems.setLayout(self.customSendItemsLayout) customSendItemsLayoutWrapper.addWidget(self.customItems) customSendItemsLayoutWrapper.addWidget(self.addButton) customSendItemsLayoutWrapper.addStretch(0) self.mainWidget.addWidget(self.receiveWidget) self.mainWidget.addWidget(clearModeWidget) self.mainWidget.addWidget(cutomSendItemsWraper0) self.mainWidget.setStretchFactor(0, 2) self.mainWidget.setStretchFactor(1, 1) self.mainWidget.setStretchFactor(2, 11) # event self.addButton.clicked.connect(lambda: self.insertSendItem()) def clearReceived(): self.receiveWidget.clear() self.statusBar.clear() self.clearBtn.clicked.connect(clearReceived) def keyModeOn(event): self.keyModeBtn.setProperty("class", "deleteBtn") utils_ui.updateStyle(self.mainWidget, self.keyModeBtn) self.keyModeClickTime = time.time() # show all shortcut widgets = self.customItems.findChildren(QPushButton, "editRemark") for i, w in enumerate(widgets): shortcut = "+".join( (name for v, name in self.config["customSendItems"][i] ["shortcut"])) w.setText(shortcut) utils_ui.updateStyle(self.mainWidget, w) def keyModeOff(event): self.keyModeBtn.setProperty("class", "") utils_ui.updateStyle(self.mainWidget, self.keyModeBtn) self.keyModeClickTime = 0 # remove all preesed keys even them not release actually, to avoid window swith by ALT+TAB bug self.pressedKeys = [] # hide all shortcut widgets = self.customItems.findChildren(QPushButton, "editRemark") for w in widgets: w.setText("") def keyModeTuggle(): if self.keyModeBtn.property("class") == "deleteBtn": if time.time() - self.keyModeClickTime < 0.2: return else: self.keyModeBtn.clearFocus() self.keyModeBtn.onFocusIn.connect(keyModeOn) self.keyModeBtn.onFocusOut.connect(keyModeOff) self.keyModeBtn.clicked.connect(keyModeTuggle) return self.mainWidget def onWidgetSettings(self, parent): root = QWidget() rootLayout = QVBoxLayout() rootLayout.setContentsMargins(0, 0, 0, 0) root.setLayout(rootLayout) setingGroup = QGroupBox(_("En-decoding settings")) layout = QGridLayout() setingGroup.setLayout(layout) self.codeItems = ComboBox() self.codeItemCustomStr = _("Custom, input name") self.codeItemLoadDefaultsStr = _("Load defaults") self.codeItems.setEditable(True) self.codeWidget = PlainTextEdit() self.saveCodeBtn = QPushButton(_("Save")) self.saveCodeBtn.setEnabled(False) self.deleteCodeBtn = QPushButton(_("Delete")) btnLayout = QHBoxLayout() btnLayout.addWidget(self.saveCodeBtn) btnLayout.addWidget(self.deleteCodeBtn) layout.addWidget(QLabel(_("Defaults")), 0, 0, 1, 1) layout.addWidget(self.codeItems, 0, 1, 1, 1) layout.addWidget(QLabel(_("Code")), 1, 0, 1, 1) layout.addWidget(self.codeWidget, 1, 1, 1, 1) layout.addLayout(btnLayout, 2, 1, 1, 1) serialSendSettingsLayout = QGridLayout() sendGroup = QGroupBox(_("Send settings")) sendGroup.setLayout(serialSendSettingsLayout) 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.sendSettingsCRLF = QCheckBox(_("<CRLF>")) self.sendSettingsCRLF.setToolTip( _("Select to send \\r\\n instead of \\n")) self.sendSettingsCRLF.setChecked(False) self.sendSettingsEscape = QCheckBox(_("Escape")) self.sendSettingsEscape.setToolTip( _("Enable escape characters support like \\t \\r \\n \\x01 \\001")) serialSendSettingsLayout.addWidget(self.sendSettingsAscii, 0, 0, 1, 1) serialSendSettingsLayout.addWidget(self.sendSettingsHex, 0, 1, 1, 1) serialSendSettingsLayout.addWidget(self.sendSettingsCRLF, 1, 0, 1, 1) serialSendSettingsLayout.addWidget(self.sendSettingsEscape, 1, 1, 1, 1) rootLayout.addWidget(sendGroup) rootLayout.addWidget(setingGroup) # event self.sendSettingsAscii.clicked.connect(lambda: self.bindVar( self.sendSettingsAscii, self.config, "sendAscii", bool)) self.sendSettingsHex.clicked.connect(lambda: self.bindVar( self.sendSettingsHex, self.config, "sendAscii", bool, invert=True)) self.sendSettingsCRLF.clicked.connect(lambda: self.bindVar( self.sendSettingsCRLF, self.config, "useCRLF", bool)) self.sendSettingsEscape.clicked.connect(lambda: self.bindVar( self.sendSettingsEscape, self.config, "sendEscape", bool)) self.saveCodeBtn.clicked.connect(self.saveCode) self.deleteCodeBtn.clicked.connect(self.deleteCode) self.codeWidget.onSave = self.saveCode return root def onWidgetStatusBar(self, parent): self.statusBar = statusBar(rxTxCount=True) return self.statusBar def onUiInitDone(self): ''' UI init done, you can update your widget here this method runs in UI thread, do not block too long ''' newItems = [] for item in self.config["customSendItems"]: item = self.insertSendItem(item, load=True) newItems.append(item) self.config["customSendItems"] = newItems self.sendSettingsAscii.setChecked(self.config["sendAscii"]) self.sendSettingsHex.setChecked(not self.config["sendAscii"]) self.sendSettingsCRLF.setChecked(self.config["useCRLF"]) self.sendSettingsEscape.setChecked(self.config["sendEscape"]) self.showReceiveDataSignal.connect(self.showReceivedData) # init decoder and encoder for k in self.config["code"]: self.codeItems.addItem(k) self.codeItems.addItem(self.codeItemCustomStr) self.codeItems.addItem(self.codeItemLoadDefaultsStr) name = self.config["currCode"] idx = self.codeItems.findText(self.config["currCode"]) if idx < 0: idx = 0 name = "default" self.codeItems.setCurrentIndex(idx) self.selectCode(name) self.codeItems.currentIndexChanged.connect( self.onCodeItemChanged ) # add here to avoid self.selectCode trigger self.codeWidget.textChanged.connect(self.onCodeChanged) class ModeButtonEventFilter(QObject): def __init__(self, keyPressCb, keyReleaseCb) -> None: super().__init__() self.keyPressCb = keyPressCb self.keyReleaseCb = keyReleaseCb def eventFilter(self, obj, evt): if evt.type() == QEvent.KeyPress: # prevent default key events self.keyPressCb(evt) return True elif evt.type() == QEvent.KeyRelease: self.keyReleaseCb(evt) return True return False def onModeBtnKeyPressEvent(self, event): # send by shortcut key = event.key() self.pressedKeys.append(key) for item in self.config["customSendItems"]: if not "shortcut" in item: continue shortcut = item["shortcut"] if len(shortcut) == len(self.pressedKeys): same = True for i in range(len(shortcut)): if shortcut[i][0] != self.pressedKeys[i]: same = False break if same: self.sendCustomItem(item) def onModeBtnKeyReleaseEvent(self, event): key = event.key() if key in self.pressedKeys: self.pressedKeys.remove(key) def onKeyPressEvent(self, event): pass def onKeyReleaseEvent(self, event): pass def insertSendItem(self, item=None, load=False): # itemsNum = self.customSendItemsLayout.count() + 1 # height = parameters.customSendItemHeight * (itemsNum + 1) + 20 # topHeight = self.receiveWidget.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) if not item: item = {"text": "", "remark": None, "icon": None} if type(item) == str: item = {"text": item, "remark": None} text = item["text"] remark = item["remark"] itemWidget = QWidget() layout = QHBoxLayout() layout.setContentsMargins(0, 0, 0, 0) itemWidget.setLayout(layout) cmd = QLineEdit(text) if remark: send = QPushButton(remark) else: send = QPushButton("") if (not "icon" in item) or not item["icon"]: item["icon"] = "fa.send" if not "shortcut" in item: item["shortcut"] = [] utils_ui.setButtonIcon(send, item["icon"]) editRemark = QPushButton("") editRemark.setObjectName("editRemark") utils_ui.setButtonIcon(editRemark, "ei.pencil") editRemark.setProperty("class", "remark") cmd.setToolTip(text) send.setToolTip(text) cmd.textChanged.connect(lambda: self.onCustomItemChange( self.customSendItemsLayout.indexOf(itemWidget), cmd, send)) send.setProperty("class", "smallBtn") def sendCustomData(idx): self.sendCustomItem(self.config["customSendItems"][idx]) send.clicked.connect(lambda: sendCustomData( self.customSendItemsLayout.indexOf(itemWidget))) delete = QPushButton("") utils_ui.setButtonIcon(delete, "fa.close") delete.setProperty("class", "deleteBtn") layout.addWidget(cmd) layout.addWidget(send) layout.addWidget(editRemark) layout.addWidget(delete) delete.clicked.connect(lambda: self.deleteSendItem( self.customSendItemsLayout.indexOf(itemWidget), itemWidget, [send, editRemark, delete])) def changeRemark(idx, obj): if not "icon" in self.config["customSendItems"][idx]: self.config["customSendItems"][idx]["icon"] = None shortcut = [] if "shortcut" in self.config["customSendItems"][idx]: shortcut = self.config["customSendItems"][idx]["shortcut"] ok, remark, icon, shortcut = EditRemarDialog( obj.text(), self.config["customSendItems"][idx]["icon"], shortcut).exec() if ok: obj.setText(remark) if icon: utils_ui.setButtonIcon(obj, icon) else: obj.setIcon(QIcon()) self.config["customSendItems"][idx]["remark"] = remark self.config["customSendItems"][idx]["icon"] = icon self.config["customSendItems"][idx]["shortcut"] = shortcut editRemark.clicked.connect(lambda: changeRemark( self.customSendItemsLayout.indexOf(itemWidget), send)) self.customSendItemsLayout.addWidget(itemWidget) if not load: self.config["customSendItems"].append(item) return item def deleteSendItem(self, idx, item, iconItems=[]): for obj in iconItems: utils_ui.clearButtonIcon(obj) item.setParent(None) self.config["customSendItems"].pop(idx) # itemsNum = self.customSendItemsLayout.count() # height = parameters.customSendItemHeight * (itemsNum + 1) + 20 # topHeight = self.receiveWidget.height() + 100 # if height + topHeight > self.funcParent.height(): # height = self.funcParent.height() - topHeight # self.customSendScroll.setMinimumHeight(height) def showReceivedData(self, text: str): curScrollValue = self.receiveWidget.verticalScrollBar().value() self.receiveWidget.moveCursor(QTextCursor.End) endScrollValue = self.receiveWidget.verticalScrollBar().value() self.receiveWidget.insertPlainText(text) if curScrollValue < endScrollValue: self.receiveWidget.verticalScrollBar().setValue(curScrollValue) else: self.receiveWidget.moveCursor(QTextCursor.End) def onReceived(self, data: bytes): self.statusBar.addRx(len(data)) try: data = self.decodeMethod(data) except Exception as e: self.hintSignal.emit("error", _("Error"), _("Run decode error") + " " + str(e)) return if not data: return for plugin in self.connChilds: plugin.onReceived(data) if type(data) != str: data = self.decodeReceivedData(data, self.configGlobal["encoding"], not self.config["sendAscii"], self.config["sendEscape"]) self.showReceiveDataSignal.emit(data + "\n") def sendData(self, data_bytes=None): try: data_bytes = self.encodeMethod(data_bytes) except Exception as e: self.hintSignal.emit("error", _("Error"), _("Run encode error") + " " + str(e)) return if data_bytes: self.send(data_bytes, callback=self.onSent) def onSent(self, ok, msg, length, path): if ok: self.statusBar.addTx(length) else: self.hintSignal.emit("error", _("Error"), _("Send data failed!") + " " + msg) def sendCustomItem(self, item): text = item["text"] dateBytes = self.parseSendData(text, self.configGlobal["encoding"], self.config["useCRLF"], not self.config["sendAscii"], self.config["sendEscape"]) if dateBytes: self.sendData(data_bytes=dateBytes) def onCustomItemChange(self, idx, edit, send): text = edit.text() edit.setToolTip(text) send.setToolTip(text) self.config["customSendItems"][idx].update({ "text": text, "remark": send.text() }) def onCodeItemChanged(self): if self.editingDefaults: return self.editingDefaults = True if self.codeItems.currentText() == self.codeItemCustomStr: self.codeItems.clearEditText() self.editingDefaults = False return if self.codeItems.currentText() == self.codeItemLoadDefaultsStr: for name in defaultProtocols: idx = self.codeItems.findText(name) if idx >= 0: self.codeItems.removeItem(idx) self.codeItems.insertItem(self.codeItems.count() - 2, name) self.config["code"][name] = defaultProtocols[name] self.codeItems.setCurrentIndex(0) self.selectCode(self.codeItems.currentText()) self.editingDefaults = False return # update code from defaults self.selectCode(self.codeItems.currentText()) self.editingDefaults = False def selectCode(self, name): if name in [self.codeItemCustomStr, self.codeItemLoadDefaultsStr ] or not name or not name in self.config["code"]: print(f"name {name} invalid") return self.config["currCode"] = name self.codeWidget.clear() self.codeWidget.insertPlainText(self.config["code"][name]) ok, e, d = self.getEnDecodeMethod(self.codeWidget.toPlainText()) if ok: self.encodeMethod = e self.decodeMethod = d self.saveCodeBtn.setText(_("Save")) self.saveCodeBtn.setEnabled(False) def getEnDecodeMethod(self, code): func = lambda x: x try: exec(code, self.codeGlobals) if (not "decode" in self.codeGlobals) or not "encode" in self.codeGlobals: raise ValueError( _("decode and encode method should be in code")) return True, self.codeGlobals["encode"], self.codeGlobals["decode"] except Exception as e: msg = _("Method error") + "\n" + str(e) self.hintSignal.emit("error", _("Error"), msg) return False, func, func def onCodeChanged(self): changed = True name = self.codeItems.currentText() if name in self.config["code"]: codeSaved = self.config["code"][name] code = self.codeWidget.toPlainText() if code == codeSaved: changed = False if changed: self.saveCodeBtn.setText(_("Save") + " *") self.saveCodeBtn.setEnabled(True) else: self.saveCodeBtn.setText(_("Save")) self.saveCodeBtn.setEnabled(False) def saveCode(self): self.editingDefaults = True name = self.codeItems.currentText() if name in [self.codeItemCustomStr, self.codeItemLoadDefaultsStr ] or not name: self.hintSignal.emit("warning", _("Warning"), _("Please input code profile name first")) self.editingDefaults = False return idx = self.codeItems.findText(name) if idx < 0: self.codeItems.insertItem(self.codeItems.count() - 2, name) self.editingDefaults = False code = self.codeWidget.toPlainText() ok, e, d = self.getEnDecodeMethod(code) if ok: self.encodeMethod = e self.decodeMethod = d self.config["code"][name] = code self.saveCodeBtn.setText(_("Save")) self.saveCodeBtn.setEnabled(False) def deleteCode(self): self.editingDefaults = True name = self.codeItems.currentText() itemsConfig = [self.codeItemCustomStr, self.codeItemLoadDefaultsStr] # QMessageBox.infomation() if name in itemsConfig or not name: self.hintSignal.emit( "warning", _("Warning"), _("Please select a code profile name first to delete")) self.editingDefaults = False return idx = self.codeItems.findText(name) if idx < 0: self.editingDefaults = False return self.codeItems.removeItem(idx) self.config["code"].pop(name) name = list(self.config["code"].keys()) if len(name) > 0: name = name[0] self.codeItems.setCurrentText(name) self.selectCode(name) self.editingDefaults = False