class Traveller: def __init__(self, id, speed): self.speed = float(speed) self.id = int(id) # Časovač opuštění oblasti self.timer = QTimer() self.timer.timeout.connect(self.done) # Cestovatel je aktivní self.active = True # Cestovatel vstupuje do sledovaného úseku def start(self, crossing): self.crossing = crossing # Časovač je v milisekundách self.timer.start(1000 * self.roadLength / self.speed) # Obsluha časovače: cestovatel opouští úsek def done(self): # Pokud jsme se mezitím stihli odpojit a připojit, neposílej nic if self.active: self.crossing.sendBack(self) # Převod argumentů zpět na řetězec def strArgs(self): return ("id=" + str(self.id) + " speed=" + str(self.speed)) def position(self): return self.roadLength - self.timer.remainingTime() * self.speed / 1000
class Executer: def __init__(self, serialObj, loggerObj): self.serialPort = serial.Serial() self.serialPort = serialObj self.logger = loggerObj self.log = self.logger.log self.execState = ExecState.NotConnected self.serialTimeoutTimer = QTimer() self.serialTimeoutTimer.setSingleShot(True) self.serialTimeoutTimer.setInterval(SERIAL_COMMAND_TIMEOUT) # self.checkStopRequestTimer = QTimer() # self.checkStopRequestTimer.setInterval(500) # self.checkStopRequestTimer.setSingleShot(False) # self.checkStopRequestTimer.timeout.connect(self.processCheckStopRequest) # self.checkStopRequestTimer.start() self._stopRequested = False def execute(self, labCode, inputDataFrame, outputFolder, inputFields=None, outputField=None, progressBar=None, model=None): # self.logger.disableLogging() self.serialPort.flushInput() self.serialPort.flushOutput() startTime = time.time() # progressBar = None if progressBar is not None: progressBar.setValue(0) try: if self.execState == ExecState.NotConnected: if self.serialPort.isOpen(): self.execState = ExecState.Connected else: #This should never happen because this function is called after serial is connected self.log("Execution failed because serial port is not open, something is wrong", type="ERROR") return ExecutionResult.FAILED if self.execState == ExecState.Connected: if self._sendCommand("SELECT_LAB", labCode, max_retry=SERIAL_COMMAND_MAX_TRIALS*4) == FAILURE_CODE: self.log("Error occured with lab selection", type="ERROR") return ExecutionResult.FAILED else: self.execState = ExecState.LabSelected if self.execState == ExecState.LabSelected: if model is not None and not model.startswith("RPI:"): self.log("Started sending the model, this could take a while, please wait", type="INFO") if self._sendSaveModelCommand(model) == FAILURE_CODE: self.log("Failed to send the selected model", type="ERROR") return ExecutionResult.FAILED else: if not model: modelName = lab_default_models[labCode] elif model.startswith("RPI:"): modelName = model[4:] if self._sendCommand("LOAD_MODEL", modelName, timeout=SERIAL_COMMAND_TIMEOUT*3) == FAILURE_CODE: self.log("Failed to load the required model", type="ERROR") return ExecutionResult.FAILED self.execState = ExecState.ModelLoaded if self.execState == ExecState.ModelLoaded: #load the inputs if inputFields is not None: inputs = inputDataFrame[inputFields] else: inputs = inputDataFrame if outputField: trueOutput = inputDataFrame[outputField] else: trueOutput = None self.execState = ExecState.Processing if self.execState == ExecState.Processing: if labCode == "LabTest": executionResult = self._executeLab(inputs, outputFolder, progressBar=progressBar, plotter=None) elif labCode == "Lab1": executionResult = self._executeLab(inputs, outputFolder, outputHeader = "Prediction", progressBar=progressBar, plotter=None, trueOutput=trueOutput, labCode=labCode) elif labCode == "Lab2": executionResult = self._executeLab(inputs, outputFolder, outputHeader = "Prediction", progressBar=progressBar, plotter=None, trueOutput=trueOutput, labCode=labCode) else: raise ValueError("Lab Code should be one of the implemented lab codes for processing to work") return ExecutionResult.FAILED if executionResult == FAILURE_CODE: return ExecutionResult.FAILED else: self.execState = ExecState.Done if self.execState == ExecState.Done: if (self._sendCommand("PROCESSING_DONE", "None") != FAILURE_CODE): if progressBar is not None: progressBar.setValue(100) # self.logger.enableLogging() self.log("Processing completed in {} ms".format((time.time()-startTime)*1000)) return ExecutionResult.COMPLETED else: self.log("Failed to let RPi know that processing is done", "ERROR") return ExecutionResult.FAILED except StopProcessingRequested: if progressBar is not None: progressBar.setValue(0) return ExecutionResult.INTERRUPTED except Exception as e: self.logger.enableLogging() self.log("Caught exception: {}".format(e), type="ERROR") self.log(traceback.format_exc()) print(traceback.format_stack()) return ExecutionResult.FAILED def executeOther(self, function, payload = "None"): if function not in SERIAL_COMMANDS: return ExecutionResult.FAILED result = self._sendCommand(function, payload) if result == FAILURE_CODE: return ExecutionResult.FAILED else: return result def reset(self): try: startBytes = bytes([STARTING_BYTE]*50) self.serialPort.write(startBytes) result = self._sendCommand("RESET", "None") if result is FAILURE_CODE: return ExecutionResult.FAILED else: return ExecutionResult.COMPLETED except SerialTimeoutException as e: self.logger.enableLogging() self.log("Please try again or reboot the RPi if the problem persists, Caught exception: {}".format(e), type="ERROR") return ExecutionResult.FAILED except Exception as e: self.logger.enableLogging() self.log("Caught exception: {}".format(e), type="ERROR") self.log(traceback.format_exc()) print(traceback.format_stack()) return ExecutionResult.FAILED def _executeLab(self, inputs, outputFolder, trueOutput= None, labCode= None, outputHeader= None, progressBar= None, plotter= None): if progressBar is not None: progressBarIncrements = 100/len(inputs.index) currentProgressBarValue = progressBar.value() outputFilePath = os.path.join(outputFolder, datetime.now().strftime("%d-%m_%H-%M-%S")) if trueOutput is None: outputDataFrame = copy.deepcopy(inputs) else: outputDataFrame = copy.deepcopy(pd.concat([inputs, trueOutput], axis=1)) with open(outputFilePath+"_OutputsOnly.csv", 'a') as outFile: headers = [] if outputHeader is not None: outFile.write(outputHeader+"\n") headers = outputHeader.split(",") for i in range(len(inputs.index)): inputStringParts = [str(n) for n in inputs.iloc[i].values.tolist()] inputString = ", ".join(inputStringParts) self.log("Now processing: {}".format(inputString), type="INFO") result = self._sendCommand("PROCESS", inputString) if result is FAILURE_CODE: self.log("Error processing line number {}, possible serial communication issues".format(i+1), type="ERROR") return FAILURE_CODE else: self.log("Output is: {}".format(result), type="SUCCESS") outFile.write(result+"\n") if plotter is not None: plotter.addNewData(inputs.iloc[i, 0], float(result.rstrip(' \t\r\n\0').split(',')[0])) if progressBar is not None: currentProgressBarValue += progressBarIncrements progressBar.setValue(currentProgressBarValue) # print(result) outputs = [float(i) for i in result.rstrip(' \t\r\n\0').split(',')] for index, output in enumerate(outputs): if index < len(headers): header = headers[index] else: header = f"Unknown_{index+1}" outputDataFrame.loc[i, header] = output outputDataFrame.to_csv(outputFilePath+"_Full.csv", index=False) self.log(f"Outputs Saved in {outputFilePath+'_OutputsOnly.csv'}\nComplete data saved in {outputFilePath+'_Full.csv'}") # calculate accuracy if trueOutput is not None and labCode: try: if labCode == "Lab1": from sklearn.metrics import r2_score, mean_squared_error r2Score = r2_score(outputDataFrame.iloc[:, -2], outputDataFrame.iloc[:, -1]) RMSE = mean_squared_error(outputDataFrame.iloc[:, -2], outputDataFrame.iloc[:, -1], squared=False) self.log(f"Regression R2 Score Calculated is {r2Score :.3f} and RMSE is {RMSE :.3f}") elif labCode == "Lab2": from sklearn.metrics import accuracy_score, recall_score, f1_score accuracyScore = accuracy_score(outputDataFrame.iloc[:, -2], outputDataFrame.iloc[:, -1]) recallScore = recall_score(outputDataFrame.iloc[:, -2], outputDataFrame.iloc[:, -1]) f1Score = f1_score(outputDataFrame.iloc[:, -2], outputDataFrame.iloc[:, -1]) self.log(f"Classification Metrics\n Accuracy: {accuracyScore*100 :.2f}%, Recall: {recallScore:.2f}, F1-Score: {f1Score:.2f}") except Exception as e: self.log(f"Failed to calculate accuracy metrics because of exception: {e}", type="ERROR") return SUCCESS_CODE def _sendCommand(self, command, payload, timeout=SERIAL_COMMAND_TIMEOUT, max_retry=SERIAL_COMMAND_MAX_TRIALS): if not command in SERIAL_COMMANDS: print("The command provided {} is not a valid serial command".format(command)) return FAILURE_CODE sendBuffer = bytearray() sendBuffer.append(STARTING_BYTE) sendString = command + ":" + payload sendBuffer.extend(sendString.encode("utf-8")) sendBuffer.append(0x00) newChecksum = Crc16() # print("Checksum Calc based on {}".format(sendBuffer[1:])) newChecksum.process(sendBuffer[1:]) checksumBytes = newChecksum.finalbytes() sendBuffer.extend(checksumBytes) # print(len(sendBuffer)) for attempt in range(max_retry): if attempt != 0: self.log(f"Attempt #{attempt+1} to send the command {command} with payload {payload}", type="DEBUG") QCoreApplication.processEvents() # t = time.time() try: self.serialPort.flushInput() self.serialPort.write(sendBuffer) except SerialTimeoutException: self.serialPort.flushOutput() continue self.serialTimeoutTimer.setInterval(timeout) self.serialTimeoutTimer.start() succeeded, string = self.getSerialAck() # print("The time spent from sending a command to receiving a reply (or timeouting) is ",time.time()-t) if succeeded: return string elif not succeeded and "EXCEPTION" in string: break return FAILURE_CODE def _sendSaveModelCommand(self, model): with open(model, 'rb') as modelFile: fileToBeSent = modelFile.read() fileToBeSent = zlib.compress(fileToBeSent, level=9) fileToBeSentStr = " ".join(map(str,fileToBeSent)) self.log(f"Estimated time for model to be sent is {int(len(fileToBeSentStr)/2000)} seconds", type="INFO") return self._sendCommand("SAVE_MODEL", fileToBeSentStr, timeout=SAVE_MODEL_COMMAND_TIMEOUT) def getSerialAck(self): string = "" succeeded = False self.serialState = SerialState.WaitingToStart currentSerialString = "" currentCheckSum = bytearray(2) while(self.serialTimeoutTimer.remainingTime()>0): QCoreApplication.processEvents() self.processCheckStopRequest() if self.serialState == SerialState.WaitingToStart: newByte = self.serialPort.read() if len(newByte) == 1: if newByte[0] == STARTING_BYTE: self.serialState = SerialState.WaitingForString if self.serialState == SerialState.WaitingForString: newBytes = self.serialPort.read_until(b'\0') if len(newBytes) >= 1: for i in range (len(newBytes)): if newBytes[i] == STARTING_BYTE: pass else: currentSerialString = currentSerialString + newBytes[i:].decode("utf-8") if newBytes[-1] == 0x00: self.serialState = SerialState.WaitingForChecksum1 break if self.serialState == SerialState.WaitingForChecksum1: newByte = self.serialPort.read() if len(newByte) == 1: currentCheckSum[0] = newByte[0] self.serialState = SerialState.WaitingForChecksum2 if self.serialState == SerialState.WaitingForChecksum2: newByte = self.serialPort.read() if len(newByte) == 1: currentCheckSum[1] = newByte[0] self.serialState = SerialState.CommandDone if self.serialState == SerialState.CommandDone: # check the message integrity receivedCommandCrc = Crc16() receivedCommandCrc.process(currentSerialString.encode('utf-8')) receivedCommandCrcBytes = receivedCommandCrc.finalbytes() # print("Checksum Calc based on {}".format(currentSerialString.encode('utf-8'))) # print("Checksum Received: {}, Calculated: {}".format(currentCheckSum, receivedCommandCrcBytes)) if receivedCommandCrcBytes == currentCheckSum: succeeded = True string = currentSerialString.split(":")[1].rstrip(' \t\r\n\0') if string == "None": string = "" else: self.log("Acknowledgment Failed, received: {}".format(currentSerialString.rstrip("\t\r\n\0")), type="ERROR") string = currentSerialString break return succeeded, string def processCheckStopRequest(self): if not self._stopRequested: return else: self._stopRequested = False raise StopProcessingRequested def requestStop(self): self._stopRequested = True @property def execState(self): return self._execState @execState.setter def execState(self, newVal): # print("Switched to Exec State: {}".format(newVal)) self._execState = newVal @property def serialState(self): return self._serialState @serialState.setter def serialState(self, newVal): # print("Switched to Serial State: {}".format(newVal)) self._serialState = newVal
class Notification(QWidget): """Custom pop-up notification widget with fade-in and fade-out effect.""" def __init__(self, parent, txt, anim_duration=500, life_span=2000): """ Args: parent (QWidget): Parent widget txt (str): Text to display in notification anim_duration (int): Duration of the animation in msecs life_span (int): How long does the notification stays in place in msecs """ super().__init__() self.setWindowFlags(Qt.Popup) self.setParent(parent) self._parent = parent self.label = QLabel(txt) self.label.setAlignment(Qt.AlignCenter) self.label.setWordWrap(True) self.label.setMargin(8) font = QFont() font.setBold(True) self.label.setFont(font) layout = QHBoxLayout() layout.addWidget(self.label) layout.setSizeConstraint(QLayout.SetFixedSize) layout.setContentsMargins(1, 0, 1, 0) self.setLayout(layout) self.adjustSize() # Move to the top right corner of the parent x = self._parent.size().width() - self.width() - 2 self.move(x, 0) self.setAttribute(Qt.WA_DeleteOnClose) self.setAttribute(Qt.WA_TranslucentBackground) ss = ("QWidget{background-color: rgba(255, 194, 179, 0.8);" "border-width: 2px;" "border-color: #ffebe6;" "border-style: groove; border-radius: 8px;}") self.setStyleSheet(ss) self.effect = QGraphicsOpacityEffect() self.setGraphicsEffect(self.effect) self.effect.setOpacity(0.0) self._opacity = 0.0 self.timer = QTimer(self) self.timer.setInterval(life_span) self.timer.timeout.connect(self.start_self_destruction) # Fade in animation self.fade_in_anim = QPropertyAnimation(self, b"opacity") self.fade_in_anim.setDuration(anim_duration) self.fade_in_anim.setStartValue(0.0) self.fade_in_anim.setEndValue(1.0) self.fade_in_anim.valueChanged.connect(self.update_opacity) self.fade_in_anim.finished.connect(self.timer.start) # Fade out animation self.fade_out_anim = QPropertyAnimation(self, b"opacity") self.fade_out_anim.setDuration(anim_duration) self.fade_out_anim.setStartValue(1.0) self.fade_out_anim.setEndValue(0) self.fade_out_anim.valueChanged.connect(self.update_opacity) self.fade_out_anim.finished.connect(self.close) # Start fade in animation self.fade_in_anim.start(QPropertyAnimation.DeleteWhenStopped) def get_opacity(self): """opacity getter.""" return self._opacity def set_opacity(self, op): """opacity setter.""" self._opacity = op @Slot(float) def update_opacity(self, value): """Updates graphics effect opacity.""" self.effect.setOpacity(value) def start_self_destruction(self): """Starts fade-out animation and closing of the notification.""" self.fade_out_anim.start(QPropertyAnimation.DeleteWhenStopped) def enterEvent(self, e): super().enterEvent(e) self.start_self_destruction() self.setAttribute(Qt.WA_TransparentForMouseEvents) def remaining_time(self): if self.timer.isActive(): return self.timer.remainingTime() if self.fade_out_anim.state() == QPropertyAnimation.Running: return 0 return self.timer.interval() opacity = Property(float, get_opacity, set_opacity)
class MainGame(QMainWindow): def init_global_fields(self): self.user_name = '' self.user_settings = dict() self.game_time = 0 self.game_step = 0 self.solved = False self.actions_queue = queue.Queue() self.current_combination = list() self.action_name = '' self.stat_session = None def __init__(self, ui_file, parent=None): self.init_global_fields() super(MainGame, self).__init__(parent) ui_file = QFile(ui_file) ui_file.open(QFile.ReadOnly) loader = QUiLoader() self.window = loader.load(ui_file) ui_file.close() self.btn_clear = self.window.findChild(QPushButton, "btn_clear") self.btn_skip = self.window.findChild(QPushButton, "btn_skip") self.btn_undo = self.window.findChild(QPushButton, "btn_undo") self.label_timer = self.window.findChild(QLabel, "label_timer") self.log: QPlainTextEdit = self.window.findChild(QPlainTextEdit, "log") self.log.clear() self.log.setReadOnly(True) self.picture_holder: QLabel = self.window.findChild( QLabel, "picture_holder") self.picture_holder.setPixmap( QtGui.QPixmap("actions/pictures/default.png")) self.global_timer = QTimer(self) self.second_timer = QTimer(self) self.second_timer.timeout.connect(self.redraw_label_timer) self.global_timer.timeout.connect(self.stop_game) self.log.textChanged.connect(self.scroll_log_to_max) self.btn_undo.clicked.connect(self.undo_action) self.btn_skip.clicked.connect(self.skip) self.btn_clear.clicked.connect(self.reset) undo_shortcut = QShortcut(QKeySequence("Ctrl+Z"), self.window) undo_shortcut.activated.connect(lambda: self.undo_action()) self.window.installEventFilter(self) self.window.adjustSize() self.window.setWindowTitle('{} Game'.format( GlobalParams.application_name)) self.logging_window = LoggingWindow('ui\LoggingDialog.ui') self.logging_window.btn_login.clicked.connect(self.login_in_game) def eventFilter(self, obj, event): if obj == self.window: if event.type( ) == QEvent.MouseButtonPress: #and self.is_cursor_in_game_zone() key = hook_mouse_event(event) self.draw_key(key) elif event.type() == QEvent.KeyPress and not event.isAutoRepeat(): key = hook_key_event(event) if key == True: return True self.draw_key(key) elif event.type() == QEvent.KeyRelease and event.key( ) == Qt.Key.Key_Tab: self.draw_key(event.key()) else: return False return QMainWindow.eventFilter(self, obj, event) def login_in_game(self): self.user_name = self.logging_window.le_username.text() self.game_time = int(self.logging_window.le_game_time.text()) * 60000 self.user_settings = SettingsParser.parse_to_key_combination( self.logging_window.settings_path) self.logging_window.hide() self.window.show() self.show_start_countdown_dialog() def start_game(self): cur_time = datetime.datetime.fromtimestamp(self.game_time / 1000) self.label_timer.setText( form_timer_label(cur_time.minute, cur_time.second)) self.global_timer.start(self.game_time) self.second_timer.start(1000) self.stat_session = SessionStat(self.user_name, self.game_time, self) queue_size = math.trunc((self.game_time / 60000) * 2) self.build_action_queue(queue_size) self.next_combination() def next_combination(self): if self.actions_queue.empty(): self.stop_game() else: self.action_name = self.actions_queue.get() current_action = self.action_by_name(self.action_name) new_picture = QtGui.QPixmap(current_action.picture_path) self.picture_holder.setPixmap(self.scale_picture(new_picture)) self.current_combination = current_action.combination self.log.clear() self.game_step = 0 print(self.current_combination) def draw_key(self, key): def key_to_text(coded_key): if coded_key in KeyShortcuts.mouse_shorts_map: return KeyShortcuts.mouse_shorts_map.get(coded_key) else: return QKeySequence(coded_key).toString() last_key_pos = len(self.current_combination) if self.game_step < last_key_pos: role = self.current_combination[self.game_step] == key self.insert_key_to_log(key_to_text(key), role) self.solved = False if self.game_step + 1 == last_key_pos and role: self.solved = True self.insert_key_to_log( 'Решено! Нажмите любую клавишу, чтобы продолжить.', True) self.stat_session.solve() elif self.solved: self.next_combination() return else: self.insert_key_to_log(key_to_text(key), False) self.game_step += 1 def show_start_countdown_dialog(self): def get_cur_time(): time = over_timer.remainingTime() return math.trunc(time / 1000) + 1 def timeout(): msg_box.close() over_timer.stop() countdown_timer.stop() self.start_game() msg_box = QMessageBox() msg_box.setIcon(QMessageBox.Information) msg_box.setText('Игра начнется через 5 секунд') msg_box.addButton('Начать сейчас', QMessageBox.YesRole) countdown_timer = QTimer(msg_box) over_timer = QTimer(msg_box) countdown_timer.timeout.connect(lambda: msg_box.setText( 'Игра начнется через {} секунд'.format(get_cur_time()))) over_timer.timeout.connect(timeout) msg_box.buttonClicked.connect(timeout) over_timer.start(5000) countdown_timer.start(1000) msg_box.exec_() def skip(self): self.stat_session.skip() self.actions_queue.put(self.action_name) self.next_combination() def reset(self): self.stat_session.reset() self.game_step = 0 self.log.clear() def undo_action(self): if self.game_step > 0: self.stat_session.reset() self.game_step -= 1 self.log.undo() self.scroll_log_to_max() def redo_action(self): self.game_step += 1 self.log.redo() def refresh_game_zone(self): self.game_step = 0 self.log.clear() def is_cursor_in_game_zone(self): pos = QtGui.QCursor.pos() name_obj = QApplication.widgetAt(pos).objectName() return name_obj == 'picture_holder' def redraw_label_timer(self): self.game_time -= 1000 cur_time = datetime.datetime.fromtimestamp(self.game_time / 1000) self.label_timer.setText( form_timer_label(cur_time.minute, cur_time.second)) def stop_game(self): self.stat_session.finish_game(self.global_timer.remainingTime()) self.global_timer.stop() self.second_timer.stop() def insert_key_to_log(self, key, role: bool): if role: self.log.appendHtml('<font color="#1cb845">{}</font>'.format(key)) else: self.stat_session.make_mistake() self.log.appendHtml('<font color="#cf0a0a">{}</font>'.format(key)) def build_action_queue(self, quantity): action_files = [ file for file in os.listdir('actions/') if os.path.splitext(file)[1] == '.csv' ] if len(action_files) == 0: self.show_error_dialog() self.close() if quantity >= len(action_files): quantity = len(action_files) random.shuffle(action_files) for action in random.sample(action_files, quantity): self.actions_queue.put(action) def show_error_dialog(self): msg_box = QMessageBox() msg_box.setDefaultButton(QMessageBox.Ok) msg_box.setIcon(QMessageBox.Critical) msg_box.setText('Файлы комбинаций не найдены.') msg_box.setInformativeText('Будет произведен выход из приложения.') msg_box.exec_() def action_by_name(self, name): return KeyAction('actions/{}'.format(name), self.user_settings) def scale_picture(self, picture: QPixmap): max_W = self.picture_holder.maximumWidth() max_H = self.picture_holder.maximumHeight() if picture.height() > max_H or picture.width() > max_W: return picture.scaled(max_W, max_H, Qt.KeepAspectRatio) else: return picture def scroll_log_to_max(self): self.log.verticalScrollBar().setValue( self.log.verticalScrollBar().maximum())
class AsemblerIDE(QMainWindow): def __init__(self): super(AsemblerIDE, self).__init__() self.workspace = None self.backupTimer = 300000 PathManager.START_DIRECTORY = os.getcwd() self.workspaceConfiguration = WorkspaceConfiguration.loadConfiguration( ) self.snippetManager = SnippetManager.loadSnippetConfiguration() self.tooltipManager = TooltipManager.loadTooltipConfiguration() self.configurationManager = ConfigurationManager() self.editorTabs = EditorTabWidget(self.snippetManager, self.tooltipManager) self.menuBar = MenuBar() self.terminal = Terminal() self.toolBar = ToolBar(self.configurationManager) self.statusBar = StatusBar() self.treeView = TreeView(self.configurationManager) self.help = HelpWidget() self.ascii = AsciiTableWidget() self.setStatusBar(self.statusBar) self.addToolBar(self.toolBar) self.addDockWidget(Qt.BottomDockWidgetArea, self.terminal) self.addDockWidget(Qt.RightDockWidgetArea, self.help) self.addDockWidget(Qt.RightDockWidgetArea, self.ascii) self.splitDockWidget(self.help, self.ascii, Qt.Vertical) self.treeDock = QDockWidget() self.treeDock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) self.treeDock.setStyleSheet("background-color: #2D2D30; color: white;") self.treeDock.setFeatures(QDockWidget.DockWidgetMovable | QDockWidget.DockWidgetClosable) self.treeDock.setWindowTitle("Workspace explorer") self.treeDock.setWidget(self.treeView) header = QLabel("Workspace explorer") # header.setStyleSheet("background-color: #007ACC;") self.treeDock.setTitleBarWidget(header) self.addDockWidget(Qt.LeftDockWidgetArea, self.treeDock) self.setMenuBar(self.menuBar) self.setMinimumSize(1200, 800) self.setWindowTitle("i386 Assembly Integrated Development Environment") self.setCentralWidget(self.editorTabs) self.setStyleSheet("background-color: #3E3E42; color: white;") self.setWindowIcon(QIcon(resource_path("resources/app_icon.ico"))) self.addTabWidgetEventHandlers() self.addMenuBarEventHandlers() self.addToolBarEventHandlers() self.addTreeViewEventHandlers() self.checkWorkspaceConfiguration() #self.populateTreeView() #self.statusBar.comboBox.currentTextChanged.connect(self.changeEditorSyntax) self.statusBar.tabWidthComboBox.currentTextChanged.connect( self.changeEditorTabWidth) self.timer = QTimer() self.timer.start(self.backupTimer) self.timer.timeout.connect(self.makeBackupSave) self.terminal.console.setFocus() self.tabSwitcher = TabSwitcher(self.editorTabs) self.tabSwitcher.hide() self.projectSwitcher = ProjectSwitcher(self.configurationManager, self.toolBar.projectComboBox) self.projectSwitcher.hide() self.terminal.projectSwitchRequested.connect(self.showProjectSwitcher) self.terminal.tabSwitchRequested.connect(self.showTabSwitcher) self.editorTabs.tabSwitchRequested.connect(self.showTabSwitcher) self.editorTabs.projectSwitchRequested.connect( self.showProjectSwitcher) self.helpDocsDialog = GettingStartedDialog() self.helpDocsDialog.hide() def makeBackupSave(self): self.workspace.saveBackup() self.timer.setInterval( self.backupTimer ) # interval has to be reset cause the timer may have been paused def changeEditorTabWidth(self, text): currentTab: EditorTab = self.editorTabs.getCurrentTab() if currentTab: currentTab.widget.editor.tabSize = int(text) def changeEditorSyntax(self, text): currentTab: EditorTab = self.editorTabs.getCurrentTab() if currentTab: if text == "Assembly": currentTab.widget.editor.sintaksa = AsemblerSintaksa( currentTab.widget.editor.document()) elif text == "C": currentTab.widget.editor.sintaksa = CSyntax( currentTab.widget.editor.document()) currentTab.widget.editor.update() def checkWorkspaceConfiguration(self): defaultWorkspace = self.workspaceConfiguration.getDefaultWorkspace() if defaultWorkspace: if self.openWorkspaceAction(defaultWorkspace): self.show() return else: self.workspaceConfiguration.removeWorkspace(defaultWorkspace) dialog = WorkspaceConfigurationEditor(self.workspaceConfiguration, self) if dialog.exec_(): self.show() else: sys.exit(0) def addTabWidgetEventHandlers(self): self.editorTabs.currentChanged.connect(self.activeTabChanged) def addTreeViewEventHandlers(self): self.treeView.fileDoubleCliked.connect(self.loadFileText) self.treeView.workspaceReload.connect( lambda wsProxy: self.openWorkspaceAction(wsProxy.path, updateWorkspace=True)) self.treeView.workspaceRename.connect( lambda oldPath, wsProxy: self.workspaceConfiguration. replaceWorkpsace(oldPath, wsProxy.path)) self.treeView.newProjectAdded.connect( lambda: self.toolBar.updateComboBox()) self.treeView.projectCompile.connect( lambda proxy: self.compileAction(proxy)) self.treeView.projectDebug.connect( lambda proxy: self.debugAction(proxy)) self.treeView.projectRun.connect(lambda proxy: self.runAction(proxy)) self.treeView.projectRemove.connect( lambda proxy: self.removeProject(proxy)) self.treeView.projectRename.connect( lambda oldPath, project: self.renameProject(oldPath, project)) self.treeView.fileRemove.connect( lambda fileProxy: self.removeFile(fileProxy)) self.treeView.fileRename.connect( lambda oldPath, fileProxy: self.renameFile(oldPath, fileProxy)) self.treeView.fileSave.connect( lambda fileProxy: self.updateEditorTrie(fileProxy)) self.treeView.invalidWorkspace.connect(self.invalidWorkspace) self.treeView.projectSave.connect(self.saveProject) self.treeView.quickAssemblyFile.connect(self.loadFileText) self.treeView.newFile.connect(self.loadFileText) def saveProject(self, projectProxy: ProjectProxy): self.saveAllFiles(projectProxy) def invalidWorkspace(self, workspace: WorkspaceNode): workspace.deleted = True msg = QMessageBox() msg.setStyleSheet("background-color: #2D2D30; color: white;") msg.setModal(True) msg.setIcon(QMessageBox.Critical) msg.setText("Workspace '{}' has been deleted from the disk.".format( workspace.path)) msg.setWindowTitle("Invalid workspace") msg.exec_() if workspace.path in self.workspaceConfiguration.getWorkspaces(): self.workspaceConfiguration.removeWorkspace(workspace.path) self.switchWorkspaceAction() def renameFile(self, oldPath: str, fileProxy: FileProxy): # fileProxy.text = None key = "{}/{}".format(fileProxy.parent.path, oldPath) if key in self.editorTabs.projectTabs: newKey = "{}/{}".format(fileProxy.parent.path, fileProxy.path) tab = self.editorTabs.projectTabs.pop(key) self.editorTabs.projectTabs[newKey] = tab tab.tabName = newKey index = self.editorTabs.tabs.index(fileProxy) tabText = newKey + "*" if fileProxy.hasUnsavedChanges else newKey self.editorTabs.setTabText(index, tabText) def renameProject(self, oldPath: str, project: ProjectNode): self.toolBar.updateComboBox() for fileProxy in project.proxy.files: oldKey = "{}/{}".format(oldPath, fileProxy.path) if oldKey in self.editorTabs.projectTabs: newKey = "{}/{}".format(project.proxy.path, fileProxy.path) tab = self.editorTabs.projectTabs.pop(oldKey) self.editorTabs.projectTabs[newKey] = tab tab.tabName = newKey index = self.editorTabs.tabs.index(fileProxy) tabText = newKey + "*" if fileProxy.hasUnsavedChanges else newKey self.editorTabs.setTabText(index, tabText) def removeFile(self, proxy: FileProxy): key = "{}/{}".format(proxy.parent.path, proxy.path) if key in self.editorTabs.projectTabs: self.editorTabs.closeTab(self.editorTabs.tabs.index(proxy), askToSave=False) def removeProject(self, proxy: ProjectProxy): for file in proxy.files: if file in self.editorTabs.tabs: self.editorTabs.closeTab(self.editorTabs.tabs.index(file), askToSave=False) self.toolBar.updateComboBox() def checkIfWorkspaceDeleted(self): if not os.path.exists(self.workspace.path): self.workspace.deleted = True def activeTabChanged(self, index): if index == -1: self.statusBar.tabWidthComboBox.setCurrentText('4') return syntax = "Assembly" if self.editorTabs.tabs[index].path[-1].lower( ) == "s" else "C" proxy = self.editorTabs.tabs[index] key = "{}/{}".format(proxy.parent.path, proxy.path) self.statusBar.comboBox.setCurrentText(syntax) self.statusBar.tabWidthComboBox.setCurrentText( str(self.editorTabs.projectTabs[key].widget.editor.tabSize)) #self.changeEditorSyntax(syntax) def populateTreeView(self): workspace = WorkspaceNode() workspace.setText(0, "My workspace") self.treeView.setRoot(workspace) for i in range(5): project = ProjectNode() project.setText(0, "My Project {}".format(i + 1)) assemblyFile = AssemblyFileNode() assemblyFile.setText(0, "procedure_{}.S".format(i + 1)) cFile = CFileNode() cFile.setText(0, "main_{}.c".format(i + 1)) self.treeView.addNode(workspace, project) self.treeView.addNode(project, assemblyFile) self.treeView.addNode(project, cFile) project.setExpanded(True) self.workspace = workspace def closeEvent(self, event): if self.tabSwitcher.isActiveWindow(): self.tabSwitcher.hide() if self.helpDocsDialog.isVisible(): self.helpDocsDialog.hide() for proxy in self.editorTabs.tabs: if proxy.hasUnsavedChanges: msg = QMessageBox() msg.setStyleSheet("background-color: #2D2D30; color: white;") msg.setParent(None) msg.setModal(True) msg.setWindowTitle("Confirm Exit") msg.setText("The file {}/{} has been modified.".format( proxy.parent.path, proxy.path)) msg.setInformativeText("Do you want to save the changes?") msg.setStandardButtons(QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel) msg.setDefaultButton(QMessageBox.Save) retValue = msg.exec_() if retValue == QMessageBox.Save: if not self.saveFileAction(): event.ignore() return elif retValue == QMessageBox.Discard: pass else: event.ignore() return self.workspaceConfiguration.saveConfiguration() self.snippetManager.saveConfiguration() self.tooltipManager.saveConfiguration() self.checkIfWorkspaceDeleted() if not self.workspace.deleted: self.workspace.proxy.closedNormally = True self.saveWorkspaceAction() else: if self.workspace.path in self.workspaceConfiguration.getWorkspaces( ): self.workspaceConfiguration.removeWorkspace( self.workspace.path) super(AsemblerIDE, self).closeEvent(event) def addMenuBarEventHandlers(self): self.menuBar.quickAssemblyProjectAction.triggered.connect( self.quickAssemblyProject) self.menuBar.newWorkspaceAction.triggered.connect( self.newWorkspaceAction) self.menuBar.saveWorkspaceAction.triggered.connect( self.saveWorkpsaceAllFiles) self.menuBar.openWorkspaceAction.triggered.connect( self.openWorkspaceAction) self.menuBar.switchWorkspaceAction.triggered.connect( self.switchWorkspaceAction) self.menuBar.saveAction.triggered.connect(self.saveFileAction) self.menuBar.findAction.triggered.connect(self.findAction) self.menuBar.editDefaultWorkspace.triggered.connect( self.editDefaultWorkspaceConfiguration) self.menuBar.editCodeSnippets.triggered.connect(self.editCodeSnippets) self.menuBar.editSettings.triggered.connect(self.editSettings) self.menuBar.aboutAction.triggered.connect(self.showAbout) self.menuBar.helpAction.triggered.connect(self.showGettingStarted) self.menuBar.view.addAction(self.terminal.toggleViewAction()) self.menuBar.view.addAction(self.treeDock.toggleViewAction()) self.menuBar.view.addAction(self.help.toggleViewAction()) self.menuBar.view.addAction(self.ascii.toggleViewAction()) self.menuBar.view.addAction(self.toolBar.toggleViewAction()) def quickAssemblyProject(self): if self.workspace: self.workspace.createQuickAssemblyProject() def showAbout(self): dialog = AboutDialog() dialog.exec_() def showGettingStarted(self): if self.helpDocsDialog.isHidden(): self.helpDocsDialog.show() def findAction(self): currentTab: EditorTabWidget = self.editorTabs.getCurrentTab() if not currentTab: msg = QMessageBox() msg.setStyleSheet("background-color: #2D2D30; color: white;") msg.setModal(True) msg.setIcon(QMessageBox.Critical) msg.setText( "Cannot open file and replace window because there is no open file at the moment." ) msg.setWindowTitle("Find & Replace error") msg.exec_() return currentTab.widget.find.setVisible(True) if currentTab.widget.editor.lastFind: currentTab.widget.find.findLabel.setText( currentTab.widget.editor.lastFind) currentTab.widget.find.findLabel.setFocus() def switchWorkspaceAction(self): remaining = self.timer.remainingTime() self.timer.stop( ) # timer for creating backups needs to be paused when switching ws dialog = WorkspaceConfigurationEditor(self.workspaceConfiguration, self, switch=True) if dialog.exec_() and dialog.workspaceDirectory: self.workspace.proxy.closedNormally = True self.saveWorkspaceAction() if not self.editorTabs.closeAllTabs(): self.timer.start( remaining) # timer for saving backups is resumed return self.openWorkspaceAction(dialog.workspaceDirectory) self.timer.start(remaining) # timer for saving backups is resumed def editDefaultWorkspaceConfiguration(self): editor = DefaultWorkspaceEditor(self.workspaceConfiguration) if editor.exec_(): self.workspaceConfiguration.saveConfiguration() def editCodeSnippets(self): editor = SnippetEditor(self.snippetManager) if editor.exec_(): self.snippetManager.saveConfiguration() def editSettings(self): editor = SettingsEditor(self.tooltipManager) if editor.exec_(): self.tooltipManager.saveConfiguration() def newWorkspaceAction(self): remaining = self.timer.remainingTime() self.timer.stop( ) # timer for creating backups needs to be paused when switching ws if not self.editorTabs.closeAllTabs(): self.timer.start(remaining) # timer for saving backups is resumed return False self.workspace.proxy.closedNormally = True self.saveWorkspaceAction() workspace = WorkspaceNode() name = QFileDialog.getExistingDirectory( self, "New workspace", "select new workspace directory") if name: path = os.path.join(name, ".metadata") backup_path = os.path.join(name, ".backup") if os.path.isdir(path) or os.path.isdir(backup_path): self.msgInvalidFolderError(name) self.timer.start( remaining) # timer for saving backups is resumed return wsname = name[name.rindex(os.path.sep) + 1:] regex = re.compile('[@!#$%^&*()<>?/\|}{~:]') if ' ' in name or regex.search(wsname): msg = QMessageBox() msg.setStyleSheet("background-color: #2D2D30; color: white;") msg.setModal(True) msg.setIcon(QMessageBox.Critical) msg.setText( "Workspace path/name cannot contain whitespace or special characters." ) msg.setWindowTitle("Workspace creation error") msg.exec_() self.timer.start( remaining) # timer for saving backups is resumed return False workspace.path = name proxy = WorkspaceProxy() proxy.path = name workspace.proxy = proxy workspace.setIcon(0, QIcon(resource_path("resources/workspace.png"))) workspace.setText(0, wsname) self.workspace = workspace self.treeView.setRoot(self.workspace) self.saveWorkspaceAction() self.configurationManager.allProjects = [] self.configurationManager.currentProject = None self.toolBar.updateComboBox() self.terminal.executeCommand("cd {}".format(self.workspace.path)) self.workspaceConfiguration.addWorkspace(self.workspace.proxy.path) self.timer.start(remaining) # timer for saving backups is resumed return True self.timer.start(remaining) # timer for saving backups is resumed return False def saveWorkspaceAction(self, workspacePath=None): if self.workspace: self.workspace.saveWorkspace(workspacePath) def saveWorkpsaceAllFiles(self): self.saveAllFiles() self.saveWorkspaceAction() def updateProjectList(self): self.configurationManager.allProjects = [] self.configurationManager.currentProject = None projects = self.treeView.getProjects() if len(projects): self.configurationManager.allProjects = self.treeView.getProjects() self.configurationManager.currentProject = projects[0] self.toolBar.updateComboBox() def openWorkspaceAction(self, workspacePath=None, updateWorkspace=False): if not self.editorTabs.closeAllTabs(): return if not workspacePath: workspacePath = QFileDialog.getExistingDirectory( self, "Open workspace", "select new workspace directory") if not workspacePath: return regex = re.compile('[@!#$%^&*()<>?/\|}{~:]') if ' ' in workspacePath or regex.search( os.path.basename(workspacePath)): msg = QMessageBox() msg.setStyleSheet("background-color: #2D2D30; color: white;") msg.setModal(True) msg.setIcon(QMessageBox.Critical) msg.setText( "Workspace path/name cannot contain whitespace or special characters." ) msg.setWindowTitle("Workspace creation error") msg.exec_() return False path = os.path.join(workspacePath, ".metadata") backup_path = os.path.join(workspacePath, ".backup") if os.path.isdir(path) or os.path.isdir(backup_path): self.msgInvalidFolderError(workspacePath) return workspace = WorkspaceProxy() self.workspace = WorkspaceNode() if os.path.exists(path): try: # in try block in case there is a corrupted .metadata file on the path with open(path, 'rb') as file: workspace = pickle.load(file) except: workspace.closedNormally = False # set it to false to trigger backup msg in case .metadata is corrupted elif not os.path.exists( backup_path ): # creates a .metadata file for a clean new workspace self.workspace.proxy = workspace self.applyWsCompatibilityFix(workspacePath) self.saveWorkspaceAction(workspacePath) self.workspace.proxy = workspace self.applyWsCompatibilityFix(workspacePath) attempted_backup = False if not self.workspace.proxy.closedNormally: if self.restoreBackupMessage(workspacePath, updateWorkspace=updateWorkspace): attempted_backup = True if self.loadWorkspaceAction( workspacePath, backup=True): # attempt to load backup self.updateProjectList() return True else: self.messageBackupError("closedAbruptly") if self.loadWorkspaceAction( workspacePath, backup=False): # attempt to load regular ws file self.updateProjectList() return True # If the regular file won't load for some reason and there was no backup attempt, ask to load the backup file elif not attempted_backup and self.restoreBackupMessage( workspacePath, failedToLoad=True): if self.loadWorkspaceAction( workspacePath, backup=True): # attempt to load the backup file self.updateProjectList() return True else: self.messageBackupError() return False def msgInvalidFolderError(self, path): msg = QMessageBox() msg.setStyleSheet("background-color: #2D2D30; color: white;") msg.setModal(True) msg.setIcon(QMessageBox.Critical) msg.setText( "Invalid folder for a workspace." "\nEnsure there are no .metadata and .backup folders in \n{}". format(path)) msg.setWindowTitle("Failed to load workspace.") msg.exec_() def messageBackupError(self, msgType=None): msg = QMessageBox() msg.setStyleSheet("background-color: #2D2D30; color: white;") msg.setModal(True) msg.setIcon(QMessageBox.Critical) if msgType == "closedAbruptly": msg.setText("Failed to load {}." "\nRegular workspace save will be restored.".format( ".backup workspace file")) else: msg.setText("Failed to load {}.".format(".backup workspace file")) msg.setWindowTitle("Failed to load backup workspace.") msg.exec_() def applyWsCompatibilityFix(self, workspacePath): try: closedNormally = self.workspace.proxy.closedNormally except AttributeError: closedNormally = True self.workspace.proxy.closedNormally = closedNormally # adds attribute to old ws files self.workspace.path = workspacePath # changes the path to currently selected dir, in case it was moved self.workspace.proxy.path = workspacePath def restoreBackupMessage(self, wsName, failedToLoad=False, updateWorkspace=False): try: msg = QMessageBox() msg.setStyleSheet("background-color: #2D2D30; color: white;") msg.setParent(None) msg.setModal(True) msg.setWindowTitle("Workspace recovery") time = strftime( '%m/%d/%Y %H:%M:%S', localtime(os.path.getmtime(os.path.join(wsName, ".backup")))) if failedToLoad: msg.setText("The workplace {} could not be loaded.\n" "\nTime the backup was created: {}".format( wsName, time)) elif updateWorkspace: msg.setText( "Choose if you want to reload workspace or to recover from backup.\n" "\nTime the backup was created: {}".format(wsName, time)) else: msg.setText("The workplace {} was closed unexpectedly.\n" "\nTime the backup was created: {}".format( wsName, time)) if not updateWorkspace: msg.setInformativeText( "Would you like to recover from backup?") else: msg.setInformativeText( "Would you like to recover from backup? Select No if you just want to update the workspace." ) msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No) msg.setDefaultButton(QMessageBox.Yes) retValue = msg.exec_() if retValue == QMessageBox.Yes: return True else: return except: return False def loadWorkspaceAction(self, workspacePath, backup=False): if backup: path = os.path.join(workspacePath, ".backup") else: path = os.path.join(workspacePath, ".metadata") if os.path.exists(path): try: # in try block in case there is a corrupted .metadata file on the path with open(path, 'rb') as file: workspace = pickle.load(file) except: return False else: return False self.workspace = WorkspaceNode() self.workspace.proxy = workspace self.applyWsCompatibilityFix(workspacePath) self.workspace.setIcon(0, QIcon(resource_path("resources/workspace.png"))) self.workspace.setText( 0, workspacePath[workspacePath.rindex(os.path.sep) + 1:]) self.workspace.proxy.closedNormally = False if backup: success = self.workspace.loadBackupWorkspace(workspacePath) else: success = self.workspace.loadWorkspace() if not success: return False self.treeView.setRoot(self.workspace) projects = self.treeView.getProjects() if projects: self.configurationManager.allProjects.clear() self.configurationManager.allProjects.extend(projects) self.toolBar.updateComboBox() #self.treeView.expandAll() self.terminal.executeCommand("cd {}".format(self.workspace.path)) self.workspaceConfiguration.addWorkspace(self.workspace.proxy.path) if workspacePath: self.saveWorkspaceAction(workspacePath) return True def addToolBarEventHandlers(self): self.toolBar.compile.triggered.connect(self.compileAction) self.toolBar.run.triggered.connect(self.runAction) self.toolBar.debug.triggered.connect(self.debugAction) def debugAction(self, projectProxy=None): currentProject: ProjectNode = self.configurationManager.currentProject if not currentProject: self.showNoCurrentProjectMessage("Debug") return if not os.path.exists(currentProject.proxy.getProjectPath()): currentProject.eventManager.invalidProject.emit(currentProject) return proxy = None if projectProxy: proxy = projectProxy else: if currentProject: proxy = currentProject.proxy if proxy: commandString = proxy.getProjectDebugCommand() self.terminal.console.setFocus() if self.terminal.executeCommand(proxy.getProjectCompileCommand()): copmileString = proxy.getProjectCompileCommand() if ' -g ' not in copmileString: msg = QMessageBox() msg.setStyleSheet( "background-color: #2D2D30; color: white;") msg.setModal(True) msg.setIcon(QMessageBox.Warning) msg.setText( "Please set '-g' option in compiler configuration to be able to debug your project." ) msg.setWindowTitle("Debug warning") msg.exec_() return False self.terminal.executeCommand(commandString) self.toolBar.projectComboBox.setCurrentText(proxy.path) def runAction(self, projectProxy=None): currentProject: ProjectNode = self.configurationManager.currentProject if not currentProject: self.showNoCurrentProjectMessage("Run") return if not os.path.exists(currentProject.proxy.getProjectPath()): currentProject.eventManager.invalidProject.emit(currentProject) return proxy = None if projectProxy: proxy = projectProxy else: if currentProject: proxy = currentProject.proxy if proxy: commandString = proxy.getProjectRunCommand() self.terminal.console.setFocus() if self.terminal.executeCommand(proxy.getProjectCompileCommand()): self.terminal.executeCommand(commandString) self.toolBar.projectComboBox.setCurrentText(proxy.path) def compileAction(self, projectProxy=None): currentProject: ProjectNode = self.configurationManager.currentProject if not currentProject: self.showNoCurrentProjectMessage("Compile") return if not os.path.exists(currentProject.proxy.getProjectPath()): currentProject.eventManager.invalidProject.emit(currentProject) return proxy = None if projectProxy: proxy = projectProxy else: if currentProject: proxy = currentProject.proxy if proxy: commandString = proxy.getProjectCompileCommand() self.terminal.console.setFocus() self.terminal.executeCommand(commandString) self.toolBar.projectComboBox.setCurrentText(proxy.path) def showNoCurrentProjectMessage(self, action: str): msg = QMessageBox() msg.setStyleSheet("background-color: #2D2D30; color: white;") msg.setModal(True) msg.setIcon(QMessageBox.Critical) msg.setText("You have to select a project first.") msg.setWindowTitle("{} error".format(action.capitalize())) msg.exec_() def checkExecutable(self): if self.editor.filePath: destination = self.editor.filePath[:-1] + "out" return os.path.exists(destination) return None def loadFileText(self, fileProxy): key = "{}/{}".format(fileProxy.parent.path, fileProxy.path) if key in self.editorTabs.projectTabs: self.editorTabs.setCurrentIndex( self.editorTabs.tabs.index(fileProxy)) return text = self.openFileAction(fileProxy) fileProxy.text = text fileProxy.hasUnsavedChanges = False if fileProxy.getFilePath()[-1].lower() == "c": currentText = "C" else: currentText = "Assembly" update = True if len(self.editorTabs.tabs) == 0: self.editorTabs.tabs.append(fileProxy) update = False self.editorTabs.addNewTab(fileProxy, update) self.statusBar.comboBox.setCurrentText(currentText) if currentText == "Assembly": self.editorTabs.projectTabs[ key].widget.editor.sintaksa = AsemblerSintaksa( self.editorTabs.projectTabs[key].widget.editor.document()) elif currentText == "C": self.editorTabs.projectTabs[key].widget.editor.sintaksa = CSyntax( self.editorTabs.projectTabs[key].widget.editor.document()) def updateEditorTrie(self, proxy: FileProxy): key = "{}/{}".format(proxy.parent.path, proxy.path) if key in self.editorTabs.projectTabs: self.editorTabs.projectTabs[key].widget.editor.updateTrie() self.editorTabs.removeChangeIdentificator(proxy) def saveAllFiles(self, projectProxy=None): couldNotBeSaved = [] for i in range(len(self.editorTabs.tabs)): fileProxy = self.editorTabs.tabs[i] try: if fileProxy and fileProxy.hasUnsavedChanges: if projectProxy and fileProxy.parent.path != projectProxy.path: continue with open(fileProxy.getFilePath(), 'w') as file: file.write(fileProxy.text) fileProxy.hasUnsavedChanges = False self.updateEditorTrie(fileProxy) except: couldNotBeSaved.append(fileProxy.path) self.saveWorkspaceAction() if couldNotBeSaved: msg = QMessageBox() msg.setStyleSheet("background-color: #2D2D30; color: white;") msg.setModal(True) msg.setIcon(QMessageBox.Critical) msg.setText("The following file(s) could not be saved: {}".format( ','.join(couldNotBeSaved))) msg.setWindowTitle("File save error") msg.exec_() def saveFileAction(self): if len(self.editorTabs.tabs): proxy = self.editorTabs.getCurrentFileProxy() if proxy and proxy.hasUnsavedChanges: try: with open(proxy.getFilePath(), 'w') as file: file.write(proxy.text) proxy.hasUnsavedChanges = False self.updateEditorTrie(proxy) except: msg = QMessageBox() msg.setStyleSheet( "background-color: #2D2D30; color: white;") msg.setModal(True) msg.setIcon(QMessageBox.Critical) msg.setText( "The following file could not be saved: {}".format( proxy.path)) msg.setWindowTitle("File save error") msg.exec_() self.saveWorkspaceAction() return True def openFileAction(self, fileName: FileProxy): text = None # if fileName.text: # return fileName.text with open(fileName.getFilePath(), 'r') as file: text = file.read() return text def keyPressEvent(self, event): if event.modifiers() == Qt.ControlModifier: if event.key() == Qt.Key_Tab: self.showTabSwitcher() elif event.key() == Qt.Key_E: self.showProjectSwitcher() super(AsemblerIDE, self).keyPressEvent(event) def showProjectSwitcher(self): if self.projectSwitcher.isHidden() and len( self.configurationManager.allProjects): self.projectSwitcher.showSwitcher() self.projectSwitcher.setFocus() def showTabSwitcher(self): if self.tabSwitcher.isHidden() and len(self.editorTabs.tabs): self.tabSwitcher.showSwitcher() self.tabSwitcher.setFocus()
class Notification(QFrame): """Custom pop-up notification widget with fade-in and fade-out effect.""" def __init__(self, parent, txt, anim_duration=500, life_span=None, word_wrap=True, corner=Qt.TopRightCorner): """ Args: parent (QWidget): Parent widget txt (str): Text to display in notification anim_duration (int): Duration of the animation in msecs life_span (int): How long does the notification stays in place in msecs word_wrap (bool) corner (Qt.Corner) """ super().__init__() if life_span is None: word_count = len(txt.split(" ")) mspw = 60000 / 140 # Assume people can read ~140 words per minute life_span = mspw * word_count self.setFocusPolicy(Qt.NoFocus) self.setWindowFlags(Qt.Popup) self.setParent(parent) self._parent = parent self._corner = corner self.label = QLabel(txt) self.label.setMaximumSize(parent.size()) self.label.setAlignment(Qt.AlignCenter) self.label.setWordWrap(word_wrap) self.label.setMargin(8) self.label.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) font = QFont() font.setBold(True) self.label.setFont(font) layout = QHBoxLayout() layout.addWidget(self.label) layout.setSizeConstraint(QLayout.SetMinimumSize) layout.setContentsMargins(3, 3, 3, 3) self.setLayout(layout) self.adjustSize() self.setAttribute(Qt.WA_DeleteOnClose) self.setObjectName("Notification") self._background_color = "#e6ffc2b3" ss = ("QFrame#Notification{" f"background-color: {self._background_color};" "border-width: 2px;" "border-color: #ffebe6;" "border-style: groove; border-radius: 8px;}") self.setStyleSheet(ss) self.setAcceptDrops(True) self.effect = QGraphicsOpacityEffect() self.setGraphicsEffect(self.effect) self.effect.setOpacity(0.0) self._opacity = 0.0 self.timer = QTimer(self) self.timer.setInterval(life_span) self.timer.timeout.connect(self.start_self_destruction) # Fade in animation self.fade_in_anim = QPropertyAnimation(self, b"opacity") self.fade_in_anim.setDuration(anim_duration) self.fade_in_anim.setStartValue(0.0) self.fade_in_anim.setEndValue(1.0) self.fade_in_anim.valueChanged.connect(self.update_opacity) self.fade_in_anim.finished.connect(self.timer.start) # Fade out animation self.fade_out_anim = QPropertyAnimation(self, b"opacity") self.fade_out_anim.setDuration(anim_duration) self.fade_out_anim.setStartValue(1.0) self.fade_out_anim.setEndValue(0) self.fade_out_anim.valueChanged.connect(self.update_opacity) self.fade_out_anim.finished.connect(self.close) # Start fade in animation self.fade_in_anim.start(QPropertyAnimation.DeleteWhenStopped) def show(self): # Move to the top right corner of the parent super().show() if self._corner in (Qt.TopRightCorner, Qt.BottomRightCorner): x = self._parent.size().width() - self.width() - 2 else: x = self.pos().x() if self._corner in (Qt.BottomLeftCorner, Qt.BottomRightCorner): y = self._parent.size().height() - self.height() - 2 else: y = self.pos().y() self.move(x, y) def get_opacity(self): """opacity getter.""" return self._opacity def set_opacity(self, op): """opacity setter.""" self._opacity = op @Slot(float) def update_opacity(self, value): """Updates graphics effect opacity.""" self.effect.setOpacity(value) def start_self_destruction(self): """Starts fade-out animation and closing of the notification.""" self.fade_out_anim.start(QPropertyAnimation.DeleteWhenStopped) self.setAttribute(Qt.WA_TransparentForMouseEvents) def enterEvent(self, e): super().enterEvent(e) self.start_self_destruction() def dragEnterEvent(self, e): super().dragEnterEvent(e) self.start_self_destruction() def remaining_time(self): if self.timer.isActive(): return self.timer.remainingTime() if self.fade_out_anim.state() == QPropertyAnimation.Running: return 0 return self.timer.interval() opacity = Property(float, get_opacity, set_opacity)
class Executer: def __init__(self, serialObj, loggerObj): self.serialPort = serial.Serial() self.serialPort = serialObj self.logger = loggerObj self.log = self.logger.log self.execState = ExecState.NotConnected self.serialTimeoutTimer = QTimer() self.serialTimeoutTimer.setSingleShot(True) self.serialTimeoutTimer.setInterval(SERIAL_COMMAND_TIMEOUT) # self.checkStopRequestTimer = QTimer() # self.checkStopRequestTimer.setInterval(500) # self.checkStopRequestTimer.setSingleShot(False) # self.checkStopRequestTimer.timeout.connect(self.processCheckStopRequest) # self.checkStopRequestTimer.start() self._stopRequested = False def execute(self, labCode, inputDataFrame, outputFolder, inputFields=None, outputField=None, progressBar=None, model=None): # self.logger.disableLogging() self.serialPort.flushInput() self.serialPort.flushOutput() startTime = time.time() # progressBar = None if progressBar is not None: progressBar.setValue(0) try: if self.execState == ExecState.NotConnected: if self.serialPort.isOpen(): self.execState = ExecState.Connected else: #This should never happen because this function is called after serial is connected self.log( "Execution failed because serial port is not open, something is wrong", type="ERROR") return ExecutionResult.FAILED if self.execState == ExecState.Connected: if self._sendCommand("SELECT_LAB", labCode, max_retry=SERIAL_COMMAND_MAX_TRIALS * 4) == FAILURE_CODE: self.log("Error occured with lab selection", type="ERROR") return ExecutionResult.FAILED else: self.execState = ExecState.LabSelected if self.execState == ExecState.LabSelected: if model is not None and not model.startswith("RPI:"): self.log( "Started sending the model, this could take a while, please wait", type="INFO") if self._sendSaveModelCommand(model) == FAILURE_CODE: self.log("Failed to send the selected model", type="ERROR") return ExecutionResult.FAILED else: if not model: modelName = lab_default_models[labCode] elif model.startswith("RPI:"): modelName = model[4:] if self._sendCommand("LOAD_MODEL", modelName, timeout=SERIAL_COMMAND_TIMEOUT * 3) == FAILURE_CODE: self.log("Failed to load the required model", type="ERROR") return ExecutionResult.FAILED self.execState = ExecState.ModelLoaded if self.execState == ExecState.ModelLoaded: #load the inputs if inputFields is not None: inputs = inputDataFrame[inputFields] else: inputs = inputDataFrame if outputField: trueOutput = inputDataFrame[outputField] else: trueOutput = None self.execState = ExecState.Processing if self.execState == ExecState.Processing: if labCode == "LabTest": executionResult = self._executeLab(inputs, outputFolder, progressBar=progressBar, plotter=None) elif labCode == "Lab1": executionResult = self._executeLab( inputs, outputFolder, outputHeader="Prediction", progressBar=progressBar, plotter=None, trueOutput=trueOutput, labCode=labCode) elif labCode == "Lab2": executionResult = self._executeLab( inputs, outputFolder, outputHeader="Prediction", progressBar=progressBar, plotter=None, trueOutput=trueOutput, labCode=labCode) else: raise ValueError( "Lab Code should be one of the implemented lab codes for processing to work" ) return ExecutionResult.FAILED if executionResult == FAILURE_CODE: return ExecutionResult.FAILED else: self.execState = ExecState.Done if self.execState == ExecState.Done: if (self._sendCommand("PROCESSING_DONE", "None") != FAILURE_CODE): if progressBar is not None: progressBar.setValue(100) # self.logger.enableLogging() self.log("Processing completed in {} ms".format( (time.time() - startTime) * 1000)) return ExecutionResult.COMPLETED else: self.log("Failed to let RPi know that processing is done", "ERROR") return ExecutionResult.FAILED except StopProcessingRequested: if progressBar is not None: progressBar.setValue(0) return ExecutionResult.INTERRUPTED except Exception as e: self.logger.enableLogging() self.log("Caught exception: {}".format(e), type="ERROR") self.log(traceback.format_exc()) print(traceback.format_stack()) return ExecutionResult.FAILED def executeOther(self, function, payload="None"): if function not in SERIAL_COMMANDS: return ExecutionResult.FAILED result = self._sendCommand(function, payload) if result == FAILURE_CODE: return ExecutionResult.FAILED else: return result def executeProject(self, mainWindow, project_name): if project_name == "SmartGroceries": self.referenceItemsLabels = {} self.itemInHand = None self.itemsInCart = list() self.currentOrderReferenceItems = list() self.currentOrderUserId = 0 self.recommendedItemsButtons = list() self.previousRecommendations = list() self.numberOfGoodRecommendations = 0 self.supportedUsers = range(1, 1001) products = pd.read_csv( os.path.join( os.path.dirname(os.path.dirname( os.path.abspath(__file__))), "projects", "SmartGroceries", "data", "products.csv")) aisles = pd.read_csv( os.path.join( os.path.dirname(os.path.dirname( os.path.abspath(__file__))), "projects", "SmartGroceries", "data", "aisles.csv")) test_data = pd.read_csv( os.path.join( os.path.dirname(os.path.dirname( os.path.abspath(__file__))), "projects", "SmartGroceries", "data", "test_set.csv")) # print(products) # print(aisles) # print(test_data) aisle_name_to_id = { k: v for k, v in zip(aisles.aisle, aisles.aisle_id) } product_name_to_id = { k: v for k, v in zip(products.product_name, products.product_id) } product_id_to_name = { k: v for k, v in zip(products.product_id, products.product_name) } def changeCurrentitem(itemName): if itemName == "": self.itemInHand = None currentItemLabel.setText( f"<b>Select an item from the list or from the recommendations</b>s" ) addToCartButton.setEnabled(False) else: self.itemInHand = product_name_to_id[itemName] currentItemLabel.setText(f"Add <b>{itemName}</b> to Cart") addToCartButton.setEnabled(True) addToCartButton.setFocus() def handleNewOrderButtonClicked(): grouped = test_data.groupby('order_id') while True: order_number = random.sample(grouped.indices.keys(), 1)[0] currentOrder = grouped.get_group(order_number) self.currentOrderReferenceItems = currentOrder.product_id.tolist( ) self.currentOrderUserId = currentOrder.user_id.iloc[0] if len( self.currentOrderReferenceItems ) > 1 and self.currentOrderUserId in self.supportedUsers: break print(self.currentOrderReferenceItems) orderInfo = f"<b>Order ID: </b>{currentOrder.order_id.iloc[0]}<br>" orderInfo += f"<b>User ID: </b>{self.currentOrderUserId} | <b>DOW: </b>{calendar.day_name[currentOrder.order_dow.iloc[0]]} | <b>Hour of Day: </b>{currentOrder.order_hour_of_day.iloc[0]} | <b>Number of Items: </b>{len(self.currentOrderReferenceItems)}" orderInfo += "<br><b>Items in the Reference Order:</b>" for widget in self.referenceItemsLabels.values(): item = referenceItemsLayout.itemAt(0) widget.setVisible(False) referenceItemsLayout.removeItem(item) del item self.referenceItemsLabels.clear() currentCartItems.clear() self.itemsInCart.clear() self.previousRecommendations.clear() self.numberOfGoodRecommendations = 0 updateCurrentRecommendations(list()) for product in self.currentOrderReferenceItems: refItemName = product_id_to_name[product] refItemLabel = QPushButton(refItemName) refItemLabel.setContentsMargins(QMargins(0, 0, 0, 0)) refItemLabel.setStyleSheet("Text-align:left") refItemLabel.setFlat(False) refItemLabel.clicked.connect( partial(changeCurrentitem, refItemName)) self.referenceItemsLabels[product] = refItemLabel orderInfoLabel.setText( f"<b>Order Information</b><br>{orderInfo}") for referenceItemLabel in self.referenceItemsLabels.values(): referenceItemsLayout.addWidget(referenceItemLabel) runAutoButton.setFocus() def handleRunAutomatically(): for referenceItemLabel in self.referenceItemsLabels.values(): referenceItemLabel.click() addToCartButton.click() def updateCurrentRecommendations(recommendations): for widget in self.recommendedItemsButtons: item = recommendationsLayout.itemAt(0) widget.setVisible(False) recommendationsLayout.removeItem(item) del item self.recommendedItemsButtons.clear() for product in recommendations: recItemName = product_id_to_name[product] recItemButton = QPushButton(recItemName) recItemButton.setContentsMargins(QMargins(0, 0, 0, 0)) recItemButton.setStyleSheet("Text-align:left;") if product not in self.currentOrderReferenceItems: recItemButton.setFlat(True) recItemButton.clicked.connect( partial(changeCurrentitem, recItemName)) self.recommendedItemsButtons.append(recItemButton) for recItemButton in self.recommendedItemsButtons: recommendationsLayout.addWidget(recItemButton) if len(recommendations) > 0: currentRecommendationsLabel.setVisible(True) else: currentRecommendationsLabel.setVisible(False) self.previousRecommendations += recommendations def handleAddToCartButtonClicked(): print(self.currentOrderReferenceItems) print(self.itemInHand) if self.itemInHand not in self.currentOrderReferenceItems: QMessageBox( QMessageBox.Critical, "Error adding item to cart", "You can only add items that exists in the reference order" ).exec_() return elif self.itemInHand in self.itemsInCart: QMessageBox(QMessageBox.Critical, "Error adding item to cart", "This item is already in the cart").exec_() return self.referenceItemsLabels[self.itemInHand].setFlat(True) self.itemsInCart.append(self.itemInHand) currentCartItems.addItem(product_id_to_name[self.itemInHand]) if self.itemInHand in self.previousRecommendations: self.numberOfGoodRecommendations += 1 self.referenceItemsLabels[self.itemInHand].setStyleSheet( "Text-align:left; background-color:green;") self.referenceItemsLabels[self.itemInHand].setFlat(False) #update recommendations result = self._sendCommand( "PROCESS_PROJECT_GROUP_2", ";".join([ str(self.currentOrderUserId), ",".join([str(x) for x in self.itemsInCart]), ",".join([ str(x) for x in set(self.previousRecommendations) ]) ])) if result == FAILURE_CODE: self.log( "Processing Failed, error getting recommendations from the RPi" ) return else: try: recommendations = [int(id) for id in result.split(',')] except: recommendations = [] updateCurrentRecommendations(recommendations) if len(self.itemsInCart) == len( self.currentOrderReferenceItems): completionMessage = QMessageBox( QMessageBox.Information, "Order Completed", f"Order Completed with {self.numberOfGoodRecommendations} Good Recommendation(s)\nPress New Order to start a new order" ) if self.numberOfGoodRecommendations == 0: completionMessage.setIconPixmap( QPixmap('images/this_is_fine.jpg')) completionMessage.setWindowIcon(appIcon) completionMessage.exec_() newOrderButton.setFocus() def aisleChanged(): aisle_number = aisle_name_to_id[ selectAisleCombobox.currentText()] products_in_aisle = products[ products.aisle_id == aisle_number].product_name.tolist() selectproductCombobox.clear() selectproductCombobox.addItem("") selectproductCombobox.addItems(products_in_aisle) def itemChanged(): current_item = selectproductCombobox.currentText() changeCurrentitem(current_item) dialog = QDialog(mainWindow) appIcon = QIcon("images/this_is_fine.jpg") dialog.setWindowIcon(appIcon) dialog.setMinimumWidth(600) dialog.setWindowTitle("Smart Groceries Demo") layout = QVBoxLayout() newOrderButton = QPushButton("New Order") orderInfoLabel = QLabel() orderInfoLabel.setTextFormat(Qt.RichText) chooseItemLayout = QHBoxLayout() verticalSpacer = QSpacerItem(20, 20) currentCartItems = QListWidget() layoutWidget = QWidget() referenceItemsLayout = QVBoxLayout(layoutWidget) referenceItemsLayout.setSpacing(0) referenceItemsLayout.setMargin(0) scroll = QScrollArea(dialog) scroll.setWidgetResizable(True) scroll.setMinimumHeight(150) scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) scroll.setWidget(layoutWidget) selectAisleLabel = QLabel("Aisle: ") selectProductLabel = QLabel("Product: ") selectAisleCombobox = QComboBox() selectproductCombobox = QComboBox() chooseItemLayout.addWidget(selectAisleLabel, 0, Qt.AlignLeft) chooseItemLayout.addWidget(selectAisleCombobox, 0, Qt.AlignLeft) chooseItemLayout.addWidget(selectProductLabel, 0, Qt.AlignLeft) chooseItemLayout.addWidget(selectproductCombobox, 0, Qt.AlignLeft) addToCartButton = QPushButton("Add to Cart") currentItemLabel = QLabel() currentItemLabel.setTextFormat(Qt.RichText) if self.itemInHand is None: currentItemLabel.setText( f"<b>Select an item from the list or from the recommendations</b>" ) addToCartButton.setDisabled(True) currentItemLayout = QHBoxLayout() currentItemLayout.addWidget(currentItemLabel) currentItemLayout.addWidget(addToCartButton) recommendationsLayout = QVBoxLayout() recommendationsLayout.setSpacing(0) recommendationsLayout.setMargin(0) newOrderButton.clicked.connect(handleNewOrderButtonClicked) addToCartButton.clicked.connect(handleAddToCartButtonClicked) selectproductCombobox.currentIndexChanged.connect(itemChanged) selectAisleCombobox.currentIndexChanged.connect(aisleChanged) selectAisleCombobox.addItems(aisles.aisle.tolist()) layout.addWidget(newOrderButton) layout.addSpacerItem(verticalSpacer) layout.addWidget(orderInfoLabel) layout.addWidget(scroll) layout.addSpacerItem(verticalSpacer) layout.addLayout(chooseItemLayout) layout.addSpacerItem(verticalSpacer) itemsInTheCartLabel = QLabel("<b>Items in the Cart<b>") layout.addWidget(itemsInTheCartLabel) itemsInTheCartLabel.setTextFormat(Qt.RichText) layout.addWidget(currentCartItems) layout.addSpacerItem(verticalSpacer) currentRecommendationsLabel = QLabel( "<b>Current Recommendations<b>") layout.addWidget(currentRecommendationsLabel) currentRecommendationsLabel.setTextFormat(Qt.RichText) currentRecommendationsLabel.setVisible(False) layout.addLayout(recommendationsLayout) layout.addSpacerItem(verticalSpacer) layout.addLayout(currentItemLayout) runAutoButton = QPushButton("Run and Watch. TRUST ME, IT IS FUN!") layout.addWidget(runAutoButton) runAutoButton.clicked.connect(handleRunAutomatically) dialog.setLayout(layout) handleNewOrderButtonClicked() dialog.exec_() return def reset(self): try: startBytes = bytes([STARTING_BYTE] * 50) self.serialPort.write(startBytes) result = self._sendCommand("RESET", "None") if result is FAILURE_CODE: return ExecutionResult.FAILED else: return ExecutionResult.COMPLETED except SerialTimeoutException as e: self.logger.enableLogging() self.log( "Please try again or reboot the RPi if the problem persists, Caught exception: {}" .format(e), type="ERROR") return ExecutionResult.FAILED except Exception as e: self.logger.enableLogging() self.log("Caught exception: {}".format(e), type="ERROR") self.log(traceback.format_exc()) print(traceback.format_stack()) return ExecutionResult.FAILED def _executeLab(self, inputs, outputFolder, trueOutput=None, labCode=None, outputHeader=None, progressBar=None, plotter=None): if progressBar is not None: progressBarIncrements = 100 / len(inputs.index) currentProgressBarValue = progressBar.value() outputFilePath = os.path.join( outputFolder, datetime.now().strftime("%d-%m_%H-%M-%S")) if trueOutput is None: outputDataFrame = copy.deepcopy(inputs) else: outputDataFrame = copy.deepcopy( pd.concat([inputs, trueOutput], axis=1)) with open(outputFilePath + "_OutputsOnly.csv", 'a') as outFile: headers = [] if outputHeader is not None: outFile.write(outputHeader + "\n") headers = outputHeader.split(",") for i in range(len(inputs.index)): inputStringParts = [ str(n) for n in inputs.iloc[i].values.tolist() ] inputString = ", ".join(inputStringParts) self.log("Now processing: {}".format(inputString), type="INFO") result = self._sendCommand("PROCESS", inputString) if result is FAILURE_CODE: self.log( "Error processing line number {}, possible serial communication issues" .format(i + 1), type="ERROR") return FAILURE_CODE else: self.log("Output is: {}".format(result), type="SUCCESS") outFile.write(result + "\n") if plotter is not None: plotter.addNewData( inputs.iloc[i, 0], float(result.rstrip(' \t\r\n\0').split(',')[0])) if progressBar is not None: currentProgressBarValue += progressBarIncrements progressBar.setValue(currentProgressBarValue) # print(result) outputs = [ float(i) for i in result.rstrip(' \t\r\n\0').split(',') ] for index, output in enumerate(outputs): if index < len(headers): header = headers[index] else: header = f"Unknown_{index+1}" outputDataFrame.loc[i, header] = output outputDataFrame.to_csv(outputFilePath + "_Full.csv", index=False) self.log( f"Outputs Saved in {outputFilePath+'_OutputsOnly.csv'}\nComplete data saved in {outputFilePath+'_Full.csv'}" ) # calculate accuracy if trueOutput is not None and labCode: try: if labCode == "Lab1": from sklearn.metrics import r2_score, mean_squared_error r2Score = r2_score(outputDataFrame.iloc[:, -2], outputDataFrame.iloc[:, -1]) RMSE = mean_squared_error(outputDataFrame.iloc[:, -2], outputDataFrame.iloc[:, -1], squared=False) self.log( f"Regression R2 Score Calculated is {r2Score :.3f} and RMSE is {RMSE :.3f}" ) elif labCode == "Lab2": from sklearn.metrics import accuracy_score, recall_score, f1_score accuracyScore = accuracy_score( outputDataFrame.iloc[:, -2], outputDataFrame.iloc[:, -1]) recallScore = recall_score(outputDataFrame.iloc[:, -2], outputDataFrame.iloc[:, -1]) f1Score = f1_score(outputDataFrame.iloc[:, -2], outputDataFrame.iloc[:, -1]) self.log( f"Classification Metrics\n Accuracy: {accuracyScore*100 :.2f}%, Recall: {recallScore:.2f}, F1-Score: {f1Score:.2f}" ) except Exception as e: self.log( f"Failed to calculate accuracy metrics because of exception: {e}", type="ERROR") return SUCCESS_CODE def _sendCommand(self, command, payload, timeout=SERIAL_COMMAND_TIMEOUT, max_retry=SERIAL_COMMAND_MAX_TRIALS): if not command in SERIAL_COMMANDS: print( "The command provided {} is not a valid serial command".format( command)) return FAILURE_CODE sendBuffer = bytearray() sendBuffer.append(STARTING_BYTE) sendString = command + ":" + payload sendBuffer.extend(sendString.encode("utf-8")) sendBuffer.append(0x00) newChecksum = Crc16() # print("Checksum Calc based on {}".format(sendBuffer[1:])) newChecksum.process(sendBuffer[1:]) checksumBytes = newChecksum.finalbytes() sendBuffer.extend(checksumBytes) # print(len(sendBuffer)) for attempt in range(max_retry): if attempt != 0: self.log( f"Attempt #{attempt+1} to send the command {command} with payload {payload}", type="DEBUG") QCoreApplication.processEvents() # t = time.time() try: self.serialPort.flushInput() self.serialPort.write(sendBuffer) except SerialTimeoutException: self.serialPort.flushOutput() continue self.serialTimeoutTimer.setInterval(timeout) self.serialTimeoutTimer.start() succeeded, string = self.getSerialAck() # print("The time spent from sending a command to receiving a reply (or timeouting) is ",time.time()-t) if succeeded: return string elif not succeeded and "EXCEPTION" in string: break return FAILURE_CODE def _sendSaveModelCommand(self, model): with open(model, 'rb') as modelFile: fileToBeSent = modelFile.read() fileToBeSent = zlib.compress(fileToBeSent, level=9) fileToBeSentStr = " ".join(map(str, fileToBeSent)) self.log( f"Estimated time for model to be sent is {int(len(fileToBeSentStr)/2000)} seconds", type="INFO") return self._sendCommand("SAVE_MODEL", fileToBeSentStr, timeout=SAVE_MODEL_COMMAND_TIMEOUT) def getSerialAck(self): string = "" succeeded = False self.serialState = SerialState.WaitingToStart currentSerialString = "" currentCheckSum = bytearray(2) while (self.serialTimeoutTimer.remainingTime() > 0): QCoreApplication.processEvents() self.processCheckStopRequest() if self.serialState == SerialState.WaitingToStart: newByte = self.serialPort.read() if len(newByte) == 1: if newByte[0] == STARTING_BYTE: self.serialState = SerialState.WaitingForString if self.serialState == SerialState.WaitingForString: newBytes = self.serialPort.read_until(b'\0') if len(newBytes) >= 1: for i in range(len(newBytes)): if newBytes[i] == STARTING_BYTE: pass else: currentSerialString = currentSerialString + newBytes[ i:].decode("utf-8") if newBytes[-1] == 0x00: self.serialState = SerialState.WaitingForChecksum1 break if self.serialState == SerialState.WaitingForChecksum1: newByte = self.serialPort.read() if len(newByte) == 1: currentCheckSum[0] = newByte[0] self.serialState = SerialState.WaitingForChecksum2 if self.serialState == SerialState.WaitingForChecksum2: newByte = self.serialPort.read() if len(newByte) == 1: currentCheckSum[1] = newByte[0] self.serialState = SerialState.CommandDone if self.serialState == SerialState.CommandDone: # check the message integrity receivedCommandCrc = Crc16() receivedCommandCrc.process(currentSerialString.encode('utf-8')) receivedCommandCrcBytes = receivedCommandCrc.finalbytes() # print("Checksum Calc based on {}".format(currentSerialString.encode('utf-8'))) # print("Checksum Received: {}, Calculated: {}".format(currentCheckSum, receivedCommandCrcBytes)) if receivedCommandCrcBytes == currentCheckSum: succeeded = True string = currentSerialString.split(":")[1].rstrip( ' \t\r\n\0') if string == "None": string = "" else: self.log("Acknowledgment Failed, received: {}".format( currentSerialString.rstrip("\t\r\n\0")), type="ERROR") string = currentSerialString break return succeeded, string def processCheckStopRequest(self): if not self._stopRequested: return else: self._stopRequested = False raise StopProcessingRequested def requestStop(self): self._stopRequested = True @property def execState(self): return self._execState @execState.setter def execState(self, newVal): # print("Switched to Exec State: {}".format(newVal)) self._execState = newVal @property def serialState(self): return self._serialState @serialState.setter def serialState(self, newVal): # print("Switched to Serial State: {}".format(newVal)) self._serialState = newVal