Пример #1
0
 def add_fs(self):
     """
     Add the file system navigator to the UI.
     """
     # Check for micro:bit
     port, serial_number = self.find_device()
     if not port:
         message = _("Could not find an attached BBC micro:bit.")
         information = _("Please make sure the device is plugged "
                         "into this computer.\n\nThe device must "
                         "have MicroPython flashed onto it before "
                         "the file system will work.\n\n"
                         "Finally, press the device's reset button "
                         "and wait a few seconds before trying "
                         "again.")
         self.view.show_message(message, information)
         return
     self.file_manager_thread = QThread(self)
     self.file_manager = FileManager(port)
     self.file_manager.moveToThread(self.file_manager_thread)
     self.file_manager_thread.started.connect(self.file_manager.on_start)
     self.fs = self.view.add_filesystem(self.workspace_dir(),
                                        self.file_manager, _("micro:bit"))
     self.fs.set_message.connect(self.editor.show_status_message)
     self.fs.set_warning.connect(self.view.show_message)
     self.file_manager_thread.start()
Пример #2
0
def test_FileManager_delete_fail():
    """
    The on_delete_fail signal is emitted when a problem is encountered.
    """
    fm = FileManager("/dev/ttyUSB0")
    fm.on_delete_fail = mock.MagicMock()
    with mock.patch('mu.modes.base.microfs.rm', side_effect=Exception('boom')):
        fm.delete('foo.py')
    fm.on_delete_fail.emit.assert_called_once_with('foo.py')
Пример #3
0
def test_FileManager_ls_fail():
    """
    The on_list_fail signal is emitted when a problem is encountered.
    """
    fm = FileManager("/dev/ttyUSB0")
    fm.on_list_fail = mock.MagicMock()
    with mock.patch("mu.modes.base.microfs.ls", side_effect=Exception("boom")):
        fm.ls()
    fm.on_list_fail.emit.assert_called_once_with()
Пример #4
0
def test_FileManager_on_start():
    """
    When a thread signals it has started, create a serial connection and then
    list the files.
    """
    fm = FileManager("/dev/ttyUSB0")
    fm.ls = mock.MagicMock()
    with mock.patch('mu.modes.base.Serial') as mock_serial:
        fm.on_start()
        mock_serial.assert_called_once_with("/dev/ttyUSB0",
                                            115200,
                                            timeout=1,
                                            parity='N')
    fm.ls.assert_called_once_with()
Пример #5
0
def test_FileManager_on_start_fails():
    """
    When a thread signals it has started, but the serial connection cannot be
    established, ensure that the on_list_fail is emitted to signal Mu can't get
    the list of files from the board (because a connection cannot be
    established).
    """
    fm = FileManager("/dev/ttyUSB0")
    fm.on_list_fail = mock.MagicMock()
    mock_serial = mock.MagicMock(side_effect=Exception('BOOM!'))
    with mock.patch('mu.modes.base.Serial', mock_serial):
        fm.on_start()
        mock_serial.assert_called_once_with("/dev/ttyUSB0",
                                            115200,
                                            timeout=1,
                                            parity='N')
    fm.on_list_fail.emit.assert_called_once_with()
Пример #6
0
    def add_fs(self):
        """
        Add the file system navigator to the UI.
        """

        # Find serial port the ESP8266/ESP32 is connected to
        device = self.editor.current_device

        # Check for MicroPython device
        if not device:
            message = _("Could not find an attached {board_name}").format(
                board_name=self.board_name)
            information = _("Please make sure the device is plugged "
                            "into this computer.\n\nThe device must "
                            "have MicroPython flashed onto it before "
                            "the file system will work.\n\n"
                            "Finally, press the device's reset button "
                            "and wait a few seconds before trying "
                            "again.")
            self.view.show_message(message, information)
            return
        self.file_manager_thread = QThread(self)
        self.file_manager = FileManager(device.port)
        self.file_manager.moveToThread(self.file_manager_thread)
        self.file_manager_thread.started.connect(self.file_manager.on_start)

        # Show directory of the current file in the left pane, if any,
        # otherwise show the default workspace_dir
        if self.view.current_tab and self.view.current_tab.path:
            path = os.path.dirname(os.path.abspath(self.view.current_tab.path))
        else:
            path = self.workspace_dir()
        self.fs = self.view.add_filesystem(
            path,
            self.file_manager,
            _("{board_name} board").format(board_name=self.board_name),
        )
        self.fs.set_message.connect(self.editor.show_status_message)
        self.fs.set_warning.connect(self.view.show_message)
        self.file_manager_thread.start()
Пример #7
0
    def add_fs(self):
        """
        Add the file system navigator to the UI.
        """

        # Find serial port boards is connected to
        device_port, serial_number = self.find_device()

        # Check for MicroPython device
        if not device_port:
            message = _("Could not find an attached Seeed's line of boards.")
            information = _("Please make sure the device is plugged "
                            "into this computer.\n\nThe device must "
                            "have MicroPython flashed onto it before "
                            "the file system will work.\n\n"
                            "Finally, press the device's reset button "
                            "and wait a few seconds before trying "
                            "again.")
            self.view.show_message(message, information)
            return

        def on_start():
            self.file_manager.on_start()
            try:
                ArdupyDeviceFileList.serial = self.file_manager.serial
            except Exception as ex:
                print(ex)

        self.file_manager_thread = QThread(self)
        self.file_manager = FileManager(device_port)
        self.file_manager.moveToThread(self.file_manager_thread)
        self.file_manager_thread.started.connect(on_start)
        self.fs = self.view.add_filesystem(self.workspace_dir(),
                                           self.file_manager,
                                           _("Seeed's line of boards"))
        self.fs.set_message.connect(self.editor.show_status_message)
        self.fs.set_warning.connect(self.view.show_message)
        self.file_manager_thread.start()
Пример #8
0
def test_FileManager_ls():
    """
    The on_list_files signal is emitted with a tuple of files when microfs.ls
    completes successfully.
    """
    fm = FileManager("/dev/ttyUSB0")
    fm.serial = mock.MagicMock()
    fm.on_list_files = mock.MagicMock()
    mock_ls = mock.MagicMock(return_value=["foo.py", "bar.py"])
    with mock.patch("mu.modes.base.microfs.ls", mock_ls):
        fm.ls()
    fm.on_list_files.emit.assert_called_once_with(("foo.py", "bar.py"))
Пример #9
0
def test_FileManager_delete():
    """
    The on_delete_file signal is emitted with the name of the effected file
    when microfs.rm completes successfully.
    """
    fm = FileManager("/dev/ttyUSB0")
    fm.serial = mock.MagicMock()
    fm.on_delete_file = mock.MagicMock()
    mock_rm = mock.MagicMock()
    with mock.patch('mu.modes.base.microfs.rm', mock_rm):
        fm.delete('foo.py')
    mock_rm.assert_called_once_with('foo.py', serial=fm.serial)
    fm.on_delete_file.emit.assert_called_once_with('foo.py')
Пример #10
0
def test_fileManager_get():
    """
    The on_get_file signal is emitted with the name of the effected file when
    microfs.get completes successfully.
    """
    fm = FileManager("/dev/ttyUSB0")
    fm.serial = mock.MagicMock()
    fm.on_get_file = mock.MagicMock()
    mock_get = mock.MagicMock()
    with mock.patch("mu.modes.base.microfs.get", mock_get):
        fm.get("foo.py", "bar.py")
    mock_get.assert_called_once_with("foo.py", "bar.py", serial=fm.serial)
    fm.on_get_file.emit.assert_called_once_with("foo.py")
Пример #11
0
def test_FileManager_put():
    """
    The on_put_file signal is emitted with the name of the effected file when
    microfs.put completes successfully.
    """
    fm = FileManager("/dev/ttyUSB0")
    fm.serial = mock.MagicMock()
    fm.on_put_file = mock.MagicMock()
    mock_put = mock.MagicMock()
    path = os.path.join('directory', 'foo.py')
    with mock.patch('mu.modes.base.microfs.put', mock_put):
        fm.put(path)
    mock_put.assert_called_once_with(path, target=None, serial=fm.serial)
    fm.on_put_file.emit.assert_called_once_with('foo.py')
