--- a/src/eric7/MicroPython/CircuitPythonDevices.py Thu Feb 02 13:42:50 2023 +0100 +++ b/src/eric7/MicroPython/CircuitPythonDevices.py Thu Feb 02 18:01:00 2023 +0100 @@ -11,6 +11,7 @@ import shutil from PyQt6.QtCore import pyqtSlot +from PyQt6.QtWidgets import QMenu from eric7 import Preferences from eric7.EricWidgets import EricFileDialog, EricMessageBox @@ -27,7 +28,7 @@ DeviceVolumeName = "CIRCUITPY" - def __init__(self, microPythonWidget, deviceType, parent=None): + def __init__(self, microPythonWidget, deviceType, boardName, parent=None): """ Constructor @@ -35,11 +36,14 @@ @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.__nonUF2devices = { @@ -164,6 +168,27 @@ self.__workspace = self.__findWorkspace(silent=silent) return self.__workspace + 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. @@ -183,7 +208,13 @@ if len(deviceDirectories) == 1: return deviceDirectories[0] else: - return self.selectDeviceDirectory(deviceDirectories) + 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) @@ -210,15 +241,23 @@ """ connected = self.microPython.isConnected() + self.__libraryMenu = QMenu(self.tr("Library Management")) + 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()) + act = menu.addAction( self.tr("Flash CircuitPython Firmware"), self.__flashCircuitPython ) act.setEnabled(not connected) menu.addSeparator() - act = menu.addAction( - self.tr("Install Library Files"), self.__installLibraryFiles - ) - act.setEnabled(self.__deviceVolumeMounted()) + menu.addMenu(self.__libraryMenu) def hasFlashMenuEntry(self): """ @@ -265,14 +304,23 @@ ) @pyqtSlot() - def __installLibraryFiles(self): + 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, - self.tr("Install Library Files"), + title, self.tr( """The device volume "<b>{0}</b>" is not available.""" """ Ensure it is mounted properly and try again.""" @@ -285,20 +333,32 @@ if not os.path.isdir(target): os.makedirs(target) - libraryFiles = EricFileDialog.getOpenFileNames( - self.microPython, - self.tr("Install Library Files"), - os.path.expanduser("~"), - self.tr( - "Compiled Python Files (*.mpy);;" - "Python Files (*.py);;" - "All Files (*)" - ), - ) + 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) + for libraryFile in libraryFiles: + if os.path.exists(libraryFile): + shutil.copy2(libraryFile, target) def getDocumentationUrl(self): """ @@ -329,7 +389,7 @@ ] -def createDevice(microPythonWidget, deviceType, vid, pid): +def createDevice(microPythonWidget, deviceType, vid, pid, boardName): """ Function to instantiate a MicroPython device object. @@ -341,7 +401,9 @@ @type int @param pid product ID @type int + @param boardName name of the board + @type str @return reference to the instantiated device object @rtype CircuitPythonDevice """ - return CircuitPythonDevice(microPythonWidget, deviceType) + return CircuitPythonDevice(microPythonWidget, deviceType, boardName)