def refresh_ports(self): # Cache value of last selected connection because it might change when manipulating combobox last_selected_connection = self.lastSelectedConnection self._connection_scanner.scan_connections(with_wifi=True) self.connectionComboBox.clear() # Test if there are any available ports if self._connection_scanner.port_list: selected_port_idx = -1 pref_port = Settings().preferred_port # Populate port combo box and get index of preferred port if available for i, port in enumerate(self._connection_scanner.port_list): self.connectionComboBox.addItem(port) if pref_port and port.upper() == pref_port.upper(): selected_port_idx = i # Override preferred port if user made selection and this port is still available if last_selected_connection and last_selected_connection in self._connection_scanner.port_list: selected_port_idx = self._connection_scanner.port_list.index(last_selected_connection) # Set current port self.connectionComboBox.setCurrentIndex(selected_port_idx if selected_port_idx >= 0 else 0) self.connectButton.setEnabled(True) else: self.connectButton.setEnabled(False)
def main(argv=None): if argv is None: import sys argv = sys.argv # Create main app global myApp myApp = QApplication(sys.argv) myApp.setQuitOnLastWindowClosed(True) path = PyInstallerHelper.resource_path("icons\\main.png") icon = QIcon(path) myApp.setWindowIcon(icon) try: sys.argv.index("--debug") Settings().debug_mode = True except ValueError: pass ex2 = MainWindow() ex2.show() # Execute the Application and Exit sys.exit(myApp.exec_())
def send_line(self, line_text, ending="\r\n"): assert isinstance(line_text, str) assert isinstance(ending, str) self._serial.write((line_text + ending).encode('utf-8')) self._serial.flush() time.sleep(Settings().send_sleep)
def _read_file_job(self, file_name, transfer): try: file_size = self.get_file_size(file_name) except OperationError: transfer.mark_error("Failed to determine file size.") return self._auto_reader_lock.acquire() self._auto_read_enabled = False if Settings().use_transfer_scripts: self._send_command("__upl.download",file_name,paste=True) else: try: self.send_download_file(file_name) except FileNotFoundError: transfer.mark_error("Couldn't locate download transfer script.") if not transfer.error: self.read_junk() try: self.recv_file(transfer, file_size) transfer.mark_finished() except FileTransferError as e: transfer.mark_error(e.details) self._auto_read_enabled = True self._auto_reader_lock.release()
def _remove_file_job(self, file_name, transfer, set_text): #TODO use translate (?) s = "Removing %s, please wait..." % file_name if set_text is not None: set_text(s) self._auto_reader_lock.acquire() self._auto_read_enabled = False success = False command = "os.remove" if Settings().use_transfer_scripts: #This removes both files and folders recursively command = "__upl.remove" self._send_command(command, file_name) try: self.read_to_next_prompt() success = True except TimeoutError: success = False transfer.mark_finished() self._auto_read_enabled = True self._auto_reader_lock.release() if not success: raise OperationError()
def start_connection(self): self.set_status("Connecting...") connection = self._connection_scanner.port_list[ self.connectionComboBox.currentIndex()] if connection == "wifi": ip_address = self.ipLineEdit.text() port = self.portSpinBox.value() if not IpHelper.is_valid_ipv4(ip_address): QMessageBox().warning(self, "Invalid IP", "The IP address has invalid format", QMessageBox.Ok) return try: self._connection = WifiConnection(ip_address, port, self._terminal, self.ask_for_password) except PasswordException: self.set_status("Password") return except NewPasswordException: QMessageBox().information( self, "Password set", "WebREPL password was not previously configured, so it was set to " "\"passw\" (without quotes). " "You can change it in port_config.py (will require reboot to take effect). " "Caution: Passwords longer than 9 characters will be truncated.\n\n" "Continue by connecting again.", QMessageBox.Ok) return else: baud_rate = BaudOptions.speeds[self.baudComboBox.currentIndex()] self._connection = SerialConnection( connection, baud_rate, self._terminal, self.serialResetCheckBox.isChecked()) if self._connection.is_connected(): if not self.serial_mcu_connection_valid(): self._connection.disconnect() self._connection = None else: # serial connection didn't work, so likely the unplugged the serial device and COM value is stale self.refresh_ports() if self._connection is not None and self._connection.is_connected(): self.connected() if isinstance(self._connection, SerialConnection): if Settings( ).use_transfer_scripts and not self._connection.check_transfer_scripts_version( ): QMessageBox.warning( self, "Transfer scripts problem", "Transfer scripts for UART are either" " missing or have wrong version.\nPlease use 'File->Init transfer files' to" " fix this issue.") else: self._connection = None self.set_status("Error") self.refresh_ports()
def select_preset(self): idx = self.presetsListView.currentIndex() assert isinstance(idx, QModelIndex) if idx.row() < 0: return _, self.selected_ip, self.selected_port, self.selected_password = Settings().wifi_presets[idx.row()] self.accept()
def update_preset_list(self): self.model.removeRows(0, self.model.rowCount()) for preset in Settings().wifi_presets: idx = self.model.rowCount() self.model.insertRow(idx) name, ip, port, _ = preset text = "{}\nIP: {} Port: {}".format(name, ip, port) self.model.setData(self.model.index(idx), text)
def refresh_ports(self): self._connection_scanner.scan_connections(with_wifi=True) # Populate port combo box and select default self.connectionComboBox.clear() if self._connection_scanner.port_list: selected_port_idx = 0 pref_port = Settings().preferred_port for i, port in enumerate(self._connection_scanner.port_list): self.connectionComboBox.addItem(port) if pref_port and port.upper() == pref_port.upper(): selected_port_idx = i self.connectionComboBox.setCurrentIndex(selected_port_idx) self.connectButton.setEnabled(True) else: self.connectButton.setEnabled(False)
def log(x): if not Settings().debug_mode: return if not Logger._log_file: Logger._log_file = open("log.txt", "w+b", 0) if isinstance(x, str): x = x.encode('utf-8') Logger._log_file.write(x)
def _flash_job(self, python_path, firmware_file, erase_flash): try: params = [python_path, "flash.py", self._port] if firmware_file: params.append("--fw={}".format(firmware_file)) if erase_flash: params.append("--erase") if Settings().debug_mode: params.append("--debug") with subprocess.Popen(params, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=1) as sub: buf = bytearray() delete = 0 Logger.log("Pipe receiving:\r\n") while True: x = sub.stdout.read(1) Logger.log(x) # Flushing content only in large blocks helps with flickering # Flush output if: # - didn't receive any character (timeout?) # - received first backspace (content is going to be deleted) # - received whitespace or dot (used to signal progress) if not x \ or (x[0] == 8 and delete == 0) \ or (x[0] in b"\r\n\t ."): with self._flash_output_mutex: if delete > 0: self._flash_output = self._flash_output[:-delete] self._flash_output.extend(buf) self._flash_output_signal.emit() buf = bytearray() delete = 0 if not x: break if x[0] == 8: delete += 1 else: buf.append(x[0]) Logger.log("\r\nPipe end.\r\n") sub.stdout.close() code = sub.wait() self._flash_finished_signal.emit(code) except (FileNotFoundError, OSError): self._flash_finished_signal.emit(-1) return
def add_preset(self): name = self.nameLineEdit.text() ip = self.ipLineEdit.text() port = self.portSpinBox.value() password = self.passwordLineEdit.text() # Make sure password is non if empty if not password: password = None if not name: QMessageBox().warning(self, "Missing name", "Fill the name of preset", QMessageBox.Ok) return if not IpHelper.is_valid_ipv4(ip): QMessageBox().warning(self, "Invalid IP", "The IP address has invalid format", QMessageBox.Ok) return Settings().wifi_presets.append((name, ip, port, password)) self.update_preset_list()
def __init__(self, parent, connection, terminal): super(TerminalDialog, self).__init__(None, Qt.WindowCloseButtonHint) self.setupUi(self) self.setWindowFlags(Qt.Window) geometry = Settings().retrieve_geometry("terminal") if geometry: self.restoreGeometry(geometry) self.connection = connection self.terminal = terminal self._auto_scroll = True # TODO: Settings? self.terminal_listener = Listener(self.emit_update_content) self._update_content_signal.connect(self.update_content) self.terminal.add_event.connect(self.terminal_listener) self.outputTextEdit.installEventFilter(self) self.outputTextEdit.verticalScrollBar().sliderPressed.connect( self._stop_scrolling) self.outputTextEdit.verticalScrollBar().sliderReleased.connect( self._scroll_released) self.outputTextEdit.verticalScrollBar().installEventFilter(self) self.inputTextBox.installEventFilter(self) self.clearButton.clicked.connect(self.clear_content) self.sendButton.clicked.connect(self.send_input) self.ctrlaButton.clicked.connect(lambda: self.send_control("a")) self.ctrlbButton.clicked.connect(lambda: self.send_control("b")) self.ctrlcButton.clicked.connect(lambda: self.send_control("c")) self.ctrldButton.clicked.connect(lambda: self.send_control("d")) self.ctrleButton.clicked.connect(lambda: self.send_control("e")) fixed_font = QFontDatabase.systemFont(QFontDatabase.FixedFont) self.outputTextEdit.setFont(fixed_font) self.inputTextBox.setFont(fixed_font) self.autoscrollCheckBox.setChecked(self._auto_scroll) self.autoscrollCheckBox.stateChanged.connect(self._auto_scroll_changed) self.terminal.read() self.outputTextEdit.setText( TerminalDialog.process_backspaces(self.terminal.history)) self._input_history_index = 0
def _read_file_job(self, file_name, transfer): self._auto_reader_lock.acquire() self._auto_read_enabled = False if Settings().use_transfer_scripts: self.run_file("__download.py", "file_name=\"{}\"".format(file_name)) else: try: self.send_download_file(file_name) except FileNotFoundError: transfer.mark_error() if not transfer.error: self.read_junk() try: self.recv_file(transfer) transfer.mark_finished() except FileTransferError: transfer.mark_error() self._auto_read_enabled = True self._auto_reader_lock.release()
def open_local_file(self, idx): assert isinstance(idx, QModelIndex) model = self.localFilesTreeView.model() assert isinstance(model, QFileSystemModel) if model.isDir(idx): return local_path = model.filePath(idx) remote_path = local_path.rsplit("/", 1)[1] if Settings().external_editor_path: self.open_external_editor(local_path) else: if FileInfo.is_file_binary(local_path): QMessageBox.information(self, "Binary file detected", "Editor doesn't support binary files.") return with open(local_path) as f: text = "".join(f.readlines()) self.open_code_editor() self._code_editor.set_code(local_path, remote_path, text)
def _write_file_job(self, file_name, text, transfer): if isinstance(text, str): text = text.encode('utf-8') self._auto_reader_lock.acquire() self._auto_read_enabled = False if Settings().use_transfer_scripts: self.run_file("__upload.py", "file_name=\"{}\"".format(file_name)) else: try: self.send_upload_file(file_name) except FileNotFoundError: transfer.mark_error("Couldn't locate upload transfer script.") if not transfer.error: self.read_junk() try: self.send_file(text, transfer) transfer.mark_finished() except FileTransferError as e: transfer.mark_error(e.details) self._auto_read_enabled = True self._auto_reader_lock.release()
def open_local_file(self, idx): assert isinstance(idx, QModelIndex) model = self.localFilesTreeView.model() assert isinstance(model, QFileSystemModel) if model.isDir(idx): return local_path = model.filePath(idx) remote_path = local_path.rsplit("/", 1)[1] if local_path.endswith(".py"): if Settings().external_editor_path: self.open_external_editor(local_path) else: with open(local_path) as f: text = "".join(f.readlines()) self.open_code_editor() self._code_editor.set_code(local_path, remote_path, text) else: QMessageBox.information( self, "Unknown file", "Files without .py ending won't open" " in editor, but can still be transferred.")
def __init__(self, parent, connection): super(CodeEditDialog, self).__init__(None, Qt.WindowCloseButtonHint) self.setupUi(self) geometry = Settings().retrieve_geometry("editor") if geometry: self.restoreGeometry(geometry) self._connection = connection self.saveLocalButton.clicked.connect(self._save_local) self.saveMcuButton.clicked.connect(self._save_to_mcu) #self.runButton.clicked.connect(self._run_file) self.runButton.hide() fixed_font = QFontDatabase.systemFont(QFontDatabase.FixedFont) self.codeEdit.setFont(fixed_font) if connection and connection.is_connected(): self.connected(connection) else: self.disconnected()
def send_bytes(self, binary): self._serial.write(binary) time.sleep(Settings().send_sleep)
def send_character(self, char): assert isinstance(char, str) self._serial.write(char.encode('utf-8')) time.sleep(Settings().send_sleep)
def __init__(self, parent): super(SettingsDialog, self).__init__(parent, Qt.WindowCloseButtonHint) self.setupUi(self) self.setModal(True) # Workaround because UI compiler doesn't recognize QKeySequenceEdit # Create new items new_line_key_edit = SettingsDialog.one_key_sequence_edit( self.terminalGroupBox, "newLineKeyEdit") send_key_edit = SettingsDialog.one_key_sequence_edit( self.terminalGroupBox, "sendKeyEdit") # Replace old items in layout self.terminalFormLayout.replaceWidget(self.newLineKeyEdit, new_line_key_edit) self.terminalFormLayout.replaceWidget(self.sendKeyEdit, send_key_edit) # Set parent to None effectively removing old items self.newLineKeyEdit.setParent(None) self.sendKeyEdit.setParent(None) # Replace references self.newLineKeyEdit = new_line_key_edit self.sendKeyEdit = send_key_edit if Settings().external_editor_path: self.externalPathLineEdit.setText(Settings().external_editor_path) if Settings().external_editor_args: self.externalCommandLineEdit.setText( Settings().external_editor_args) self.externalPathBrowseButton.clicked.connect( self.browse_external_editor) self.newLineKeyEdit.setKeySequence(Settings().new_line_key) self.sendKeyEdit.setKeySequence(Settings().send_key) self.tabSpacesSpinBox.setValue(Settings().terminal_tab_spaces) if Settings().mpy_cross_path: self.mpyCrossPathLineEdit.setText(Settings().mpy_cross_path) self.mpyPathBrowseButton.clicked.connect(self.browse_mpy_cross) if Settings().preferred_port: self.preferredPortLineEdit.setText(Settings().preferred_port) if Settings().external_transfer_scripts_folder: self.transferFilesPathLineEdit.setText( Settings().external_transfer_scripts_folder) self.transferFilesPathBrowseButton.clicked.connect( self.browse_external_transfer_files)
def compile_files(self): local_file_paths = self.get_local_file_selection() compiled_file_paths = [] for local_path in local_file_paths: split = os.path.splitext(local_path) if split[1] == ".mpy": title = "COMPILE WARNING!! " + os.path.basename(local_path) QMessageBox.warning( self, title, "Can't compile .mpy files, already bytecode") continue mpy_path = split[0] + ".mpy" try: os.remove(mpy_path) # Force view to update itself so that it sees file removed self.localFilesTreeView.repaint() QApplication.processEvents(QEventLoop.ExcludeUserInputEvents) except OSError: pass try: with subprocess.Popen( [Settings().mpy_cross_path, os.path.basename(local_path)], cwd=os.path.dirname(local_path), stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE) as proc: proc.wait() # Wait for process to finish out = proc.stderr.read() if out: QMessageBox.warning(self, "Compilation error", out.decode("utf-8")) continue except OSError: QMessageBox.warning(self, "Compilation error", "Failed to run mpy-cross") continue compiled_file_paths += [mpy_path] # Force view to update so that it sees compiled files added self.localFilesTreeView.repaint() QApplication.processEvents(QEventLoop.ExcludeUserInputEvents) # Force view to update last time so that it resorts the content # Without this, the new file will be placed at the bottom of the view no matter what header = self.localFilesTreeView.header() column = header.sortIndicatorSection() order = header.sortIndicatorOrder() # This is necessary so that view actually sorts anything again self.localFilesTreeView.sortByColumn(-1, Qt.AscendingOrder) QApplication.processEvents(QEventLoop.ExcludeUserInputEvents) self.localFilesTreeView.sortByColumn(column, order) selection_model = self.localFilesTreeView.selectionModel() if compiled_file_paths: assert isinstance(selection_model, QItemSelectionModel) selection_model.clearSelection() for mpy_path in compiled_file_paths: idx = self.localFilesTreeView.model().index(mpy_path) selection_model.select( idx, QItemSelectionModel.Select | QItemSelectionModel.Rows) if (self.autoTransferCheckBox.isChecked() and self._connection and self._connection.is_connected() and compiled_file_paths): self.transfer_to_mcu()
def _pick_python(self): p = QFileDialog.getOpenFileName(parent=self, caption="Select python executable") path = p[0] if path: self.pythonPathEdit.setText(path) Settings().python_flash_executable = path
def __init__(self): super(MainWindow, self).__init__() self.setupUi(self) self.setAttribute(Qt.WA_QuitOnClose) geometry = Settings().retrieve_geometry("main") if geometry: self.restoreGeometry(geometry) geometry = Settings().retrieve_geometry("localPanel") if geometry: self.localFilesTreeView.header().restoreState(geometry) self._connection_scanner = ConnectionScanner() self._connection = None self._root_dir = Settings().root_dir self._mcu_files_model = None self._terminal = Terminal() self._terminal_dialog = None self._code_editor = None self._flash_dialog = None self._settings_dialog = None self._about_dialog = None self._preset_password = None self.actionNavigate.triggered.connect(self.navigate_directory) self.actionTerminal.triggered.connect(self.open_terminal) self.actionCode_Editor.triggered.connect(self.open_code_editor) self.actionUpload.triggered.connect(self.upload_transfer_scripts) self.actionFlash.triggered.connect(self.open_flash_dialog) self.actionSettings.triggered.connect(self.open_settings_dialog) self.actionAbout.triggered.connect(self.open_about_dialog) self.connectionComboBox.currentIndexChanged.connect( self.connection_changed) self.refreshButton.clicked.connect(self.refresh_ports) # Populate baud speed combo box and select default self.baudComboBox.clear() for speed in BaudOptions.speeds: self.baudComboBox.addItem(str(speed)) self.baudComboBox.setCurrentIndex(BaudOptions.speeds.index(115200)) self.presetButton.clicked.connect(self.show_presets) self.connectButton.clicked.connect(self.connect_pressed) self.update_file_tree() self.listButton.clicked.connect(self.list_mcu_files) self.mcuFilesListView.clicked.connect(self.mcu_file_selection_changed) self.mcuFilesListView.doubleClicked.connect(self.read_mcu_file) self.executeButton.clicked.connect(self.execute_mcu_code) self.removeButton.clicked.connect(self.remove_file) self.localPathEdit.setText(self._root_dir) local_selection_model = self.localFilesTreeView.selectionModel() local_selection_model.selectionChanged.connect( self.local_file_selection_changed) self.localFilesTreeView.doubleClicked.connect(self.open_local_file) self.compileButton.clicked.connect(self.compile_files) self.update_compile_button() self.autoTransferCheckBox.setChecked(Settings().auto_transfer) self.transferToMcuButton.clicked.connect(self.transfer_to_mcu) self.transferToPcButton.clicked.connect(self.transfer_to_pc) self.disconnected()
def update_compile_button(self): self.compileButton.setEnabled( bool(Settings().mpy_cross_path) and len(self.get_local_file_selection()) > 0)
def save_settings(self): Settings().external_editor_path = self.externalPathLineEdit.text() Settings().external_editor_args = self.externalCommandLineEdit.text() Settings().new_line_key = self.newLineKeyEdit.keySequence() Settings().send_key = self.sendKeyEdit.keySequence() Settings().terminal_tab_spaces = self.tabSpacesSpinBox.value() Settings().mpy_cross_path = self.mpyCrossPathLineEdit.text() Settings().preferred_port = self.preferredPortLineEdit.text() Settings( ).use_transfer_scripts = self.useTransferFilesCheckBox.isChecked() Settings( ).use_custom_transfer_scripts = self.useCustomTransferFilesCheckBox.isChecked( ) Settings( ).external_transfer_scripts_folder = self.transferFilesPathLineEdit.text( ) Settings().save()
def closeEvent(self, event): Settings().update_geometry("terminal", self.saveGeometry()) if self.terminal_listener: self.terminal.add_event.disconnect(self.terminal_listener) self.terminal_listener = None super(TerminalDialog, self).closeEvent(event)
def closeEvent(self, event): Settings().update_geometry("editor", self.saveGeometry()) super(CodeEditDialog, self).closeEvent(event)
from PyQt5.QtWidgets import QApplication from src.gui.main_window import MainWindow from src.helpers.pyinstaller_helper import PyInstallerHelper from src.utility.settings import Settings __author__ = "Ivan Sevcik" # Main Function if __name__ == '__main__': # Create main app myApp = QApplication(sys.argv) myApp.setQuitOnLastWindowClosed(True) path = PyInstallerHelper.resource_path("icons\\main.png") icon = QIcon(path) myApp.setWindowIcon(icon) try: sys.argv.index("--debug") Settings().debug_mode = True except ValueError: pass ex2 = MainWindow() ex2.show() # Execute the Application and Exit sys.exit(myApp.exec_())