Пример #12
0
class ESPMode(MicroPythonMode):
    """
    Represents the functionality required for running MicroPython on ESP8266
    """
    name = _('ESP MicroPython')
    description = _("Write MicroPython on ESP8266/ESP32 boards.")
    icon = 'esp'
    fs = None

    # There are many boards which use ESP microcontrollers but they often use
    # the same USB / serial chips (which actually define the Vendor ID and
    # Product ID for the connected devices.
    valid_boards = [
        # VID  , PID
        (0x1A86, 0x7523),  # HL-340
        (0x10C4, 0xEA60),  # CP210x
        (0x0403, 0x6015),   # Sparkfun ESP32 VID, PID
    ]

    def actions(self):
        """
        Return an ordered list of actions provided by this module. An action
        is a name (also used to identify the icon) , description, and handler.
        """
        buttons = [
            {
                'name': 'run',
                'display_name': _('Run'),
                'description': _('Run your code directly on the ESP8266/ESP32'
                                 ' via the REPL.'),
                'handler': self.run,
                'shortcut': 'F5',
            },
            {
                'name': 'files',
                'display_name': _('Files'),
                'description': _('Access the file system on ESP8266/ESP32.'),
                'handler': self.toggle_files,
                'shortcut': 'F4',
            },
            {
                'name': 'repl',
                'display_name': _('REPL'),
                'description': _('Use the REPL to live-code on the '
                                 'ESP8266/ESP32.'),
                'handler': self.toggle_repl,
                'shortcut': 'Ctrl+Shift+I',
            }, ]
        if CHARTS:
            buttons.append({
                'name': 'plotter',
                'display_name': _('Plotter'),
                'description': _('Plot incoming REPL data.'),
                'handler': self.toggle_plotter,
                'shortcut': 'CTRL+Shift+P',
            })
        return buttons

    def api(self):
        """
        Return a list of API specifications to be used by auto-suggest and call
        tips.
        """
        return SHARED_APIS + ESP_APIS

    def toggle_repl(self, event):
        if self.fs is None:
            if self.repl:
                # Remove REPL
                super().toggle_repl(event)
                self.set_buttons(files=True)
            elif not (self.repl):
                # Add REPL
                super().toggle_repl(event)
                if self.repl:
                    self.set_buttons(files=False)
        else:
            message = _("REPL and file system cannot work at the same time.")
            information = _("The REPL and file system both use the same USB "
                            "serial connection. Only one can be active "
                            "at any time. Toggle the file system off and "
                            "try again.")
            self.view.show_message(message, information)

    def toggle_plotter(self, event):
        """
        Check for the existence of the file pane before toggling plotter.
        """
        if self.fs is None:
            super().toggle_plotter(event)
            if self.plotter:
                self.set_buttons(files=False)
            elif not (self.repl or self.plotter):
                self.set_buttons(files=True)
        else:
            message = _("The plotter and file system cannot work at the same "
                        "time.")
            information = _("The plotter and file system both use the same "
                            "USB serial connection. Only one can be active "
                            "at any time. Toggle the file system off and "
                            "try again.")
            self.view.show_message(message, information)

    def run(self):
        """
        Takes the currently active tab, compiles the Python script therein into
        a hex file and flashes it all onto the connected device.
        """
        """
        if self.repl:
            message = _("Flashing cannot be performed at the same time as the "
                        "REPL is active.")
            information = _("File transfers use the same "
                            "USB serial connection as the REPL. Toggle the "
                            "REPL off and try again.")
            self.view.show_message(message, information)
            return
        """
        logger.info('Running script.')
        # Grab the Python script.
        tab = self.view.current_tab
        if tab is None:
            # There is no active text editor.
            message = _("Cannot run anything without any active editor tabs.")
            information = _("Running transfers the content of the current tab"
                            " onto the device. It seems like you don't have "
                            " any tabs open.")
            self.view.show_message(message, information)
            return
        python_script = tab.text().split('\n')
        if not self.repl:
            self.toggle_repl(None)
        if self.repl:
            self.view.repl_pane.send_commands(python_script)

    def toggle_files(self, event):
        """
        Check for the existence of the REPL or plotter before toggling the file
        system navigator for the MicroPython device on or off.
        """
        if self.repl:
            message = _("File system cannot work at the same time as the "
                        "REPL or plotter.")
            information = _("The file system and the REPL and plotter "
                            "use the same USB serial connection. Toggle the "
                            "REPL and plotter off and try again.")
            self.view.show_message(message, information)
        else:
            if self.fs is None:
                self.add_fs()
                if self.fs:
                    logger.info('Toggle filesystem on.')
                    self.set_buttons(run=False, repl=False, plotter=False)
            else:
                self.remove_fs()
                logger.info('Toggle filesystem off.')
                self.set_buttons(run=True, repl=True, plotter=True)

    def add_fs(self):
        """
        Add the file system navigator to the UI.
        """

        # Find serial port the ESP8266/ESP32 is connected to
        device_port, serial_number = self.find_device()

        # Check for MicroPython device
        if not device_port:
            message = _('Could not find an attached ESP8266/ESP32.')
            information = _("Please make sure the device is plugged "
                            "into this computer.\n\nThe device must "
                            "have MicroPython flashed onto it before "
                            "the file system will work.\n\n"
                            "Finally, press the device's reset button "
                            "and wait a few seconds before trying "
                            "again.")
            self.view.show_message(message, information)
            return
        self.file_manager_thread = QThread(self)
        self.file_manager = FileManager(device_port)
        self.file_manager.moveToThread(self.file_manager_thread)
        self.file_manager_thread.started.\
            connect(self.file_manager.on_start)

        # Show directory of the current file in the left pane, if any,
        # otherwise show the default workspace_dir
        if self.view.current_tab and self.view.current_tab.path:
            path = os.path.dirname(os.path.abspath(self.view.current_tab.path))
        else:
            path = self.workspace_dir()
        self.fs = self.view.add_filesystem(path,
                                           self.file_manager,
                                           _("ESP board"))
        self.fs.set_message.connect(self.editor.show_status_message)
        self.fs.set_warning.connect(self.view.show_message)
        self.file_manager_thread.start()

    def remove_fs(self):
        """
        Remove the file system navigator from the UI.
        """
        self.view.remove_filesystem()
        self.file_manager = None
        self.file_manager_thread = None
        self.fs = None

    def on_data_flood(self):
        """
        Ensure the Files button is active before the REPL is killed off when
        a data flood of the plotter is detected.
        """
        self.set_buttons(files=True)
        super().on_data_flood()
