예제 #1
0
 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
예제 #2
0
    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)
예제 #3
0
파일: microbit.py 프로젝트: opt9/mu
    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.warn('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)