src/eric7/MicroPython/CircuitPythonDevices.py

branch
eric7
changeset 9756
9854647c8c5c
parent 9755
1a09700229e7
child 9757
ab6e87f6f1c4
--- a/src/eric7/MicroPython/CircuitPythonDevices.py	Sun Feb 12 18:11:20 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,533 +0,0 @@
-# -*- coding: utf-8 -*-
-
-# Copyright (c) 2019 - 2023 Detlev Offenbach <detlev@die-offenbachs.de>
-#
-
-"""
-Module implementing the device interface class for CircuitPython boards.
-"""
-
-import os
-import shutil
-
-from PyQt6.QtCore import QProcess, QUrl, pyqtSlot
-from PyQt6.QtNetwork import QNetworkRequest
-from PyQt6.QtWidgets import QMenu
-
-from eric7 import Globals, Preferences
-from eric7.EricWidgets import EricFileDialog, EricMessageBox
-from eric7.EricWidgets.EricApplication import ericApp
-from eric7.SystemUtilities import FileSystemUtilities
-
-from .CircuitPythonUpdater.CircuitPythonUpdaterInterface import (
-    CircuitPythonUpdaterInterface,
-    isCircupAvailable,
-)
-from .MicroPythonDevices import FirmwareGithubUrls, MicroPythonDevice
-from .MicroPythonWidget import HAS_QTCHART
-
-
-class CircuitPythonDevice(MicroPythonDevice):
-    """
-    Class implementing the device for CircuitPython boards.
-    """
-
-    DeviceVolumeName = "CIRCUITPY"
-
-    def __init__(self, microPythonWidget, deviceType, boardName, parent=None):
-        """
-        Constructor
-
-        @param microPythonWidget reference to the main MicroPython widget
-        @type MicroPythonWidget
-        @param deviceType device type assigned to this device interface
-        @type str
-        @param boardName name of the board
-        @type str
-        @param parent reference to the parent object
-        @type QObject
-        """
-        super().__init__(microPythonWidget, deviceType, parent)
-
-        self.__boardName = boardName
-        self.__workspace = self.__findWorkspace()
-
-        self.__updater = CircuitPythonUpdaterInterface(self)
-
-        self.__createCPyMenu()
-
-    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
-        )
-
-        if self.__deviceVolumeMounted():
-            self.microPython.setActionButtons(open=True, save=True)
-
-    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 False
-
-    def deviceName(self):
-        """
-        Public method to get the name of the device.
-
-        @return name of the device
-        @rtype str
-        """
-        return self.tr("CircuitPython")
-
-    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 supportsLocalFileAccess(self):
-        """
-        Public method to indicate file access via a local directory.
-
-        @return flag indicating file access via local directory
-        @rtype bool
-        """
-        return self.__deviceVolumeMounted()
-
-    def __deviceVolumeMounted(self):
-        """
-        Private method to check, if the device volume is mounted.
-
-        @return flag indicated a mounted device
-        @rtype bool
-        """
-        if self.__workspace and not os.path.exists(self.__workspace):
-            self.__workspace = ""  # reset
-
-        return self.DeviceVolumeName in self.getWorkspace(silent=True)
-
-    def __findDeviceDirectories(self, directories):
-        """
-        Private method to find the device directories associated with the
-        current board name.
-
-        @param directories list of directories to be checked
-        @type list of str
-        @return list of associated directories
-        @rtype list of str
-        """
-        boardDirectories = []
-        for directory in directories:
-            bootFile = os.path.join(directory, "boot_out.txt")
-            if os.path.exists(bootFile):
-                with open(bootFile, "r") as f:
-                    line = f.readline()
-                if self.__boardName in line:
-                    boardDirectories.append(directory)
-
-        return boardDirectories
-
-    def __findWorkspace(self, silent=False):
-        """
-        Private method to find the workspace directory.
-
-        @param silent flag indicating silent operations
-        @type bool
-        @return workspace directory used for saving files
-        @rtype str
-        """
-        # Attempts to find the paths on the filesystem that represents the
-        # plugged in CIRCUITPY boards.
-        deviceDirectories = FileSystemUtilities.findVolume(
-            self.DeviceVolumeName, findAll=True
-        )
-
-        if deviceDirectories:
-            if len(deviceDirectories) == 1:
-                return deviceDirectories[0]
-            else:
-                boardDirectories = self.__findDeviceDirectories(deviceDirectories)
-                if len(boardDirectories) == 1:
-                    return boardDirectories[0]
-                elif len(boardDirectories) > 1:
-                    return self.selectDeviceDirectory(boardDirectories)
-                else:
-                    return self.selectDeviceDirectory(deviceDirectories)
-        else:
-            # return the default workspace and give the user a warning (unless
-            # silent mode is selected)
-            if not silent:
-                EricMessageBox.warning(
-                    self.microPython,
-                    self.tr("Workspace Directory"),
-                    self.tr(
-                        "Python files for CircuitPython can be edited in"
-                        " place, if the device volume is locally"
-                        " available. Such a volume was not found. In"
-                        " place editing will not be available."
-                    ),
-                )
-
-            return super().getWorkspace()
-
-    def getWorkspace(self, silent=False):
-        """
-        Public method to get the workspace directory.
-
-        @param silent flag indicating silent operations
-        @type bool
-        @return workspace directory used for saving files
-        @rtype str
-        """
-        if self.__workspace:
-            # return cached entry
-            return self.__workspace
-        else:
-            self.__workspace = self.__findWorkspace(silent=silent)
-            return self.__workspace
-
-    def __createCPyMenu(self):
-        """
-        Private method to create the CircuitPython submenu.
-        """
-        self.__libraryMenu = QMenu(self.tr("Library Management"))
-        self.__libraryMenu.aboutToShow.connect(self.__aboutToShowLibraryMenu)
-        self.__libraryMenu.setTearOffEnabled(True)
-
-        self.__cpyMenu = QMenu(self.tr("CircuitPython Functions"))
-
-        self.__cpyMenu.addAction(
-            self.tr("Show CircuitPython Versions"), self.__showCircuitPythonVersions
-        )
-        self.__cpyMenu.addSeparator()
-
-        lBoardName = self.microPython.getCurrentBoard().lower()
-        if "teensy" in lBoardName:
-            # Teensy 4.0 and 4.1 don't support UF2 flashing
-            self.__cpyMenu.addAction(
-                self.tr("CircuitPython Flash Instructions"),
-                self.__showTeensyFlashInstructions,
-            )
-            self.__flashCpyAct = self.__cpyMenu.addAction(
-                self.tr("Flash CircuitPython Firmware"), self.__startTeensyLoader
-            )
-            self.__flashCpyAct.setToolTip(
-                self.tr(
-                    "Start the 'Teensy Loader' application to flash the Teensy device."
-                )
-            )
-        else:
-            self.__flashCpyAct = self.__cpyMenu.addAction(
-                self.tr("Flash CircuitPython Firmware"), self.__flashCircuitPython
-            )
-        self.__cpyMenu.addSeparator()
-        self.__cpyMenu.addMenu(self.__libraryMenu)
-
-    def addDeviceMenuEntries(self, menu):
-        """
-        Public method to add device specific entries to the given menu.
-
-        @param menu reference to the context menu
-        @type QMenu
-        """
-        linkConnected = self.microPython.isLinkConnected()
-
-        self.__flashCpyAct.setEnabled(not linkConnected)
-
-        menu.addMenu(self.__cpyMenu)
-
-    @pyqtSlot()
-    def __aboutToShowLibraryMenu(self):
-        """
-        Private slot to populate the 'Library Management' menu.
-        """
-        self.__libraryMenu.clear()
-
-        if isCircupAvailable():
-            self.__updater.populateMenu(self.__libraryMenu)
-        else:
-            act = self.__libraryMenu.addAction(
-                self.tr("Install Library Files"), self.__installLibraryFiles
-            )
-            act.setEnabled(self.__deviceVolumeMounted())
-            act = self.__libraryMenu.addAction(
-                self.tr("Install Library Package"),
-                lambda: self.__installLibraryFiles(packageMode=True),
-            )
-            act.setEnabled(self.__deviceVolumeMounted())
-            self.__libraryMenu.addSeparator()
-            self.__libraryMenu.addAction(
-                self.tr("Install 'circup' Package"),
-                self.__updater.installCircup,
-            )
-
-    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 __flashCircuitPython(self):
-        """
-        Private slot to flash a CircuitPython firmware to a device supporting UF2.
-        """
-        from .UF2FlashDialog import UF2FlashDialog
-
-        dlg = UF2FlashDialog(boardType="circuitpython")
-        dlg.exec()
-
-    def __showTeensyFlashInstructions(self):
-        """
-        Private method to show a message box because Teensy does not support
-        the UF2 bootloader yet.
-        """
-        EricMessageBox.information(
-            self.microPython,
-            self.tr("Flash CircuitPython Firmware"),
-            self.tr(
-                """<p>Teensy 4.0 and Teensy 4.1 do not support the UF2"""
-                """ bootloader. Please use the 'Teensy Loader'"""
-                """ application to flash CircuitPython. Make sure you"""
-                """ downloaded the CircuitPython .hex file.</p>"""
-                """<p>See <a href="{0}">the PJRC Teensy web site</a>"""
-                """ for details.</p>"""
-            ).format("https://www.pjrc.com/teensy/loader.html"),
-        )
-
-    def __startTeensyLoader(self):
-        """
-        Private method to start the 'Teensy Loader' application.
-
-        Note: The application must be accessible via the application search path.
-        """
-        ok, _ = QProcess.startDetached("teensy")
-        if not ok:
-            EricMessageBox.warning(
-                self.microPython,
-                self.tr("Start 'Teensy Loader'"),
-                self.tr(
-                    """<p>The 'Teensy Loader' application <b>teensy</b> could not"""
-                    """ be started. Ensure it is in the application search path or"""
-                    """ start it manually.</p>"""
-                ),
-            )
-
-    @pyqtSlot()
-    def __showCircuitPythonVersions(self):
-        """
-        Private slot to show the CircuitPython version of a connected device and
-        the latest available one (from Github).
-        """
-        ui = ericApp().getObject("UserInterface")
-        request = QNetworkRequest(QUrl(FirmwareGithubUrls["circuitpython"]))
-        reply = ui.networkAccessManager().head(request)
-        reply.finished.connect(lambda: self.__cpyVersionResponse(reply))
-
-    def __cpyVersionResponse(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]
-        latestVersion = Globals.versionToTuple(tag)
-
-        cpyVersionStr = self.tr("unknown")
-        cpyVersion = (0, 0, 0)
-        if self.supportsLocalFileAccess():
-            bootFile = os.path.join(self.getWorkspace(), "boot_out.txt")
-            if os.path.exists(bootFile):
-                with open(bootFile, "r") as f:
-                    line = f.readline()
-                cpyVersionStr = line.split(";")[0].split()[2]
-                cpyVersion = Globals.versionToTuple(cpyVersionStr)
-        if (
-            cpyVersion == (0, 0, 0)
-            and self._deviceData
-            and self._deviceData["mpy_version"] != "unknown"
-        ):
-            # drive is not mounted or 'boot_out.txt' is missing but the device
-            # is connected via the serial console
-            cpyVersionStr = self._deviceData["mpy_version"]
-            cpyVersion = Globals.versionToTuple(cpyVersionStr)
-
-        msg = self.tr(
-            "<h4>CircuitPython Version Information</h4>"
-            "<table>"
-            "<tr><td>Installed:</td><td>{0}</td></tr>"
-            "<tr><td>Available:</td><td>{1}</td></tr>"
-            "</table>"
-        ).format(cpyVersionStr, tag)
-        if cpyVersion < latestVersion and cpyVersion != (0, 0, 0):
-            msg += self.tr("<p><b>Update available!</b></p>")
-
-        EricMessageBox.information(
-            None,
-            self.tr("CircuitPython Version"),
-            msg,
-        )
-
-    @pyqtSlot()
-    def __installLibraryFiles(self, packageMode=False):
-        """
-        Private slot to install Python files into the onboard library.
-
-        @param packageMode flag indicating to install a library package
-            (defaults to False)
-        @type bool (optional)
-        """
-        title = (
-            self.tr("Install Library Package")
-            if packageMode
-            else self.tr("Install Library Files")
-        )
-        if not self.__deviceVolumeMounted():
-            EricMessageBox.critical(
-                self.microPython,
-                title,
-                self.tr(
-                    """The device volume "<b>{0}</b>" is not available."""
-                    """ Ensure it is mounted properly and try again."""
-                ),
-            )
-            return
-
-        target = os.path.join(self.getWorkspace(), "lib")
-        # ensure that the library directory exists on the device
-        if not os.path.isdir(target):
-            os.makedirs(target)
-
-        if packageMode:
-            libraryPackage = EricFileDialog.getExistingDirectory(
-                self.microPython,
-                title,
-                os.path.expanduser("~"),
-                EricFileDialog.Option(0),
-            )
-            if libraryPackage:
-                target = os.path.join(target, os.path.basename(libraryPackage))
-                shutil.rmtree(target, ignore_errors=True)
-                shutil.copytree(libraryPackage, target)
-        else:
-            libraryFiles = EricFileDialog.getOpenFileNames(
-                self.microPython,
-                title,
-                os.path.expanduser("~"),
-                self.tr(
-                    "Compiled Python Files (*.mpy);;"
-                    "Python Files (*.py);;"
-                    "All Files (*)"
-                ),
-            )
-
-            for libraryFile in libraryFiles:
-                if os.path.exists(libraryFile):
-                    shutil.copy2(libraryFile, target)
-
-    def getDocumentationUrl(self):
-        """
-        Public method to get the device documentation URL.
-
-        @return documentation URL of the device
-        @rtype str
-        """
-        return Preferences.getMicroPython("CircuitPythonDocuUrl")
-
-    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)
-        """
-        return [
-            (
-                self.tr("CircuitPython Firmware"),
-                Preferences.getMicroPython("CircuitPythonFirmwareUrl"),
-            ),
-            (
-                self.tr("CircuitPython Libraries"),
-                Preferences.getMicroPython("CircuitPythonLibrariesUrl"),
-            ),
-        ]
-
-
-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 CircuitPythonDevice
-    """
-    return CircuitPythonDevice(microPythonWidget, deviceType, boardName)

eric ide

mercurial