Пример #13
0
class MicrobitMode(MicroPythonMode):
    """
    Represents the functionality required by the micro:bit mode.
    """

    name = _("BBC micro:bit")
    short_name = "microbit"
    description = _("Write MicroPython for the BBC micro:bit.")
    icon = "microbit"
    fs = None  #: Reference to filesystem navigator.
    flash_thread = None
    file_extensions = ["hex"]

    # Device name should only be supplied for modes
    # supporting more than one board, thus None is returned.
    #
    #               VID,     PID,   manufact., device name
    valid_boards = [(0x0D28, 0x0204, None, "BBC micro:bit")]

    valid_board_ids = [0x9900, 0x9901]  # Board IDs of supported boards.

    python_script = ""

    def actions(self):
        """
        Return an ordered list of actions provided by this module. An action
        is a name (also used to identify the icon) , description, and handler.
        """
        buttons = [
            {
                "name": "flash",
                "display_name": _("Flash"),
                "description": _("Flash your code onto the micro:bit."),
                "handler": self.flash,
                "shortcut": "F3",
            },
            {
                "name": "files",
                "display_name": _("Files"),
                "description": _("Access the file system on the micro:bit."),
                "handler": self.toggle_files,
                "shortcut": "F4",
            },
            {
                "name": "repl",
                "display_name": _("REPL"),
                "description": _("Use the REPL to live-code on the "
                                 "micro:bit."),
                "handler": self.toggle_repl,
                "shortcut": "Ctrl+Shift+I",
            },
        ]
        if CHARTS:
            buttons.append({
                "name": "plotter",
                "display_name": _("Plotter"),
                "description": _("Plot incoming REPL data."),
                "handler": self.toggle_plotter,
                "shortcut": "CTRL+Shift+P",
            })
        return buttons

    def api(self):
        """
        Return a list of API specifications to be used by auto-suggest and call
        tips.
        """
        return SHARED_APIS + MICROBIT_APIS

    def minify_if_needed(self, python_script_bytes):
        """
        Minify the script if is too large to fit in flash via uFlash appended
        method.
        Raises exceptions if minification fails or cannot be performed.
        """
        if len(python_script_bytes) < uflash._MAX_SIZE:
            # Script will fit without issues, no need to minify
            return python_script_bytes
        if not self.editor.minify:
            raise Exception(
                _("Your script is too long and code minification is disabled"))
        if not can_minify:
            raise Exception(
                _("Your script is too long and the minifier isn't available"))

        original_length = len(python_script_bytes)
        script = python_script_bytes.decode("utf-8")
        try:
            mangled = nudatus.mangle(script).encode("utf-8")
        except TokenError as e:
            msg, (line, col) = e.args
            logger.debug("Minify failed")
            logger.exception(e)
            raise Exception("{}\n".format(_("Problem minifying script")) +
                            "{} [{}:{}]".format(msg, line, col))
        saved = original_length - len(mangled)
        percent = saved / original_length * 100
        logger.debug("Script minified, {} bytes ({:.2f}%) saved:".format(
            saved, percent))
        logger.debug(mangled)
        if len(mangled) >= uflash._MAX_SIZE:
            logger.debug("Script still too long after minification")
            raise Exception(
                _("Our minifier tried but your script is too long!"))
        return mangled

    def find_microbit(self):
        """
        Finds a micro:bit path, serial port and board ID.
        """
        port = None
        board_id = None
        path_to_microbit = uflash.find_microbit()
        logger.info("Path to micro:bit: {}".format(path_to_microbit))
        if self.editor.current_device:
            port = self.editor.current_device.port
            serial_number = self.editor.current_device.serial_number
            # The board ID are the first 4 hex digits for the USB serial number
            board_id = int(serial_number[:4], 16)
            logger.info("Serial port: {}".format(port))
            logger.info("Device serial number: {}".format(serial_number))
            logger.info("Board ID: 0x{:x}".format(board_id))
        return path_to_microbit, port, board_id

    def get_device_micropython_version(self):
        """
        Retrieves the MicroPython version from a micro:bit board.
        Errors bubble up, so caller must catch them.
        """
        version_info = microfs.version()
        logger.info(version_info)
        board_info = version_info["version"].split()
        if board_info[0] == "micro:bit" and board_info[1].startswith("v"):
            # New style versions, so the correct information will be
            # in the "release" field.
            # Check the release is a correct semantic version.
            semver.parse(version_info["release"])
            board_version = version_info["release"]
            logger.info("Board MicroPython: {}".format(board_version))
        else:
            # MicroPython was found, but not with an expected version string.
            # 0.0.1 indicates an old unknown version. This is just a valid
            # arbitrary flag for semver comparison
            board_version = "0.0.1"
        return board_version

    def flash(self):
        """
        Performs multiple checks to see if it needs to flash MicroPython
        into the micro:bit and then sends via serial the Python script from the
        currently active tab.
        In some error cases it attaches the code directly into the MicroPython
        hex and flashes that (this method is much slower and deprecated).

        WARNING: This method is getting more complex due to several edge
        cases. Ergo, it's a target for refactoring.
        """
        logger.info("Preparing to flash script.")
        # The first thing to do is check the tab and script are valid.
        tab = self.view.current_tab
        if tab is None:
            # There is no active text editor. Exit.
            return
        python_script = tab.text().encode("utf-8")
        logger.debug("Python script from '{}' tab:".format(tab.label))
        logger.debug(python_script)
        try:
            python_script = self.minify_if_needed(python_script)
        except Exception as e:
            logger.debug("Could not minify Python script")
            warn_message = _('Unable to flash "{}"').format(tab.label)
            self.view.show_message(warn_message, "{}".format(e), "Warning")
            return

        # Next step: find the micro:bit path, port, and board ID.
        path_to_microbit, port, board_id = self.find_microbit()
        # If micro:bit path wasn't found ask the user to locate it.
        user_defined_microbit_path = False
        if path_to_microbit is None:
            path_to_microbit = self.view.get_microbit_path(
                config.HOME_DIRECTORY)
            user_defined_microbit_path = True
            logger.debug(
                "User defined path to micro:bit: {}".format(path_to_microbit))
        if not path_to_microbit or not os.path.exists(path_to_microbit):
            # Try to be helpful... essentially there is nothing Mu can do but
            # prompt for patience while the device is mounted and/or do the
            # classic "have you tried switching it off and on again?" trick.
            # This one's for James at the Raspberry Pi Foundation. ;-)
            message = _("Could not find an attached BBC micro:bit.")
            information = _("Please ensure you leave enough time for the BBC"
                            " micro:bit to be attached and configured"
                            " correctly by your computer. This may take"
                            " several seconds."
                            " Alternatively, try removing and re-attaching the"
                            " device or saving your work and restarting Mu if"
                            " the device remains unfound.")
            self.view.show_message(message, information)
            return

        # Check use of custom runtime.
        rt_hex_path = self.editor.microbit_runtime.strip()
        if rt_hex_path and os.path.exists(rt_hex_path):
            logger.info("Using custom runtime: {}".format(rt_hex_path))
        else:
            rt_hex_path = None
            self.editor.microbit_runtime = ""

        # Old hex-attach flash method when there's no port (likely Windows<8.1
        # and/or old DAPLink), or when user has selected a PC location.
        if not port or user_defined_microbit_path:
            self.flash_start(python_script,
                             path_to_microbit,
                             rt_hex_path,
                             serial_fs=False)
            return
        # If the user has specified a bespoke runtime hex file assume they
        # know what they're doing, always flash it, and hope for the best.
        if rt_hex_path:
            self.flash_start(python_script, path_to_microbit, rt_hex_path)
            return

        # Get the version of MicroPython on the device.
        logger.info("Checking target device.")
        update_micropython = False
        try:
            board_version = self.get_device_micropython_version()
            logger.info("Mu MicroPython: {}".format(
                uflash.MICROPYTHON_VERSION))
            # If there's an older version of MicroPython on the device,
            # update it with the one packaged with Mu.
            if semver.compare(board_version, uflash.MICROPYTHON_VERSION) < 0:
                logger.info("Board MicroPython is older than Mu's MicroPython")
                update_micropython = True
        except Exception:
            # Could not get version of MicroPython. This means either the
            # device has a really old version or running something else.
            logger.warning("Could not detect version of MicroPython.")
            update_micropython = True

        if not python_script.strip():
            logger.info("Python script empty. Forcing flash.")
            update_micropython = True

        if update_micropython:
            if board_id in self.valid_board_ids:
                # The connected board has a serial number that indicates the
                # MicroPython hex bundled with Mu supports it, so flash it.
                self.flash_start(python_script, path_to_microbit, None)
                return
            else:
                message = _("Unsupported BBC micro:bit.")
                information = _(
                    "Your device is newer than this version of Mu. Please "
                    "update Mu to the latest version to support this device."
                    "\n\nhttps://codewith.mu/")
                self.view.show_message(message, information)
                return
        else:
            self.set_buttons(flash=False,
                             repl=False,
                             files=False,
                             plotter=False)
            try:
                self.copy_main(python_script)
            except IOError as ioex:
                # There was a problem with the serial communication with
                # the device, so revert to forced flash... "old style".
                # THIS IS A HACK! :-(
                logger.warning("Could not copy file to device.")
                logger.error(ioex)
                logger.info("Falling back to old-style flashing.")
                self.flash_start(
                    python_script,
                    path_to_microbit,
                    rt_hex_path,
                    serial_fs=False,
                )
                return
            except Exception as ex:
                self.flash_failed(ex)
            self.set_buttons(flash=True, repl=True, files=True, plotter=True)

    def flash_start(self, script, microbit_path, rt_path, serial_fs=True):
        """
        Start the MicroPython hex flashing process in a new thread with a
        custom hex file, or the one provided by uFlash.
        Then send the user script via serial.
        If the serial_fs argument is False, use instead of sending the script,
        use the old method of attaching the script to the hex file.
        """
        logger.info("Flashing new MicroPython runtime onto device")
        self.set_buttons(flash=False, repl=False, files=False, plotter=False)
        status_message = _("Flashing the micro:bit")
        if rt_path:
            status_message += ". {}: {}".format(_("Runtime"), rt_path)
        self.editor.show_status_message(status_message, 10)
        if serial_fs:
            # Store the script to be sent via serial after flashing the runtime
            self.python_script = script
            self.flash_thread = DeviceFlasher([microbit_path], None, rt_path)
        else:
            # Use the old style to attach the script to the hex for flashing
            self.python_script = ""
            self.flash_thread = DeviceFlasher([microbit_path], script, rt_path)
        self.flash_thread.finished.connect(self.flash_finished)
        self.flash_thread.on_flash_fail.connect(self.flash_failed)
        self.flash_thread.start()

    def flash_finished(self):
        """
        Called when the thread used to flash the micro:bit has finished.
        """
        self.editor.show_status_message(_("Finished flashing."))
        logger.info("Flashing successful.")
        self.flash_thread = None
        if self.python_script:
            try:
                self.copy_main(self.python_script)
            except Exception as ex:
                self.flash_failed(ex)
        self.set_buttons(flash=True, repl=True, files=True, plotter=True)

    def copy_main(self, script):
        """
        If script argument contains any code, copy it onto the
        connected micro:bit as main.py, then restart the board (CTRL-D).
        """
        if script.strip():
            logger.info("Copying main.py onto device")
            commands = ["fd = open('main.py', 'wb')", "f = fd.write"]
            while script:
                line = script[:64]
                commands.append("f(" + repr(line) + ")")
                script = script[64:]
            commands.append("fd.close()")
            logger.info(commands)
            serial = microfs.get_serial()
            out, err = microfs.execute(commands, serial)
            logger.info((out, err))
            if err:
                raise IOError(microfs.clean_error(err))
            # Reset the device.
            serial.write(b"import microbit\r\n")
            serial.write(b"microbit.reset()\r\n")
            self.editor.show_status_message(_("Copied code onto micro:bit."))

    def flash_failed(self, error):
        """
        Called when the thread used to flash the micro:bit encounters a
        problem.
        """
        logger.error(error)
        message = _("There was a problem flashing the micro:bit.")
        information = _("Please do not disconnect the device until flashing"
                        " has completed. Please check the logs for more"
                        " information.")
        self.view.show_message(message, information, "Warning")
        self.set_buttons(flash=True, repl=True, files=True, plotter=True)
        self.flash_thread = None

    def toggle_repl(self, event):
        """
        Check for the existence of the file pane before toggling REPL.
        """
        if self.fs is None:
            super().toggle_repl(event)
            if self.repl:
                self.set_buttons(flash=False, files=False)
            elif not (self.repl or self.plotter):
                self.set_buttons(flash=True, files=True)
        else:
            message = _("REPL and file system cannot work at the same time.")
            information = _("The REPL and file system both use the same USB "
                            "serial connection. Only one can be active "
                            "at any time. Toggle the file system off and "
                            "try again.")
            self.view.show_message(message, information)

    def toggle_plotter(self, event):
        """
        Check for the existence of the file pane before toggling plotter.
        """
        if self.fs is None:
            super().toggle_plotter(event)
            if self.plotter:
                self.set_buttons(flash=False, files=False)
            elif not (self.repl or self.plotter):
                self.set_buttons(flash=True, files=True)
        else:
            message = _("The plotter and file system cannot work at the same "
                        "time.")
            information = _("The plotter and file system both use the same "
                            "USB serial connection. Only one can be active "
                            "at any time. Toggle the file system off and "
                            "try again.")
            self.view.show_message(message, information)

    def toggle_files(self, event):
        """
        Check for the existence of the REPL or plotter before toggling the file
        system navigator for the micro:bit on or off.
        """
        if self.repl or self.plotter:
            message = _("File system cannot work at the same time as the "
                        "REPL or plotter.")
            information = _("The file system and the REPL and plotter "
                            "use the same USB serial connection. Toggle the "
                            "REPL and plotter off and try again.")
            self.view.show_message(message, information)
        else:
            if self.fs is None:
                self.add_fs()
                if self.fs:
                    logger.info("Toggle filesystem on.")
                    self.set_buttons(flash=False, repl=False, plotter=False)
            else:
                self.remove_fs()
                logger.info("Toggle filesystem off.")
                self.set_buttons(flash=True, repl=True, plotter=True)

    def add_fs(self):
        """
        Add the file system navigator to the UI.
        """
        # Check for micro:bit
        device = self.editor.current_device
        if device is None:
            message = _("Could not find an attached BBC micro:bit.")
            information = _("Please make sure the device is plugged "
                            "into this computer.\n\nThe device must "
                            "have MicroPython flashed onto it before "
                            "the file system will work.\n\n"
                            "Finally, press the device's reset button "
                            "and wait a few seconds before trying "
                            "again.")
            self.view.show_message(message, information)
            return
        self.file_manager_thread = QThread(self)
        self.file_manager = FileManager(device.port)
        self.file_manager.moveToThread(self.file_manager_thread)
        self.file_manager_thread.started.connect(self.file_manager.on_start)
        self.fs = self.view.add_filesystem(self.workspace_dir(),
                                           self.file_manager, _("micro:bit"))
        self.fs.set_message.connect(self.editor.show_status_message)
        self.fs.set_warning.connect(self.view.show_message)
        self.file_manager_thread.start()

    def remove_fs(self):
        """
        Remove the file system navigator from the UI.
        """
        self.view.remove_filesystem()
        self.file_manager = None
        self.file_manager_thread = None
        self.fs = None

    def on_data_flood(self):
        """
        Ensure the Files button is active before the REPL is killed off when
        a data flood of the plotter is detected.
        """
        self.set_buttons(files=True)
        super().on_data_flood()

    def open_file(self, path):
        """
        Tries to open a MicroPython hex file with an embedded Python script.

        Returns the embedded Python script and newline convention.
        """
        text = None
        if path.lower().endswith(".hex"):
            # Try to open the hex and extract the Python script
            try:
                with open(path, newline="") as f:
                    text = uflash.extract_script(f.read())
            except Exception:
                return None, None
            return text, sniff_newline_convention(text)
        else:
            return None, None

    def deactivate(self):
        """
        Invoked whenever the mode is deactivated.
        """
        super().deactivate()
        if self.fs:
            self.remove_fs()

    def device_changed(self, new_device):
        """
        Invoked when the user changes device.
        """
        super().device_changed(new_device)
        if self.fs:
            self.remove_fs()
            self.add_fs()
