Example #1
0
    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)
Example #2
0
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_())
Example #3
0
    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)
Example #4
0
    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()
Example #5
0
    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()
Example #6
0
    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()
Example #7
0
    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()
Example #8
0
 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)
Example #9
0
    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)
Example #10
0
    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)
Example #11
0
    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
Example #12
0
    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()
Example #13
0
    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
Example #14
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()
Example #15
0
    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)
Example #16
0
    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()
Example #17
0
    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.")
Example #18
0
    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()
Example #19
0
 def send_bytes(self, binary):
     self._serial.write(binary)
     time.sleep(Settings().send_sleep)
Example #20
0
    def send_character(self, char):
        assert isinstance(char, str)

        self._serial.write(char.encode('utf-8'))
        time.sleep(Settings().send_sleep)
Example #21
0
    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)
Example #22
0
    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()
Example #23
0
 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
Example #24
0
    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()
Example #25
0
 def update_compile_button(self):
     self.compileButton.setEnabled(
         bool(Settings().mpy_cross_path)
         and len(self.get_local_file_selection()) > 0)
Example #26
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()
Example #27
0
 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)
Example #28
0
 def closeEvent(self, event):
     Settings().update_geometry("editor", self.saveGeometry())
     super(CodeEditDialog, self).closeEvent(event)
Example #29
0
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_())