Wed, 03 Feb 2021 19:15:58 +0100
MicroPython: implemented code to deal with multiple devices attached to the computer.
--- a/eric6/MicroPython/CircuitPythonDevices.py Wed Feb 03 19:14:35 2021 +0100 +++ b/eric6/MicroPython/CircuitPythonDevices.py Wed Feb 03 19:15:58 2021 +0100 @@ -38,6 +38,8 @@ @type QObject """ super(CircuitPythonDevice, self).__init__(microPythonWidget, parent) + + self.__workspace = self.__findWorkspace() def setButtons(self): """ @@ -135,7 +137,7 @@ @return flag indicated a mounted device @rtype bool """ - return self.getWorkspace(silent=True).endswith(self.DeviceVolumeName) + return self.DeviceVolumeName in self.getWorkspace(silent=True) def getWorkspace(self, silent=False): """ @@ -146,12 +148,32 @@ @return workspace directory used for saving files @rtype str """ - # Attempts to find the path on the filesystem that represents the - # plugged in CIRCUITPY board. - deviceDirectory = Utilities.findVolume(self.DeviceVolumeName) + if self.__workspace: + # return cached entry + return self.__workspace + else: + self.__workspace = self.__findWorkspace(silent=silent) + return self.__workspace + + def __findWorkspace(self, silent=False): + """ + Public method to find the workspace directory. - if deviceDirectory: - return deviceDirectory + @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 = Utilities.findVolume(self.DeviceVolumeName, + all=True) + + if deviceDirectories: + if len(deviceDirectories) == 1: + return deviceDirectories[0] + else: + return self.selectDeviceDirectory(deviceDirectories) else: # return the default workspace and give the user a warning (unless # silent mode is selected)
--- a/eric6/MicroPython/CircuitPythonFirmwareSelectionDialog.py Wed Feb 03 19:14:35 2021 +0100 +++ b/eric6/MicroPython/CircuitPythonFirmwareSelectionDialog.py Wed Feb 03 19:15:58 2021 +0100 @@ -85,6 +85,7 @@ ("PyPortal Pynt", "PORTALBOOT"), ("PyPortal Titano", "PORTALBOOT"), ("PyRuler", "TRINKETBOOT"), + ("QT Py M0", "QTPY_BOOT"), ("Radiofruit M0", "RADIOBOOT"), ("Trellis M4 Express", "TRELM4BOOT"), ("Trinket M0", "TRINKETBOOT"), @@ -103,6 +104,7 @@ # Seed boards ("--- Seeed Studio ---", ""), ("Grove Zero", "Grove Zero"), + ("Seeduino XIAO", "Arduino"), # other boards we know about (self.tr("--- Others ---"), ""), @@ -140,9 +142,18 @@ if volumeName and volumeName != self.__manualMarker: # check if the user selected a board and the board is in # bootloader mode - deviceDirectory = Utilities.findVolume(volumeName) - if deviceDirectory: - self.bootPicker.setText(deviceDirectory) + deviceDirectories = Utilities.findVolume(volumeName, all=True) + if len(deviceDirectories) > 1: + enable = False + E5MessageBox.warning( + self, + self.tr("Select Path to Device"), + self.tr("There are multiple devices in 'bootloader'" + " mode and mounted. Please make sure, that" + " only one device is prepared for flashing.") + ) + elif len(deviceDirectories) == 1: + self.bootPicker.setText(deviceDirectories[0]) enable = True else: enable = False
--- a/eric6/MicroPython/MicroPythonDevices.py Wed Feb 03 19:14:35 2021 +0100 +++ b/eric6/MicroPython/MicroPythonDevices.py Wed Feb 03 19:15:58 2021 +0100 @@ -12,6 +12,7 @@ import os from PyQt5.QtCore import pyqtSlot, QObject +from PyQt5.QtWidgets import QInputDialog from E5Gui.E5Application import e5App @@ -58,8 +59,9 @@ (0x04D8, 0xED94), # PyCubed (0x04D8, 0xEDBE), # SAM32 (0x1D50, 0x60E8), # PewPew Game Console + (0x2886, 0x002F), # Seeed XIAO (0x2886, 0x802D), # Seeed Wio Terminal - (0x2886, 0x002F), # Seeed XIAO + (0x2886, 0x802F), # Seeed XIAO (0x1B4F, 0x0016), # Sparkfun Thing Plus - SAMD51 (0x2341, 0x8057), # Arduino Nano 33 IoT board (0x04D8, 0xEAD1), # DynOSSAT-EDU-EPS @@ -361,6 +363,28 @@ return (Preferences.getMultiProject("Workspace") or os.path.expanduser("~")) + def selectDeviceDirectory(self, deviceDirectories): + """ + Public method to select the device directory from a list of detected + ones. + + @param deviceDirectories list of directories to select from + @type list of str + @return selected directory or an empty string + @rtype str + """ + deviceDirectory, ok = QInputDialog.getItem( + None, + self.tr("Select Device Directory"), + self.tr("Select the directory for the connected device:"), + [""] + deviceDirectories, + 0, False) + if ok: + return deviceDirectory + else: + # user cancelled + return "" + def sendCommands(self, commandsList): """ Public method to send a list of commands to the device.
--- a/eric6/MicroPython/MicrobitDevices.py Wed Feb 03 19:14:35 2021 +0100 +++ b/eric6/MicroPython/MicrobitDevices.py Wed Feb 03 19:15:58 2021 +0100 @@ -42,6 +42,8 @@ super(MicrobitDevice, self).__init__(microPythonWidget, parent) self.__deviceType = deviceType + + self.__workspace = self.__findWorkspace() def setButtons(self): """ @@ -132,17 +134,34 @@ @return workspace directory used for saving files @rtype str """ + if self.__workspace: + # return cached entry + return self.__workspace + else: + self.__workspace = self.__findWorkspace() + return self.__workspace + + def __findWorkspace(self): + """ + Public method to find the workspace directory. + + @return workspace directory used for saving files + @rtype str + """ # Attempts to find the path on the filesystem that represents the # plugged in MICROBIT or MINI board. if self.__deviceType == "bbc_microbit": # BBC micro:bit - deviceDirectory = Utilities.findVolume("MICROBIT") + deviceDirectories = Utilities.findVolume("MICROBIT", all=True) else: # Calliope mini - deviceDirectory = Utilities.findVolume("MINI") + deviceDirectories = Utilities.findVolume("MINI", all=True) - if deviceDirectory: - return deviceDirectory + if deviceDirectories: + if len(deviceDirectories) == 1: + return deviceDirectories[0] + else: + return self.selectDeviceDirectory(deviceDirectories) else: # return the default workspace and give the user a warning E5MessageBox.warning( @@ -207,20 +226,22 @@ # 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. - # The Calliope mini board must be in standard mode. if self.__deviceType == "bbc_microbit": # BBC micro:bit if firmware: - deviceDirectory = Utilities.findVolume("MAINTENANCE") + deviceDirectories = Utilities.findVolume("MAINTENANCE", + all=True) else: - deviceDirectory = Utilities.findVolume("MICROBIT") + deviceDirectories = Utilities.findVolume("MICROBIT", + all=True) else: # Calliope mini if firmware: - deviceDirectory = Utilities.findVolume("MAINTENANCE") + deviceDirectories = Utilities.findVolume("MAINTENANCE", + all=True) else: - deviceDirectory = Utilities.findVolume("MINI") - if not deviceDirectory: + deviceDirectories = Utilities.findVolume("MINI", all=True) + if len(deviceDirectories) == 0: if self.__deviceType == "bbc_microbit": # BBC micro:bit is not ready or not mounted if firmware: @@ -284,7 +305,7 @@ '</p>' ) ) - else: + elif len(deviceDirectories) == 1: downloadsPath = QStandardPaths.standardLocations( QStandardPaths.DownloadLocation)[0] firmware = E5FileDialog.getOpenFileName( @@ -294,7 +315,14 @@ self.tr("MicroPython/Firmware Files (*.hex *.bin);;" "All Files (*)")) if firmware and os.path.exists(firmware): - shutil.copy2(firmware, deviceDirectory) + shutil.copy2(firmware, deviceDirectories[0]) + else: + E5MessageBox.warning( + self, + self.tr("Flash MicroPython/Firmware"), + self.tr("There are multiple devices in ready for flashing." + " Please make sure, that only one device is prepared.") + ) @pyqtSlot() def __saveMain(self):
--- a/eric6/MicroPython/PyBoardDevices.py Wed Feb 03 19:14:35 2021 +0100 +++ b/eric6/MicroPython/PyBoardDevices.py Wed Feb 03 19:15:58 2021 +0100 @@ -43,6 +43,8 @@ @type QObject """ super(PyBoardDevice, self).__init__(microPythonWidget, parent) + + self.__workspace = self.__findWorkspace() def setButtons(self): """ @@ -140,7 +142,7 @@ @return flag indicated a mounted device @rtype bool """ - return self.getWorkspace(silent=True).endswith(self.DeviceVolumeName) + return self.DeviceVolumeName in self.getWorkspace(silent=True) def getWorkspace(self, silent=False): """ @@ -151,12 +153,32 @@ @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 __findWorkspace(self, silent=False): + """ + Public 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 path on the filesystem that represents the # plugged in PyBoard board. - deviceDirectory = Utilities.findVolume(self.DeviceVolumeName) + deviceDirectories = Utilities.findVolume(self.DeviceVolumeName, + all=True) - if deviceDirectory: - return deviceDirectory + if deviceDirectories: + if len(deviceDirectories) == 1: + return deviceDirectories[0] + else: + return self.selectDeviceDirectory(deviceDirectories) else: # return the default workspace and give the user a warning (unless # silent mode is selected)
--- a/eric6/Utilities/__init__.py Wed Feb 03 19:14:35 2021 +0100 +++ b/eric6/Utilities/__init__.py Wed Feb 03 19:15:58 2021 +0100 @@ -1238,15 +1238,22 @@ return dirs -def findVolume(volumeName): +def findVolume(volumeName, all=False): """ Function to find the directory belonging to a given volume name. @param volumeName name of the volume to search for @type str - @return directory path of the given volume name - @rtype str + @param all flag indicating to get the directories for all volumes + starting with the given name (defaults to False) + @type bool (optional) + @return directory path or list of directory paths for the given volume + name + @rtype str or list of str """ + # TODO: add option to get all (i.e. all starting with volumeName) + # and return a list of found directories + volumeDirectories = [] volumeDirectory = None if isWindowsPlatform(): @@ -1277,10 +1284,14 @@ try: for disk in "ABCDEFGHIJKLMNOPQRSTUVWXYZ": dirpath = "{0}:\\".format(disk) - if (os.path.exists(dirpath) and - getVolumeName(dirpath) == volumeName): - volumeDirectory = dirpath - break + if os.path.exists(dirpath): + if all: + if getVolumeName(dirpath).startswith(volumeName): + volumeDirectories.append(dirpath) + else: + if getVolumeName(dirpath) == volumeName: + volumeDirectory = dirpath + break finally: ctypes.windll.kernel32.SetErrorMode(oldMode) else: @@ -1291,16 +1302,26 @@ subprocess.check_output(mountCommand).splitlines() # secok ) mountedVolumes = [x.split()[2] for x in mountOutput] - for volume in mountedVolumes: - if volume.decode("utf-8").endswith(volumeName): - volumeDirectory = volume.decode("utf-8") + if all: + for volume in mountedVolumes: + if volumeName in volume.decode("utf-8"): + volumeDirectories.append(volume.decode("utf-8")) + if volumeDirectories: break - if volumeDirectory: - break + else: + for volume in mountedVolumes: + if volume.decode("utf-8").endswith(volumeName): + volumeDirectory = volume.decode("utf-8") + break + if volumeDirectory: + break except FileNotFoundError: pass - return volumeDirectory + if all: + return volumeDirectories + else: + return volumeDirectory def getTestFileName(fn):