Пример #14
0
Файл: esp.py Проект: skerr92/mu
class ESPMode(MicroPythonMode):
    """
    Represents the functionality required for running MicroPython on ESP8266
    """

    name = _("ESP MicroPython")
    short_name = "esp"
    description = _("Write MicroPython on ESP8266/ESP32 boards.")
    icon = "esp"
    fs = None

    # The below list defines the supported devices, however, many
    # devices are using the exact same FTDI USB-interface, with vendor
    # ID 0x403 without reporting their own VID/PID

    # In some instances we can recognize the device not on VID/PID,
    # but on manufacturer ID, that's what the third column is for.
    # These more specific device specifications, should be listed
    # before the generic FTDI VID/PID's
    valid_boards = [
        # VID  , PID,    Manufacturer string, Device name
        (0x1A86, 0x7523, None, "HL-340"),
        (0x10C4, 0xEA60, None, "CP210x"),
        (0x0403, 0x6001, "M5STACK Inc.", "M5Stack ESP32 device"),
        (0x0403, 0x6001, None, None),  # FT232/FT245 (XinaBox CW01, CW02)
        (0x0403, 0x6010, None, None),  # FT2232C/D/L/HL/Q (ESP-WROVER-KIT)
        (0x0403, 0x6011, None, None),  # FT4232
        (0x0403, 0x6014, None, None),  # FT232H
        (0x0403, 0x6015, None, None),  # FT X-Series (Sparkfun ESP32)
        (0x0403, 0x601C, None, None),  # FT4222H
    ]

    def actions(self):
        """
        Return an ordered list of actions provided by this module. An action
        is a name (also used to identify the icon) , description, and handler.
        """
        buttons = [
            {
                "name":
                "run",
                "display_name":
                _("Run"),
                "description":
                _("Run your code directly on the ESP8266/ESP32"
                  " via the REPL."),
                "handler":
                self.run,
                "shortcut":
                "F5",
            },
            {
                "name": "files",
                "display_name": _("Files"),
                "description": _("Access the file system on ESP8266/ESP32."),
                "handler": self.toggle_files,
                "shortcut": "F4",
            },
            {
                "name":
                "repl",
                "display_name":
                _("REPL"),
                "description":
                _("Use the REPL to live-code on the "
                  "ESP8266/ESP32."),
                "handler":
                self.toggle_repl,
                "shortcut":
                "Ctrl+Shift+I",
            },
        ]
        if CHARTS:
            buttons.append({
                "name": "plotter",
                "display_name": _("Plotter"),
                "description": _("Plot incoming REPL data."),
                "handler": self.toggle_plotter,
                "shortcut": "CTRL+Shift+P",
            })
        return buttons

    def api(self):
        """
        Return a list of API specifications to be used by auto-suggest and call
        tips.
        """
        return SHARED_APIS + ESP_APIS

    def toggle_repl(self, event):
        if self.fs is None:
            if self.repl:
                # Remove REPL
                super().toggle_repl(event)
                self.set_buttons(files=True)
            elif not (self.repl):
                # Add REPL
                super().toggle_repl(event)
                if self.repl:
                    self.set_buttons(files=False)
        else:
            message = _("REPL and file system cannot work at the same time.")
            information = _("The REPL and file system both use the same USB "
                            "serial connection. Only one can be active "
                            "at any time. Toggle the file system off and "
                            "try again.")
            self.view.show_message(message, information)

    def toggle_plotter(self, event):
        """
        Check for the existence of the file pane before toggling plotter.
        """
        if self.fs is None:
            super().toggle_plotter(event)
            if self.plotter:
                self.set_buttons(files=False)
            elif not (self.repl or self.plotter):
                self.set_buttons(files=True)
        else:
            message = _("The plotter and file system cannot work at the same "
                        "time.")
            information = _("The plotter and file system both use the same "
                            "USB serial connection. Only one can be active "
                            "at any time. Toggle the file system off and "
                            "try again.")
            self.view.show_message(message, information)

    def run(self):
        """
        Takes the currently active tab, compiles the Python script therein into
        a hex file and flashes it all onto the connected device.
        """
        """
        if self.repl:
            message = _("Flashing cannot be performed at the same time as the "
                        "REPL is active.")
            information = _("File transfers use the same "
                            "USB serial connection as the REPL. Toggle the "
                            "REPL off and try again.")
            self.view.show_message(message, information)
            return
        """
        logger.info("Running script.")
        # Grab the Python script.
        tab = self.view.current_tab
        if tab is None:
            # There is no active text editor.
            message = _("Cannot run anything without any active editor tabs.")
            information = _("Running transfers the content of the current tab"
                            " onto the device. It seems like you don't have "
                            " any tabs open.")
            self.view.show_message(message, information)
            return
        python_script = tab.text().split("\n")
        if not self.repl:
            self.toggle_repl(None)
        if self.repl and self.connection:
            self.connection.send_commands(python_script)

    def toggle_files(self, event):
        """
        Check for the existence of the REPL or plotter before toggling the file
        system navigator for the MicroPython device on or off.
        """
        if self.repl:
            message = _("File system cannot work at the same time as the "
                        "REPL or plotter.")
            information = _("The file system and the REPL and plotter "
                            "use the same USB serial connection. Toggle the "
                            "REPL and plotter off and try again.")
            self.view.show_message(message, information)
        else:
            if self.fs is None:
                self.add_fs()
                if self.fs:
                    logger.info("Toggle filesystem on.")
                    self.set_buttons(run=False, repl=False, plotter=False)
            else:
                self.remove_fs()
                logger.info("Toggle filesystem off.")
                self.set_buttons(run=True, repl=True, plotter=True)

    def add_fs(self):
        """
        Add the file system navigator to the UI.
        """

        # Find serial port the ESP8266/ESP32 is connected to
        device = self.editor.current_device

        # Check for MicroPython device
        if not device:
            message = _("Could not find an attached ESP8266/ESP32.")
            information = _("Please make sure the device is plugged "
                            "into this computer.\n\nThe device must "
                            "have MicroPython flashed onto it before "
                            "the file system will work.\n\n"
                            "Finally, press the device's reset button "
                            "and wait a few seconds before trying "
                            "again.")
            self.view.show_message(message, information)
            return
        self.file_manager_thread = QThread(self)
        self.file_manager = FileManager(device.port)
        self.file_manager.moveToThread(self.file_manager_thread)
        self.file_manager_thread.started.connect(self.file_manager.on_start)

        # Show directory of the current file in the left pane, if any,
        # otherwise show the default workspace_dir
        if self.view.current_tab and self.view.current_tab.path:
            path = os.path.dirname(os.path.abspath(self.view.current_tab.path))
        else:
            path = self.workspace_dir()
        self.fs = self.view.add_filesystem(path, self.file_manager,
                                           _("ESP board"))
        self.fs.set_message.connect(self.editor.show_status_message)
        self.fs.set_warning.connect(self.view.show_message)
        self.file_manager_thread.start()

    def remove_fs(self):
        """
        Remove the file system navigator from the UI.
        """
        self.view.remove_filesystem()
        self.file_manager = None
        self.file_manager_thread = None
        self.fs = None

    def on_data_flood(self):
        """
        Ensure the Files button is active before the REPL is killed off when
        a data flood of the plotter is detected.
        """
        self.set_buttons(files=True)
        super().on_data_flood()

    def deactivate(self):
        """
        Invoked whenever the mode is deactivated.
        """
        super().deactivate()
        if self.fs:
            self.remove_fs()

    def device_changed(self, new_device):
        """
        Invoked when the user changes device.
        """
        super().device_changed(new_device)
        if self.fs:
            self.remove_fs()
            self.add_fs()
Пример #15
0
    def add_fs(self):
        """
        Add the file system navigator to the UI.
        """

        # Find serial port boards is connected to
        device_port, serial_number = self.find_device()
        # Check for MicroPython device
        if not device_port:
            message = _("Could not find an attached Seeed's line of boards.")
            information = _("Please make sure the device is plugged "
                            "into this computer.\n\nThe device must "
                            "have MicroPython flashed onto it before "
                            "the file system will work.\n\n"
                            "Finally, press the device's reset button "
                            "and wait a few seconds before trying "
                            "again.")
            self.view.show_message(message, information)
            return

        def on_start():
            self.file_manager.on_start()
            try:
                ArdupyDeviceFileList.serial = self.file_manager.serial
            except Exception as ex:
                print(ex)

        # replace to US panes
        def add_filesystem(home, file_manager, board_name):
            self.fs = self.view.add_filesystem(home, file_manager, board_name)
            # reset panes
            self.fs.deleteLater()
            self.fs = None
            self.fs = SeeedFileSystemPane(home)
            # setup panes
            self.fs.setFocus()
            self.view.fs_pane = self.fs
            self.view.fs.setWidget(self.fs)
            # connect
            file_manager.on_list_files.connect(self.fs.on_ls)
            file_manager.on_put_file.connect(self.fs.microbit_fs.on_put)
            file_manager.on_delete_file.connect(self.fs.microbit_fs.on_delete)
            file_manager.on_get_file.connect(self.fs.local_fs.on_get)
            file_manager.on_list_fail.connect(self.fs.on_ls_fail)
            file_manager.on_put_fail.connect(self.fs.on_put_fail)
            file_manager.on_delete_fail.connect(self.fs.on_delete_fail)
            file_manager.on_get_fail.connect(self.fs.on_get_fail)
            self.fs.open_file.connect(self.view.open_file)
            self.fs.list_files.connect(file_manager.ls)
            self.fs.microbit_fs.put.connect(file_manager.put)
            self.fs.microbit_fs.delete.connect(file_manager.delete)
            self.fs.microbit_fs.list_files.connect(file_manager.ls)
            self.fs.local_fs.get.connect(file_manager.get)
            self.fs.local_fs.list_files.connect(file_manager.ls)
            self.view.connect_zoom(self.fs)
            return self.fs

        self.file_manager_thread = QThread(self)
        self.file_manager = FileManager(device_port)
        self.file_manager.moveToThread(self.file_manager_thread)
        self.file_manager_thread.started.connect(on_start)
        self.fs = add_filesystem(
            self.workspace_dir(),
            self.file_manager,
            _("Seeed's line of boards"),
        )
        self.fs.set_message.connect(self.editor.show_status_message)
        self.fs.set_warning.connect(self.view.show_message)
        self.file_manager_thread.start()
