src/eric7/MicroPython/MicrobitDevices.py

branch
eric7
changeset 9756
9854647c8c5c
parent 9755
1a09700229e7
child 9757
ab6e87f6f1c4
--- a/src/eric7/MicroPython/MicrobitDevices.py	Sun Feb 12 18:11:20 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,653 +0,0 @@
-# -*- coding: utf-8 -*-
-
-# Copyright (c) 2019 - 2023 Detlev Offenbach <detlev@die-offenbachs.de>
-#
-
-"""
-Module implementing the device interface class for BBC micro:bit and
-Calliope mini boards.
-"""
-
-import contextlib
-import os
-import shutil
-
-from PyQt6.QtCore import QStandardPaths, QUrl, pyqtSlot
-from PyQt6.QtNetwork import QNetworkRequest
-from PyQt6.QtWidgets import QInputDialog, QLineEdit, QMenu
-
-from eric7 import Globals, Preferences
-from eric7.EricWidgets import EricFileDialog, EricMessageBox
-from eric7.EricWidgets.EricApplication import ericApp
-from eric7.SystemUtilities import FileSystemUtilities
-
-from .MicroPythonDevices import FirmwareGithubUrls, MicroPythonDevice
-from .MicroPythonWidget import HAS_QTCHART
-
-
-class MicrobitDevice(MicroPythonDevice):
-    """
-    Class implementing the device for BBC micro:bit and Calliope mini boards.
-    """
-
-    def __init__(self, microPythonWidget, deviceType, serialNumber, parent=None):
-        """
-        Constructor
-
-        @param microPythonWidget reference to the main MicroPython widget
-        @type MicroPythonWidget
-        @param deviceType type of the device
-        @type str
-        @param serialNumber serial number of the board
-        @type str
-        @param parent reference to the parent object
-        @type QObject
-        """
-        super().__init__(microPythonWidget, deviceType, parent)
-
-        self.__boardId = 0  # illegal ID
-        if serialNumber:
-            with contextlib.suppress(ValueError):
-                self.__boardId = int(serialNumber[:4], 16)
-
-        self.__createMicrobitMenu()
-
-    def setButtons(self):
-        """
-        Public method to enable the supported action buttons.
-        """
-        super().setButtons()
-        self.microPython.setActionButtons(
-            run=True, repl=True, files=True, chart=HAS_QTCHART
-        )
-
-    def forceInterrupt(self):
-        """
-        Public method to determine the need for an interrupt when opening the
-        serial connection.
-
-        @return flag indicating an interrupt is needed
-        @rtype bool
-        """
-        return True
-
-    def deviceName(self):
-        """
-        Public method to get the name of the device.
-
-        @return name of the device
-        @rtype str
-        """
-        if self.getDeviceType() == "bbc_microbit":
-            # BBC micro:bit
-            return self.tr("BBC micro:bit")
-        else:
-            # Calliope mini
-            return self.tr("Calliope mini")
-
-    def canStartRepl(self):
-        """
-        Public method to determine, if a REPL can be started.
-
-        @return tuple containing a flag indicating it is safe to start a REPL
-            and a reason why it cannot.
-        @rtype tuple of (bool, str)
-        """
-        return True, ""
-
-    def canStartPlotter(self):
-        """
-        Public method to determine, if a Plotter can be started.
-
-        @return tuple containing a flag indicating it is safe to start a
-            Plotter and a reason why it cannot.
-        @rtype tuple of (bool, str)
-        """
-        return True, ""
-
-    def canRunScript(self):
-        """
-        Public method to determine, if a script can be executed.
-
-        @return tuple containing a flag indicating it is safe to start a
-            Plotter and a reason why it cannot.
-        @rtype tuple of (bool, str)
-        """
-        return True, ""
-
-    def runScript(self, script):
-        """
-        Public method to run the given Python script.
-
-        @param script script to be executed
-        @type str
-        """
-        pythonScript = script.split("\n")
-        self.sendCommands(pythonScript)
-
-    def canStartFileManager(self):
-        """
-        Public method to determine, if a File Manager can be started.
-
-        @return tuple containing a flag indicating it is safe to start a
-            File Manager and a reason why it cannot.
-        @rtype tuple of (bool, str)
-        """
-        return True, ""
-
-    def hasTimeCommands(self):
-        """
-        Public method to check, if the device supports time commands.
-
-        The default returns True.
-
-        @return flag indicating support for time commands
-        @rtype bool
-        """
-        if (
-            self.microPython.isConnected()
-            and self.checkDeviceData()
-            and self._deviceData["mpy_name"] == "circuitpython"
-        ):
-            return True
-
-        return False
-
-    def __isMicroBitV1(self):
-        """
-        Private method to check, if the device is a BBC micro:bit v1.
-
-        @return falg indicating a BBC micro:bit v1
-        @rtype bool
-        """
-        return self.__boardId in (0x9900, 0x9901)
-
-    def __isMicroBitV2(self):
-        """
-        Private method to check, if the device is a BBC micro:bit v2.
-
-        @return falg indicating a BBC micro:bit v2
-        @rtype bool
-        """
-        return self.__boardId in (0x9903, 0x9904, 0x9905, 0x9906)
-
-    def __isCalliope(self):
-        """
-        Private method to check, if the device is a Calliope mini.
-
-        @return flag indicating a Calliope mini
-        @rtype bool
-        """
-        return self.__boardId in (0x12A0,)
-
-    def __createMicrobitMenu(self):
-        """
-        Private method to create the microbit submenu.
-        """
-        self.__microbitMenu = QMenu(self.tr("BBC micro:bit/Calliope Functions"))
-
-        self.__showMpyAct = self.__microbitMenu.addAction(
-            self.tr("Show MicroPython Versions"), self.__showFirmwareVersions
-        )
-        self.__microbitMenu.addSeparator()
-        self.__flashMpyAct = self.__microbitMenu.addAction(
-            self.tr("Flash MicroPython"), self.__flashMicroPython
-        )
-        self.__flashDAPLinkAct = self.__microbitMenu.addAction(
-            self.tr("Flash Firmware"), lambda: self.__flashMicroPython(firmware=True)
-        )
-        self.__microbitMenu.addSeparator()
-        self.__saveScripAct = self.__microbitMenu.addAction(
-            self.tr("Save Script"), self.__saveScriptToDevice
-        )
-        self.__saveScripAct.setToolTip(
-            self.tr("Save the current script to the selected device")
-        )
-        self.__saveMainScriptAct = self.__microbitMenu.addAction(
-            self.tr("Save Script as 'main.py'"), self.__saveMain
-        )
-        self.__saveMainScriptAct.setToolTip(
-            self.tr("Save the current script as 'main.py' on the connected device")
-        )
-        self.__microbitMenu.addSeparator()
-        self.__resetAct = self.__microbitMenu.addAction(
-            self.tr("Reset {0}").format(self.deviceName()), self.__resetDevice
-        )
-
-    def addDeviceMenuEntries(self, menu):
-        """
-        Public method to add device specific entries to the given menu.
-
-        @param menu reference to the context menu
-        @type QMenu
-        """
-        connected = self.microPython.isConnected()
-        linkConnected = self.microPython.isLinkConnected()
-
-        self.__showMpyAct.setEnabled(connected and self.getDeviceType() != "calliope")
-        self.__flashMpyAct.setEnabled(not linkConnected)
-        self.__flashDAPLinkAct.setEnabled(not linkConnected)
-        self.__saveScripAct.setEnabled(connected)
-        self.__saveMainScriptAct.setEnabled(connected)
-        self.__resetAct.setEnabled(connected)
-
-        menu.addMenu(self.__microbitMenu)
-
-    def hasFlashMenuEntry(self):
-        """
-        Public method to check, if the device has its own flash menu entry.
-
-        @return flag indicating a specific flash menu entry
-        @rtype bool
-        """
-        return True
-
-    @pyqtSlot()
-    def __flashMicroPython(self, firmware=False):
-        """
-        Private slot to flash MicroPython or the DAPLink firmware to the
-        device.
-
-        @param firmware flag indicating to flash the DAPLink firmware
-        @type bool
-        """
-        # Attempts to find the path on the file system that represents the
-        # plugged in micro:bit board. To flash the DAPLink firmware, it must be
-        # in maintenance mode, for MicroPython in standard mode.
-        if self.getDeviceType() == "bbc_microbit":
-            # BBC micro:bit
-            if firmware:
-                deviceDirectories = FileSystemUtilities.findVolume(
-                    "MAINTENANCE", findAll=True
-                )
-            else:
-                deviceDirectories = FileSystemUtilities.findVolume(
-                    "MICROBIT", findAll=True
-                )
-        else:
-            # Calliope mini
-            if firmware:
-                deviceDirectories = FileSystemUtilities.findVolume(
-                    "MAINTENANCE", findAll=True
-                )
-            else:
-                deviceDirectories = FileSystemUtilities.findVolume("MINI", findAll=True)
-        if len(deviceDirectories) == 0:
-            if self.getDeviceType() == "bbc_microbit":
-                # BBC micro:bit is not ready or not mounted
-                if firmware:
-                    EricMessageBox.critical(
-                        self.microPython,
-                        self.tr("Flash MicroPython/Firmware"),
-                        self.tr(
-                            "<p>The BBC micro:bit is not ready for flashing"
-                            " the DAPLink firmware. Follow these"
-                            " instructions. </p>"
-                            "<ul>"
-                            "<li>unplug USB cable and any batteries</li>"
-                            "<li>keep RESET button pressed and plug USB cable"
-                            " back in</li>"
-                            "<li>a drive called MAINTENANCE should be"
-                            " available</li>"
-                            "</ul>"
-                            "<p>See the "
-                            '<a href="https://microbit.org/guide/firmware/">'
-                            "micro:bit web site</a> for details.</p>"
-                        ),
-                    )
-                else:
-                    EricMessageBox.critical(
-                        self.microPython,
-                        self.tr("Flash MicroPython/Firmware"),
-                        self.tr(
-                            "<p>The BBC micro:bit is not ready for flashing"
-                            " the MicroPython firmware. Please make sure,"
-                            " that a drive called MICROBIT is available."
-                            "</p>"
-                        ),
-                    )
-            else:
-                # Calliope mini is not ready or not mounted
-                if firmware:
-                    EricMessageBox.critical(
-                        self.microPython,
-                        self.tr("Flash MicroPython/Firmware"),
-                        self.tr(
-                            '<p>The "Calliope mini" is not ready for flashing'
-                            " the DAPLink firmware. Follow these"
-                            " instructions. </p>"
-                            "<ul>"
-                            "<li>unplug USB cable and any batteries</li>"
-                            "<li>keep RESET button pressed an plug USB cable"
-                            " back in</li>"
-                            "<li>a drive called MAINTENANCE should be"
-                            " available</li>"
-                            "</ul>"
-                        ),
-                    )
-                else:
-                    EricMessageBox.critical(
-                        self.microPython,
-                        self.tr("Flash MicroPython/Firmware"),
-                        self.tr(
-                            '<p>The "Calliope mini" is not ready for flashing'
-                            " the MicroPython firmware. Please make sure,"
-                            " that a drive called MINI is available."
-                            "</p>"
-                        ),
-                    )
-        elif len(deviceDirectories) == 1:
-            downloadsPath = QStandardPaths.standardLocations(
-                QStandardPaths.StandardLocation.DownloadLocation
-            )[0]
-            firmware = EricFileDialog.getOpenFileName(
-                self.microPython,
-                self.tr("Flash MicroPython/Firmware"),
-                downloadsPath,
-                self.tr("MicroPython/Firmware Files (*.hex *.bin);;All Files (*)"),
-            )
-            if firmware and os.path.exists(firmware):
-                shutil.copy2(firmware, deviceDirectories[0])
-        else:
-            EricMessageBox.warning(
-                self,
-                self.tr("Flash MicroPython/Firmware"),
-                self.tr(
-                    "There are multiple devices ready for flashing."
-                    " Please make sure, that only one device is prepared."
-                ),
-            )
-
-    @pyqtSlot()
-    def __showFirmwareVersions(self):
-        """
-        Private slot to show the firmware version of the connected device and the
-        available firmware version.
-        """
-        if self.microPython.isConnected() and self.checkDeviceData():
-            if self._deviceData["mpy_name"] not in ("micropython", "circuitpython"):
-                EricMessageBox.critical(
-                    None,
-                    self.tr("Show MicroPython Versions"),
-                    self.tr(
-                        """The firmware of the connected device cannot be"""
-                        """ determined or the board does not run MicroPython"""
-                        """ or CircuitPython. Aborting..."""
-                    ),
-                )
-            else:
-                if self.getDeviceType() == "bbc_microbit":
-                    if self._deviceData["mpy_name"] == "micropython":
-                        if self.__isMicroBitV1():
-                            url = QUrl(FirmwareGithubUrls["microbit_v1"])
-                        elif self.__isMicroBitV2():
-                            url = QUrl(FirmwareGithubUrls["microbit_v2"])
-                        else:
-                            EricMessageBox.critical(
-                                None,
-                                self.tr("Show MicroPython Versions"),
-                                self.tr(
-                                    """<p>The BBC micro:bit generation cannot be"""
-                                    """ determined. Aborting...</p>"""
-                                ),
-                            )
-                            return
-                    elif self._deviceData["mpy_name"] == "circuitpython":
-                        url = QUrl(FirmwareGithubUrls["circuitpython"])
-                else:
-                    EricMessageBox.critical(
-                        None,
-                        self.tr("Show MicroPython Versions"),
-                        self.tr(
-                            """<p>The firmware URL for the device type <b>{0}</b>"""
-                            """ is not known. Aborting...</p>"""
-                        ).format(self.getDeviceType()),
-                    )
-                    return
-
-                ui = ericApp().getObject("UserInterface")
-                request = QNetworkRequest(url)
-                reply = ui.networkAccessManager().head(request)
-                reply.finished.connect(lambda: self.__firmwareVersionResponse(reply))
-
-    def __firmwareVersionResponse(self, reply):
-        """
-        Private method handling the response of the latest version request.
-
-        @param reply reference to the reply object
-        @type QNetworkReply
-        """
-        latestUrl = reply.url().toString()
-        tag = latestUrl.rsplit("/", 1)[-1]
-        while tag and not tag[0].isdecimal():
-            # get rid of leading non-decimal characters
-            tag = tag[1:]
-        latestVersion = Globals.versionToTuple(tag)
-
-        if self._deviceData["release"] == "unknown":
-            currentVersionStr = self.tr("unknown")
-            currentVersion = (0, 0, 0)
-        else:
-            currentVersionStr = self._deviceData["release"]
-            currentVersion = Globals.versionToTuple(currentVersionStr)
-
-        if self._deviceData["mpy_name"] == "circuitpython":
-            kind = "CircuitPython"
-            microbitVersion = "2"  # only v2 device can run CircuitPython
-        elif self._deviceData["mpy_name"] == "micropython":
-            kind = "MicroPython"
-            if self.__isMicroBitV1():
-                microbitVersion = "1"
-            elif self.__isMicroBitV2():
-                microbitVersion = "2"
-        else:
-            kind = self.tr("Firmware")
-            microbitVersion = "?"
-
-        msg = self.tr(
-            "<h4>{0} Version Information<br/>"
-            "(BBC micro:bit v{1})</h4>"
-            "<table>"
-            "<tr><td>Installed:</td><td>{2}</td></tr>"
-            "<tr><td>Available:</td><td>{3}</td></tr>"
-            "</table>"
-        ).format(kind, microbitVersion, currentVersionStr, tag)
-        if currentVersion < latestVersion:
-            msg += self.tr("<p><b>Update available!</b></p>")
-
-        EricMessageBox.information(
-            None,
-            self.tr("{0} Version").format(kind),
-            msg,
-        )
-
-    @pyqtSlot()
-    def __saveMain(self):
-        """
-        Private slot to copy the current script as 'main.py' onto the
-        connected device.
-        """
-        self.__saveScriptToDevice("main.py")
-
-    @pyqtSlot()
-    def __saveScriptToDevice(self, scriptName=""):
-        """
-        Private method to save the current script onto the connected
-        device.
-
-        @param scriptName name of the file on the device
-        @type str
-        """
-        aw = ericApp().getObject("ViewManager").activeWindow()
-        if not aw:
-            return
-
-        title = (
-            self.tr("Save Script as '{0}'").format(scriptName)
-            if scriptName
-            else self.tr("Save Script")
-        )
-
-        if not (aw.isPyFile() or aw.isMicroPythonFile()):
-            yes = EricMessageBox.yesNo(
-                self.microPython,
-                title,
-                self.tr(
-                    """The current editor does not contain a Python"""
-                    """ script. Write it anyway?"""
-                ),
-            )
-            if not yes:
-                return
-
-        script = aw.text().strip()
-        if not script:
-            EricMessageBox.warning(
-                self.microPython, title, self.tr("""The script is empty. Aborting.""")
-            )
-            return
-
-        if not scriptName:
-            scriptName = os.path.basename(aw.getFileName())
-            scriptName, ok = QInputDialog.getText(
-                self.microPython,
-                title,
-                self.tr("Enter a file name on the device:"),
-                QLineEdit.EchoMode.Normal,
-                scriptName,
-            )
-            if not ok or not bool(scriptName):
-                return
-
-            title = self.tr("Save Script as '{0}'").format(scriptName)
-
-        commands = [
-            "fd = open('{0}', 'wb')".format(scriptName),
-            "f = fd.write",
-        ]
-        for line in script.splitlines():
-            commands.append("f(" + repr(line + "\n") + ")")
-        commands.append("fd.close()")
-        out, err = self.microPython.commandsInterface().execute(commands)
-        if err:
-            EricMessageBox.critical(
-                self.microPython,
-                title,
-                self.tr(
-                    """<p>The script could not be saved to the"""
-                    """ device.</p><p>Reason: {0}</p>"""
-                ).format(err.decode("utf-8")),
-            )
-
-        # reset the device
-        self.__resetDevice()
-
-    @pyqtSlot()
-    def __resetDevice(self):
-        """
-        Private slot to reset the connected device.
-        """
-        if self.getDeviceType() == "bbc_microbit":
-            # BBC micro:bit
-            self.microPython.commandsInterface().execute(
-                [
-                    "import microbit",
-                    "microbit.reset()",
-                ]
-            )
-        else:
-            # Calliope mini
-            self.microPython.commandsInterface().execute(
-                [
-                    "import calliope_mini",
-                    "calliope_mini.reset()",
-                ]
-            )
-
-    def getDocumentationUrl(self):
-        """
-        Public method to get the device documentation URL.
-
-        @return documentation URL of the device
-        @rtype str
-        """
-        if self.getDeviceType() == "bbc_microbit":
-            # BBC micro:bit
-            if self._deviceData and self._deviceData["mpy_name"] == "circuitpython":
-                return Preferences.getMicroPython("CircuitPythonDocuUrl")
-            else:
-                return Preferences.getMicroPython("MicrobitDocuUrl")
-        else:
-            # Calliope mini
-            return Preferences.getMicroPython("CalliopeDocuUrl")
-
-    def getDownloadMenuEntries(self):
-        """
-        Public method to retrieve the entries for the downloads menu.
-
-        @return list of tuples with menu text and URL to be opened for each
-            entry
-        @rtype list of tuple of (str, str)
-        """
-        if self.getDeviceType() == "bbc_microbit":
-            if self.__isMicroBitV1():
-                return [
-                    (
-                        self.tr("MicroPython Firmware for BBC micro:bit V1"),
-                        Preferences.getMicroPython("MicrobitMicroPythonUrl"),
-                    ),
-                    (
-                        self.tr("DAPLink Firmware"),
-                        Preferences.getMicroPython("MicrobitFirmwareUrl"),
-                    ),
-                ]
-            elif self.__isMicroBitV2():
-                return [
-                    (
-                        self.tr("MicroPython Firmware for BBC micro:bit V2"),
-                        Preferences.getMicroPython("MicrobitV2MicroPythonUrl"),
-                    ),
-                    (
-                        self.tr("CircuitPython Firmware for BBC micro:bit V2"),
-                        "https://circuitpython.org/board/microbit_v2/",
-                    ),
-                    (
-                        self.tr("DAPLink Firmware"),
-                        Preferences.getMicroPython("MicrobitFirmwareUrl"),
-                    ),
-                ]
-            else:
-                return []
-        else:
-            return [
-                (
-                    self.tr("MicroPython Firmware"),
-                    Preferences.getMicroPython("CalliopeMicroPythonUrl"),
-                ),
-                (
-                    self.tr("DAPLink Firmware"),
-                    Preferences.getMicroPython("CalliopeDAPLinkUrl"),
-                ),
-            ]
-
-
-def createDevice(microPythonWidget, deviceType, vid, pid, boardName, serialNumber):
-    """
-    Function to instantiate a MicroPython device object.
-
-    @param microPythonWidget reference to the main MicroPython widget
-    @type MicroPythonWidget
-    @param deviceType device type assigned to this device interface
-    @type str
-    @param vid vendor ID
-    @type int
-    @param pid product ID
-    @type int
-    @param boardName name of the board
-    @type str
-    @param serialNumber serial number of the board
-    @type str
-    @return reference to the instantiated device object
-    @rtype MicrobitDevice
-    """
-    return MicrobitDevice(microPythonWidget, deviceType, serialNumber)

eric ide

mercurial