Пример #16
0
class MicrobitMode(MicroPythonMode):
    """
    Represents the functionality required by the micro:bit mode.
    """

    name = _("BBC micro:bit")
    description = _("Write MicroPython for the BBC micro:bit.")
    icon = "microbit"
    fs = None  #: Reference to filesystem navigator.
    flash_thread = None
    flash_timer = None
    file_extensions = ["hex"]

    valid_boards = [(0x0D28, 0x0204)]  # micro:bit USB VID, PID

    valid_serial_numbers = [9900, 9901]  # Serial numbers of supported boards.

    python_script = ""

    def actions(self):
        """
        Return an ordered list of actions provided by this module. An action
        is a name (also used to identify the icon) , description, and handler.
        """
        buttons = [
            {
                "name": "flash",
                "display_name": _("Flash"),
                "description": _("Flash your code onto the micro:bit."),
                "handler": self.flash,
                "shortcut": "F3",
            },
            {
                "name": "files",
                "display_name": _("Files"),
                "description": _("Access the file system on the micro:bit."),
                "handler": self.toggle_files,
                "shortcut": "F4",
            },
            {
                "name": "repl",
                "display_name": _("REPL"),
                "description": _("Use the REPL to live-code on the "
                                 "micro:bit."),
                "handler": self.toggle_repl,
                "shortcut": "Ctrl+Shift+I",
            },
        ]
        if CHARTS:
            buttons.append({
                "name": "plotter",
                "display_name": _("Plotter"),
                "description": _("Plot incoming REPL data."),
                "handler": self.toggle_plotter,
                "shortcut": "CTRL+Shift+P",
            })
        return buttons

    def api(self):
        """
        Return a list of API specifications to be used by auto-suggest and call
        tips.
        """
        return SHARED_APIS + MICROBIT_APIS

    def flash(self):
        """
        Takes the currently active tab, compiles the Python script therein into
        a hex file and flashes it all onto the connected device.

        WARNING: This method is getting more complex due to several edge
        cases. Ergo, it's a target for refactoring.
        """
        user_defined_microbit_path = None
        self.python_script = ""
        logger.info("Preparing to flash script.")
        # The first thing to do is check the script is valid and of the
        # expected length.
        # Grab the Python script.
        tab = self.view.current_tab
        if tab is None:
            # There is no active text editor. Exit.
            return
        # Check the script's contents.
        python_script = tab.text().encode("utf-8")
        logger.debug("Python script:")
        logger.debug(python_script)
        # Check minification status.
        minify = False
        if uflash.get_minifier():
            minify = self.editor.minify
        # Attempt and handle minification.
        if len(python_script) >= uflash._MAX_SIZE:
            message = _('Unable to flash "{}"').format(tab.label)
            if minify and can_minify:
                orginal = len(python_script)
                script = python_script.decode("utf-8")
                try:
                    mangled = nudatus.mangle(script).encode("utf-8")
                except TokenError as e:
                    msg, (line, col) = e.args
                    logger.debug("Minify failed")
                    logger.exception(e)
                    message = _("Problem with script")
                    information = _("{} [{}:{}]").format(msg, line, col)
                    self.view.show_message(message, information, "Warning")
                    return
                saved = orginal - len(mangled)
                percent = saved / orginal * 100
                logger.debug(
                    "Script minified, {} bytes ({:.2f}%) saved:".format(
                        saved, percent))
                logger.debug(mangled)
                python_script = mangled
                if len(python_script) >= 8192:
                    information = _("Our minifier tried but your "
                                    "script is too long!")
                    self.view.show_message(message, information, "Warning")
                    return
            elif minify and not can_minify:
                information = _("Your script is too long and the minifier"
                                " isn't available")
                self.view.show_message(message, information, "Warning")
                return
            else:
                information = _("Your script is too long!")
                self.view.show_message(message, information, "Warning")
                return
        # By this point, there's a valid Python script in "python_script".
        # Assign this to an attribute for later processing in a different
        # method.
        self.python_script = python_script
        # Next step: find the microbit port and serial number.
        path_to_microbit = uflash.find_microbit()
        logger.info("Path to micro:bit: {}".format(path_to_microbit))
        port = None
        serial_number = None
        try:
            port, serial_number = self.find_device()
            logger.info("Serial port: {}".format(port))
            logger.info("Device serial number: {}".format(serial_number))
        except Exception as ex:
            logger.warning("Unable to make serial connection to micro:bit.")
            logger.warning(ex)
        # Determine the location of the BBC micro:bit. If it can't be found
        # fall back to asking the user to locate it.
        if path_to_microbit is None:
            # Ask the user to locate the device.
            path_to_microbit = self.view.get_microbit_path(HOME_DIRECTORY)
            user_defined_microbit_path = path_to_microbit
            logger.debug("User defined path to micro:bit: {}".format(
                user_defined_microbit_path))
        # Check the path and that it exists simply because the path maybe based
        # on stale data.
        if path_to_microbit and os.path.exists(path_to_microbit):
            force_flash = False  # If set to true, fully flash the device.
            # If there's no port but there's a path_to_microbit, then we're
            # probably running on Windows with an old device, so force flash.
            if not port:
                force_flash = True
            if not self.python_script.strip():
                # If the script is empty, this is a signal to simply force a
                # flash.
                logger.info("Python script empty. Forcing flash.")
                force_flash = True
            logger.info("Checking target device.")
            # Get the version of MicroPython on the device.
            try:
                version_info = microfs.version()
                logger.info(version_info)
                board_info = version_info["version"].split()
                if board_info[0] == "micro:bit" and board_info[1].startswith(
                        "v"):
                    # New style versions, so the correct information will be
                    # in the "release" field.
                    try:
                        # Check the release is a correct semantic version.
                        semver.parse(version_info["release"])
                        board_version = version_info["release"]
                    except ValueError:
                        # If it's an invalid semver, set to unknown version to
                        # force flash.
                        board_version = "0.0.1"
                else:
                    # 0.0.1 indicates an old unknown version. This is just a
                    # valid arbitrary flag for semver comparison a couple of
                    # lines below.
                    board_version = "0.0.1"
                logger.info("Board MicroPython: {}".format(board_version))
                logger.info("Mu MicroPython: {}".format(
                    uflash.MICROPYTHON_VERSION))
                # If there's an older version of MicroPython on the device,
                # update it with the one packaged with Mu.
                if (semver.compare(board_version, uflash.MICROPYTHON_VERSION) <
                        0):
                    force_flash = True
            except Exception:
                # Could not get version of MicroPython. This means either the
                # device has a really old version of MicroPython or is running
                # something else. In any case, flash MicroPython onto the
                # device.
                logger.warning("Could not detect version of MicroPython.")
                force_flash = True
            # Check use of custom runtime.
            rt_hex_path = self.editor.microbit_runtime.strip()
            message = _('Flashing "{}" onto the micro:bit.').format(tab.label)
            if rt_hex_path and os.path.exists(rt_hex_path):
                message = message + _(" Runtime: {}").format(rt_hex_path)
                force_flash = True  # Using a custom runtime, so flash it.
            else:
                rt_hex_path = None
                self.editor.microbit_runtime = ""
            # Check for use of user defined path (to save hex onto local
            # file system.
            if user_defined_microbit_path:
                force_flash = True
            # If we need to flash the device with a clean hex, do so now.
            if force_flash:
                logger.info("Flashing new MicroPython runtime onto device")
                self.editor.show_status_message(message, 10)
                self.set_buttons(flash=False)
                if user_defined_microbit_path or not port:
                    # The user has provided a path to a location on the
                    # filesystem. In this case save the combined hex/script
                    # in the specified path_to_microbit.
                    # Or... Mu has a path to a micro:bit but can't establish
                    # a serial connection, so use the combined hex/script
                    # to flash the device.
                    self.flash_thread = DeviceFlasher([path_to_microbit],
                                                      self.python_script,
                                                      rt_hex_path)
                    # Reset python_script so Mu doesn't try to copy it as the
                    # main.py file.
                    self.python_script = ""
                else:
                    # We appear to need to flash a connected micro:bit device,
                    # so just flash the Python hex with no embedded Python
                    # script, since this will be copied over when the
                    # flashing operation has finished.
                    model_serial_number = int(serial_number[:4])
                    if rt_hex_path:
                        # If the user has specified a bespoke runtime hex file
                        # assume they know what they're doing and hope for the
                        # best.
                        self.flash_thread = DeviceFlasher([path_to_microbit],
                                                          b"", rt_hex_path)
                    elif model_serial_number in self.valid_serial_numbers:
                        # The connected board has a serial number that
                        # indicates the MicroPython hex bundled with Mu
                        # supports it. In which case, flash it.
                        self.flash_thread = DeviceFlasher([path_to_microbit],
                                                          b"", None)
                    else:
                        message = _("Unsupported BBC micro:bit.")
                        information = _("Your device is newer than this "
                                        "version of Mu. Please update Mu "
                                        "to the latest version to support "
                                        "this device.\n\n"
                                        "https://codewith.mu/")
                        self.view.show_message(message, information)
                        return
                if sys.platform == "win32":
                    # Windows blocks on write.
                    self.flash_thread.finished.connect(self.flash_finished)
                else:
                    if user_defined_microbit_path:
                        # Call the flash_finished immediately the thread
                        # finishes if Mu is writing the hex file to a user
                        # defined location on the local filesystem.
                        self.flash_thread.finished.connect(self.flash_finished)
                    else:
                        # Other platforms don't block, so schedule the finish
                        # call for 10 seconds (approximately how long flashing
                        # the connected device takes).
                        self.flash_timer = QTimer()
                        self.flash_timer.timeout.connect(self.flash_finished)
                        self.flash_timer.setSingleShot(True)
                        self.flash_timer.start(10000)
                self.flash_thread.on_flash_fail.connect(self.flash_failed)
                self.flash_thread.start()
            else:
                try:
                    self.copy_main()
                except IOError as ioex:
                    # There was a problem with the serial communication with
                    # the device, so revert to forced flash... "old style".
                    # THIS IS A HACK! :-(
                    logger.warning("Could not copy file to device.")
                    logger.error(ioex)
                    logger.info("Falling back to old-style flashing.")
                    self.flash_thread = DeviceFlasher([path_to_microbit],
                                                      self.python_script,
                                                      rt_hex_path)
                    self.python_script = ""
                    if sys.platform == "win32":
                        # Windows blocks on write.
                        self.flash_thread.finished.connect(self.flash_finished)
                    else:
                        self.flash_timer = QTimer()
                        self.flash_timer.timeout.connect(self.flash_finished)
                        self.flash_timer.setSingleShot(True)
                        self.flash_timer.start(10000)
                    self.flash_thread.on_flash_fail.connect(self.flash_failed)
                    self.flash_thread.start()
                except Exception as ex:
                    self.flash_failed(ex)
        else:
            # Try to be helpful... essentially there is nothing Mu can do but
            # prompt for patience while the device is mounted and/or do the
            # classic "have you tried switching it off and on again?" trick.
            # This one's for James at the Raspberry Pi Foundation. ;-)
            message = _("Could not find an attached BBC micro:bit.")
            information = _("Please ensure you leave enough time for the BBC"
                            " micro:bit to be attached and configured"
                            " correctly by your computer. This may take"
                            " several seconds."
                            " Alternatively, try removing and re-attaching the"
                            " device or saving your work and restarting Mu if"
                            " the device remains unfound.")
            self.view.show_message(message, information)

    def flash_finished(self):
        """
        Called when the thread used to flash the micro:bit has finished.
        """
        self.set_buttons(flash=True)
        self.editor.show_status_message(_("Finished flashing."))
        self.flash_thread = None
        self.flash_timer = None
        if self.python_script:
            try:
                self.copy_main()
            except Exception as ex:
                self.flash_failed(ex)

    def copy_main(self):
        """
        If the attribute self.python_script contains any code, copy it onto the
        connected micro:bit as main.py, then restart the board (CTRL-D).
        """
        if self.python_script.strip():
            script = self.python_script
            logger.info("Copying main.py onto device")
            commands = ["fd = open('main.py', 'wb')", "f = fd.write"]
            while script:
                line = script[:64]
                commands.append("f(" + repr(line) + ")")
                script = script[64:]
            commands.append("fd.close()")
            logger.info(commands)
            serial = microfs.get_serial()
            out, err = microfs.execute(commands, serial)
            logger.info((out, err))
            if err:
                raise IOError(microfs.clean_error(err))
            # Reset the device.
            serial.write(b"import microbit\r\n")
            serial.write(b"microbit.reset()\r\n")
            self.editor.show_status_message(_("Copied code onto micro:bit."))
        self.python_script = ""

    def flash_failed(self, error):
        """
        Called when the thread used to flash the micro:bit encounters a
        problem.
        """
        logger.error(error)
        message = _("There was a problem flashing the micro:bit.")
        information = _("Please do not disconnect the device until flashing"
                        " has completed. Please check the logs for more"
                        " information.")
        self.view.show_message(message, information, "Warning")
        if self.flash_timer:
            self.flash_timer.stop()
            self.flash_timer = None
        self.set_buttons(flash=True)
        self.flash_thread = None

    def toggle_repl(self, event):
        """
        Check for the existence of the file pane before toggling REPL.
        """
        if self.fs is None:
            super().toggle_repl(event)
            if self.repl:
                self.set_buttons(flash=False, files=False)
            elif not (self.repl or self.plotter):
                self.set_buttons(flash=True, files=True)
        else:
            message = _("REPL and file system cannot work at the same time.")
            information = _("The REPL and file system both use the same USB "
                            "serial connection. Only one can be active "
                            "at any time. Toggle the file system off and "
                            "try again.")
            self.view.show_message(message, information)

    def toggle_plotter(self, event):
        """
        Check for the existence of the file pane before toggling plotter.
        """
        if self.fs is None:
            super().toggle_plotter(event)
            if self.plotter:
                self.set_buttons(flash=False, files=False)
            elif not (self.repl or self.plotter):
                self.set_buttons(flash=True, files=True)
        else:
            message = _("The plotter and file system cannot work at the same "
                        "time.")
            information = _("The plotter and file system both use the same "
                            "USB serial connection. Only one can be active "
                            "at any time. Toggle the file system off and "
                            "try again.")
            self.view.show_message(message, information)

    def toggle_files(self, event):
        """
        Check for the existence of the REPL or plotter before toggling the file
        system navigator for the micro:bit on or off.
        """
        if self.repl or self.plotter:
            message = _("File system cannot work at the same time as the "
                        "REPL or plotter.")
            information = _("The file system and the REPL and plotter "
                            "use the same USB serial connection. Toggle the "
                            "REPL and plotter off and try again.")
            self.view.show_message(message, information)
        else:
            if self.fs is None:
                self.add_fs()
                if self.fs:
                    logger.info("Toggle filesystem on.")
                    self.set_buttons(flash=False, repl=False, plotter=False)
            else:
                self.remove_fs()
                logger.info("Toggle filesystem off.")
                self.set_buttons(flash=True, repl=True, plotter=True)

    def add_fs(self):
        """
        Add the file system navigator to the UI.
        """
        # Check for micro:bit
        port, serial_number = self.find_device()
        if not port:
            message = _("Could not find an attached BBC micro:bit.")
            information = _("Please make sure the device is plugged "
                            "into this computer.\n\nThe device must "
                            "have MicroPython flashed onto it before "
                            "the file system will work.\n\n"
                            "Finally, press the device's reset button "
                            "and wait a few seconds before trying "
                            "again.")
            self.view.show_message(message, information)
            return
        self.file_manager_thread = QThread(self)
        self.file_manager = FileManager(port)
        self.file_manager.moveToThread(self.file_manager_thread)
        self.file_manager_thread.started.connect(self.file_manager.on_start)
        self.fs = self.view.add_filesystem(self.workspace_dir(),
                                           self.file_manager, _("micro:bit"))
        self.fs.set_message.connect(self.editor.show_status_message)
        self.fs.set_warning.connect(self.view.show_message)
        self.file_manager_thread.start()

    def remove_fs(self):
        """
        Remove the file system navigator from the UI.
        """
        self.view.remove_filesystem()
        self.file_manager = None
        self.file_manager_thread = None
        self.fs = None

    def on_data_flood(self):
        """
        Ensure the Files button is active before the REPL is killed off when
        a data flood of the plotter is detected.
        """
        self.set_buttons(files=True)
        super().on_data_flood()

    def open_file(self, path):
        """
        Tries to open a MicroPython hex file with an embedded Python script.

        Returns the embedded Python script and newline convention.
        """
        text = None
        if path.lower().endswith(".hex"):
            # Try to open the hex and extract the Python script
            try:
                with open(path, newline="") as f:
                    text = uflash.extract_script(f.read())
            except Exception:
                return None, None
            return text, sniff_newline_convention(text)
        else:
            return None, None
Пример #17
0
class SeeedMode(MicroPythonMode):
    """
    Represents the functionality required for running MicroPython on Seeed's
    line of boards
    """

    name = _("Seeed MicroPython")
    description = _("Use MicroPython on Seeed's line of boards.")
    icon = "seeed"
    fs = None
    info = Info()
    in_running_script = False
    # There are many boards which use ESP microcontrollers but they often use
    # the same USB / serial chips (which actually define the Vendor ID and
    # Product ID for the connected devices.

    # VID  , PID
    valid_boards = info.board_normal + info.board_boot

    def __init__(self, editor, view):
        super().__init__(editor, view)
        self.invoke = FirmwareUpdater(
            mu_code_path=super().workspace_dir(),  # mu_code/
            confirm=self.__confirm,
            show_status=self.editor.show_status_message,
            show_message_box=self.__show_message_box,
            set_all_button=self.__set_all_button,
        )
        self.invoke.info = SeeedMode.info
        self.invoke.start()
        # check terminal finish
        self.terminalKeywords = r"KeyboardInterrupt"
        self.checkTerminalTimer = QTimer()
        self.checkTerminalTimer.timeout.connect(self.checkTerminal)
        # get serial write status
        self.checkSerialWriteTimer = QTimer()
        self.checkSerialWriteTimer.timeout.connect(self.serialWriteFinish)
        ArdupyDeviceFileList.info = SeeedMode.info
        LocalFileTree.info = SeeedMode.info
        editor.addDeviceCallback = self.__asyc_detect_new_device_handle
        editor.rmDeviceCallback = self.__asyc_disconnected_handle

    def __load(self, *args, default_path=None):
        """
        Loads a Python (or other supported) file from the file system or
        extracts a Python script from a hex file.
        """
        # Get all supported extensions from the different modes
        extensions = ["py"]
        for mode_name, mode in self.editor.modes.items():
            if mode.file_extensions:
                extensions += mode.file_extensions
        extensions = set([e.lower() for e in extensions])
        extensions = "*.{} *.{}".format(" *.".join(extensions),
                                        " *.".join(extensions).upper())
        folder = super().workspace_dir()
        allow_previous = False
        path = self.view.get_load_path(folder,
                                       extensions,
                                       allow_previous=allow_previous)
        if path:
            self.current_path = os.path.dirname(os.path.abspath(path))
            self.editor._load(path)

    def __set_all_button(self, state):
        print("button Enable=" + str(state))
        self.set_buttons(files=state, run=state, repl=state, plotter=state)

    def __confirm(self, flag):
        flag.confirm = QMessageBox.Ok == self.view.show_confirmation(
            flag.hint, icon="Question")

    def __show_message_box(self, text):
        self.msg = QMessageBox()
        self.msg.setWindowTitle("Hint")
        self.msg.setDefaultButton(self.msg.Ok)
        self.msg.setText(text)
        self.msg.show()

    def __asyc_disconnected_handle(self, device):
        type = device[0]
        if type == "seeed":
            self.__set_all_button(False)
            self.in_running_script = False
            if self.fs:
                self.toggle_files(None)
            if self.plotter:
                self.toggle_plotter(None)
            if self.repl:
                self.toggle_repl(None)

    def __asyc_detect_new_device_handle(self, device):
        device_name = device[1]
        prefixPath = r"/dev/"
        if device_name[:len(prefixPath)] == prefixPath:
            device_name = device_name[len(prefixPath):]
        self.__set_all_button(False)
        self.info.has_firmware = False
        self.info.board_id = None
        self.info.board_name = device_name
        port = QSerialPortInfo(device_name)

        def match(pvid, ids):
            for valid in ids:
                if pvid == valid:
                    self.info.board_id = str(valid)
                    return True
            return False

        pvid = (port.vendorIdentifier(), port.productIdentifier())
        print(device_name, pvid)

        # need match the seeed board pid vid
        if match(pvid, self.info.board_normal):
            self.invoke.in_bootload_mode = False
            self.invoke.detected = True
            print("detect a normal mode borad")
        if match(pvid, self.info.board_boot):
            self.invoke.in_bootload_mode = True
            self.invoke.detected = True
            print("detect a bootload mode borad")

    def actions(self):
        """
        Return an ordered list of actions provided by this module. An action
        is a name (also used to identify the icon) , description, and handler.
        """
        buttons = [
            {
                "name":
                "run",
                "display_name":
                _("Run"),
                "description":
                _("Run your code directly on the Seeed's"
                  " line of boards. via the REPL."),
                "handler":
                self.run,
                "shortcut":
                "F5",
            },
            {
                "name":
                "files",
                "display_name":
                _("Files"),
                "description":
                _("Access the file system on "
                  "Seeed's line of boards."),
                "handler":
                self.toggle_files,
                "shortcut":
                "F4",
            },
            {
                "name":
                "repl",
                "display_name":
                _("REPL"),
                "description":
                _("Use the REPL to live-code on the "
                  "Seeed's line of boards."),
                "handler":
                self.toggle_repl,
                "shortcut":
                "Ctrl+Shift+I",
            },
        ]
        if CHARTS:
            buttons.append({
                "name": "plotter",
                "display_name": _("Plotter"),
                "description": _("Plot incoming REPL data."),
                "handler": self.toggle_plotter,
                "shortcut": "CTRL+Shift+P",
            })
        self.editor.load = self.__load
        return buttons

    def api(self):
        """
        Return a list of API specifications to be used by auto-suggest and call
        tips.
        """
        return SHARED_APIS + SEEED_APIS

    def toggle_repl(self, event):
        if self.fs is None:
            if self.repl:
                # Remove REPL
                super().toggle_repl(event)
                if self.plotter:
                    super().remove_plotter()
                if self.in_running_script:
                    self.in_running_script = False
                    self.setRunIcon()
                    self.set_buttons(repl=False)
                    self.invoke.board_halt()
                self.set_buttons(modes=True, run=True, files=True, repl=True)
            elif not self.repl:
                # Add REPL
                super().toggle_repl(event)
                if not self.repl:
                    return
                self.set_buttons(run=False, files=False, repl=True)
        else:
            message = _("REPL and file system cannot work at the same time.")
            information = _("The REPL and file system both use the same USB "
                            "serial connection. Only one can be active "
                            "at any time. Toggle the file system off and "
                            "try again.")
            self.view.show_message(message, information)

    def toggle_plotter(self, event):
        """
        Check for the existence of the file pane before toggling plotter.
        """
        if self.fs is None:
            super().toggle_plotter(event)
            if self.plotter:
                self.set_buttons(files=False)
            elif not (self.repl or self.plotter):
                self.set_buttons(files=True)
        else:
            message = _("The plotter and file system cannot work at the same "
                        "time.")
            information = _("The plotter and file system both use the same "
                            "USB serial connection. Only one can be active "
                            "at any time. Toggle the file system off and "
                            "try again.")
            self.view.show_message(message, information)

    def run(self):
        """
        Takes the currently active tab, compiles the Python script therein into
        a hex file and flashes it all onto the connected device.
        """
        """
        if self.repl:
            message = _("Flashing cannot be performed at the same time as the "
                        "REPL is active.")
            information = _("File transfers use the same "
                            "USB serial connection as the REPL. Toggle the "
                            "REPL off and try again.")
            self.view.show_message(message, information)
            return
        """
        # stop
        if self.in_running_script:
            self.set_buttons(run=False)
            self.curPos = len(self.view.repl_pane.toPlainText())
            self.view.repl_pane.serial.write(b"\x03")
            self.in_running_script = False
            self.checkTerminalTimer.start(250)
        # run
        else:
            logger.info("Running script.")
            # Grab the Python script.
            tab = self.view.current_tab
            if tab is None:
                # There is no active text editor.
                message = _(
                    "Cannot run anything without any active editor tabs.")
                information = _(
                    "Running transfers the content of the current tab"
                    " onto the device. It seems like you don't have "
                    " any tabs open.")
                self.view.show_message(message, information)
                return
            python_script = tab.text().split("\n")
            if not self.repl:
                super().toggle_repl(None)
            if self.repl:
                self.set_buttons(
                    modes=False,
                    run=False,
                    files=False,
                    repl=True,
                    plotter=True,
                )
                self.view.repl_pane.serial.bytesWritten.connect(
                    self.on_serialData_write)
                self.setRunIcon(False)
                self.in_running_script = True
                self.view.repl_pane.send_commands(python_script)

    def toggle_files(self, event):
        """
        Check for the existence of the REPL or plotter before toggling the file
        system navigator for the MicroPython device on or off.
        """
        if self.repl:
            message = _("File system cannot work at the same time as the "
                        "REPL or plotter.")
            information = _("The file system and the REPL and plotter "
                            "use the same USB serial connection. Toggle the "
                            "REPL and plotter off and try again.")
            self.view.show_message(message, information)
        else:
            if self.fs is None:
                self.add_fs()
                if self.fs:
                    logger.info("Toggle filesystem on.")
                    self.set_buttons(run=False, repl=False, plotter=False)
            else:
                self.remove_fs()
                logger.info("Toggle filesystem off.")
                self.set_buttons(run=True, repl=True, plotter=True)

    def add_fs(self):
        """
        Add the file system navigator to the UI.
        """

        # Find serial port boards is connected to
        device_port, serial_number = self.find_device()
        # Check for MicroPython device
        if not device_port:
            message = _("Could not find an attached Seeed's line of boards.")
            information = _("Please make sure the device is plugged "
                            "into this computer.\n\nThe device must "
                            "have MicroPython flashed onto it before "
                            "the file system will work.\n\n"
                            "Finally, press the device's reset button "
                            "and wait a few seconds before trying "
                            "again.")
            self.view.show_message(message, information)
            return

        def on_start():
            self.file_manager.on_start()
            try:
                ArdupyDeviceFileList.serial = self.file_manager.serial
            except Exception as ex:
                print(ex)

        # replace to US panes
        def add_filesystem(home, file_manager, board_name):
            self.fs = self.view.add_filesystem(home, file_manager, board_name)
            # reset panes
            self.fs.deleteLater()
            self.fs = None
            self.fs = SeeedFileSystemPane(home)
            # setup panes
            self.fs.setFocus()
            self.view.fs_pane = self.fs
            self.view.fs.setWidget(self.fs)
            # connect
            file_manager.on_list_files.connect(self.fs.on_ls)
            file_manager.on_put_file.connect(self.fs.microbit_fs.on_put)
            file_manager.on_delete_file.connect(self.fs.microbit_fs.on_delete)
            file_manager.on_get_file.connect(self.fs.local_fs.on_get)
            file_manager.on_list_fail.connect(self.fs.on_ls_fail)
            file_manager.on_put_fail.connect(self.fs.on_put_fail)
            file_manager.on_delete_fail.connect(self.fs.on_delete_fail)
            file_manager.on_get_fail.connect(self.fs.on_get_fail)
            self.fs.open_file.connect(self.view.open_file)
            self.fs.list_files.connect(file_manager.ls)
            self.fs.microbit_fs.put.connect(file_manager.put)
            self.fs.microbit_fs.delete.connect(file_manager.delete)
            self.fs.microbit_fs.list_files.connect(file_manager.ls)
            self.fs.local_fs.get.connect(file_manager.get)
            self.fs.local_fs.list_files.connect(file_manager.ls)
            self.view.connect_zoom(self.fs)
            return self.fs

        self.file_manager_thread = QThread(self)
        self.file_manager = FileManager(device_port)
        self.file_manager.moveToThread(self.file_manager_thread)
        self.file_manager_thread.started.connect(on_start)
        self.fs = add_filesystem(
            self.workspace_dir(),
            self.file_manager,
            _("Seeed's line of boards"),
        )
        self.fs.set_message.connect(self.editor.show_status_message)
        self.fs.set_warning.connect(self.view.show_message)
        self.file_manager_thread.start()

    def remove_fs(self):
        """
        Remove the file system navigator from the UI.
        """
        self.view.remove_filesystem()
        self.file_manager = None
        self.file_manager_thread = None
        self.fs = None
        ArdupyDeviceFileList.serial = None

    def on_data_flood(self):
        """
        Ensure the Files button is active before the REPL is killed off when
        a data flood of the plotter is detected.
        """
        self.set_buttons(files=True)
        super().on_data_flood()

    def setRunIcon(self, run=True):
        keyword = "run" if run else "stop"
        textHead = "Run" if run else "Stop"
        tooltip = textHead + "your Python script."
        run_slot = self.view.button_bar.slots["run"]
        run_slot.setIcon(load_icon(keyword))
        run_slot.setText(_(textHead))
        run_slot.setToolTip(_(tooltip))

    def checkTerminal(self):
        text = self.view.repl_pane.toPlainText()
        res = re.search(self.terminalKeywords, text[self.curPos:],
                        re.IGNORECASE)
        if res:
            self.checkTerminalTimer.stop()
            self.setRunIcon()
            self.set_buttons(modes=True, run=True)
            self.in_running_script = False

    def on_serialData_write(self, word):
        self.checkSerialWriteTimer.start(250)

    def serialWriteFinish(self):
        self.view.repl_pane.serial.bytesWritten.disconnect(
            self.on_serialData_write)
        self.checkSerialWriteTimer.stop()
        self.set_buttons(modes=False,
                         run=True,
                         files=False,
                         repl=True,
                         plotter=True)
Пример #18
0
class SeeedMode(MicroPythonMode):
    """
    Represents the functionality required for running MicroPython on Seeed's
    line of boards
    """
    name = _('Seeed MicroPython')
    description = _("Use MicroPython on Seeed's line of boards.")
    icon = 'seeed'
    fs = None
    info = Info()
    # There are many boards which use ESP microcontrollers but they often use
    # the same USB / serial chips (which actually define the Vendor ID and
    # Product ID for the connected devices.

    # VID  , PID
    valid_boards = info.board_normal + info.board_boot

    def __init__(self, editor, view):
        super().__init__(editor, view)
        self.invoke = FirmwareUpdater(
            mu_code_path=super().workspace_dir(),  # mu_code/
            confirm=self.__confirm,
            show_status=self.editor.show_status_message,
            show_message_box=self.__show_message_box,
            set_all_button=self.__set_all_button
        )
        self.invoke.info = SeeedMode.info
        self.invoke.start()
        self.view.default_pane = SeeedFileSystemPane
        ArdupyDeviceFileList.info = SeeedMode.info
        LocalFileTree.info = SeeedMode.info
        editor.detect_new_device_handle = \
            self.__asyc_detect_new_device_handle

    def __set_all_button(self, state):
        print('button Enable=' + str(state))
        self.set_buttons(files=state, run=state, repl=state, plotter=state)

    def __confirm(self, flag):
        flag.confirm = QMessageBox.Ok == \
            self.view.show_confirmation(flag.hint, icon='Question')

    def __show_message_box(self, text):
        self.msg = QMessageBox()
        self.msg.setWindowTitle('Hint')
        self.msg.setDefaultButton(self.msg.Ok)
        self.msg.setText(text)
        self.msg.show()

    def __asyc_detect_new_device_handle(self, device_name):
        self.info.has_firmware = False
        self.info.board_id = None
        self.info.board_name = device_name
        available_ports = QSerialPortInfo.availablePorts()

        def match(pvid, ids):
            for valid in ids:
                if pvid == valid:
                    self.info.board_id = str(valid)
                    return True
            return False

        for port in available_ports:
            pvid = (
                port.vendorIdentifier(),
                port.productIdentifier()
            )
            if match(pvid, self.info.board_normal):
                self.invoke.in_bootload_mode = False
                print('detect a normal mode borad')
                break
            if match(pvid, self.info.board_boot):
                self.invoke.in_bootload_mode = True
                print('detect a bootload mode borad')
                break

        self.invoke.detected = True

    def actions(self):
        """
        Return an ordered list of actions provided by this module. An action
        is a name (also used to identify the icon) , description, and handler.
        """
        buttons = [
            {
                'name': 'run',
                'display_name': _('Run'),
                'description': _("Run your code directly on the Seeed's"
                                 " line of boards. via the REPL."),
                'handler': self.run,
                'shortcut': 'F5',
            },
            {
                'name': 'files',
                'display_name': _('Files'),
                'description': _("Access the file system on "
                                 "Seeed's line of boards."),
                'handler': self.toggle_files,
                'shortcut': 'F4',
            },
            {
                'name': 'repl',
                'display_name': _('REPL'),
                'description': _("Use the REPL to live-code on the "
                                 "Seeed's line of boards."),
                'handler': self.toggle_repl,
                'shortcut': 'Ctrl+Shift+I',
            }, ]
        if CHARTS:
            buttons.append({
                'name': 'plotter',
                'display_name': _('Plotter'),
                'description': _('Plot incoming REPL data.'),
                'handler': self.toggle_plotter,
                'shortcut': 'CTRL+Shift+P',
            })
        return buttons

    def api(self):
        """
        Return a list of API specifications to be used by auto-suggest and call
        tips.
        """
        return SHARED_APIS + SEEED_APIS

    def check_firmware_existence(self):
        hint = 'denit! please make sure your board connected to ' + \
            'the computer and there is a Ardupy firmware in your board.'
        if self.info.has_firmware:
            return True
        self.__show_message_box(hint)
        return False

    def toggle_repl(self, event):
        if not self.check_firmware_existence():
            return
        if self.fs is None:
            if self.repl:
                # Remove REPL
                super().toggle_repl(event)
                self.set_buttons(files=True)
            elif not (self.repl):
                # Add REPL
                super().toggle_repl(event)
                if self.repl:
                    self.set_buttons(files=False)
        else:
            message = _("REPL and file system cannot work at the same time.")
            information = _("The REPL and file system both use the same USB "
                            "serial connection. Only one can be active "
                            "at any time. Toggle the file system off and "
                            "try again.")
            self.view.show_message(message, information)

    def toggle_plotter(self, event):
        """
        Check for the existence of the file pane before toggling plotter.
        """
        if not self.check_firmware_existence():
            return
        if self.fs is None:
            super().toggle_plotter(event)
            if self.plotter:
                self.set_buttons(files=False)
            elif not (self.repl or self.plotter):
                self.set_buttons(files=True)
        else:
            message = _("The plotter and file system cannot work at the same "
                        "time.")
            information = _("The plotter and file system both use the same "
                            "USB serial connection. Only one can be active "
                            "at any time. Toggle the file system off and "
                            "try again.")
            self.view.show_message(message, information)

    def run(self):
        """
        Takes the currently active tab, compiles the Python script therein into
        a hex file and flashes it all onto the connected device.
        """
        """
        if self.repl:
            message = _("Flashing cannot be performed at the same time as the "
                        "REPL is active.")
            information = _("File transfers use the same "
                            "USB serial connection as the REPL. Toggle the "
                            "REPL off and try again.")
            self.view.show_message(message, information)
            return
        """
        logger.info('Running script.')
        # Grab the Python script.
        tab = self.view.current_tab
        if tab is None:
            # There is no active text editor.
            message = _("Cannot run anything without any active editor tabs.")
            information = _("Running transfers the content of the current tab"
                            " onto the device. It seems like you don't have "
                            " any tabs open.")
            self.view.show_message(message, information)
            return
        python_script = tab.text().split('\n')
        if not self.repl:
            self.toggle_repl(None)
        if self.repl:
            self.view.repl_pane.send_commands(python_script)

    def toggle_files(self, event):
        """
        Check for the existence of the REPL or plotter before toggling the file
        system navigator for the MicroPython device on or off.
        """
        if not self.check_firmware_existence():
            return
        if self.repl:
            message = _("File system cannot work at the same time as the "
                        "REPL or plotter.")
            information = _("The file system and the REPL and plotter "
                            "use the same USB serial connection. Toggle the "
                            "REPL and plotter off and try again.")
            self.view.show_message(message, information)
        else:
            if self.fs is None:
                self.add_fs()
                if self.fs:
                    logger.info('Toggle filesystem on.')
                    self.set_buttons(run=False, repl=False, plotter=False)
            else:
                self.remove_fs()
                logger.info('Toggle filesystem off.')
                self.set_buttons(run=True, repl=True, plotter=True)

    def add_fs(self):
        """
        Add the file system navigator to the UI.
        """

        # Find serial port boards is connected to
        device_port, serial_number = self.find_device()

        # Check for MicroPython device
        if not device_port:
            message = _("Could not find an attached Seeed's line of boards.")
            information = _("Please make sure the device is plugged "
                            "into this computer.\n\nThe device must "
                            "have MicroPython flashed onto it before "
                            "the file system will work.\n\n"
                            "Finally, press the device's reset button "
                            "and wait a few seconds before trying "
                            "again.")
            self.view.show_message(message, information)
            return

        def on_start():
            self.file_manager.on_start()
            try:
                ArdupyDeviceFileList.serial = self.file_manager.serial
            except Exception as ex:
                print(ex)

        self.file_manager_thread = QThread(self)
        self.file_manager = FileManager(device_port)
        self.file_manager.moveToThread(self.file_manager_thread)
        self.file_manager_thread.started.connect(on_start)
        self.fs = self.view.add_filesystem(self.workspace_dir(),
                                           self.file_manager,
                                           _("Seeed's line of boards"))
        self.fs.set_message.connect(self.editor.show_status_message)
        self.fs.set_warning.connect(self.view.show_message)
        self.file_manager_thread.start()

    def remove_fs(self):
        """
        Remove the file system navigator from the UI.
        """
        self.view.remove_filesystem()
        self.file_manager = None
        self.file_manager_thread = None
        self.fs = None
        ArdupyDeviceFileList.serial = None

    def on_data_flood(self):
        """
        Ensure the Files button is active before the REPL is killed off when
        a data flood of the plotter is detected.
        """
        self.set_buttons(files=True)
        super().on_data_flood()