diff -r df9520c864f2 -r 5d409223cf3f src/eric7/MicroPython/MicrobitDevices.py --- a/src/eric7/MicroPython/MicrobitDevices.py Wed Feb 08 11:54:36 2023 +0100 +++ b/src/eric7/MicroPython/MicrobitDevices.py Wed Feb 08 18:09:19 2023 +0100 @@ -11,15 +11,16 @@ import os import shutil -from PyQt6.QtCore import QStandardPaths, pyqtSlot +from PyQt6.QtCore import QStandardPaths, QUrl, pyqtSlot +from PyQt6.QtNetwork import QNetworkRequest from PyQt6.QtWidgets import QInputDialog, QLineEdit -from eric7 import Preferences +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 MicroPythonDevice +from .MicroPythonDevices import FirmwareGithubUrls, MicroPythonDevice from .MicroPythonWidget import HAS_QTCHART @@ -143,13 +144,17 @@ @type QMenu """ connected = self.microPython.isConnected() + linkConnected = self.microPython.isLinkConnected() - act = menu.addAction(self.tr("Flash MicroPython"), self.__flashMicroPython) - act.setEnabled(not connected) - act = menu.addAction( + menu.addAction( + self.tr("Show MicroPython Versions"), self.__showFirmwareVersions + ).setEnabled(connected and self.getDeviceType() != "calliope") + menu.addAction( + self.tr("Flash MicroPython"), self.__flashMicroPython + ).setEnabled(not linkConnected) + menu.addAction( self.tr("Flash Firmware"), lambda: self.__flashMicroPython(firmware=True) - ) - act.setEnabled(not connected) + ).setEnabled(not linkConnected) menu.addSeparator() act = menu.addAction(self.tr("Save Script"), self.__saveScriptToDevice) act.setToolTip(self.tr("Save the current script to the selected device")) @@ -160,10 +165,9 @@ ) act.setEnabled(connected) menu.addSeparator() - act = menu.addAction( + menu.addAction( self.tr("Reset {0}").format(self.deviceName()), self.__resetDevice - ) - act.setEnabled(connected) + ).setEnabled(connected) def hasFlashMenuEntry(self): """ @@ -217,7 +221,7 @@ " instructions. </p>" "<ul>" "<li>unplug USB cable and any batteries</li>" - "<li>keep RESET button pressed an plug USB cable" + "<li>keep RESET button pressed and plug USB cable" " back in</li>" "<li>a drive called MAINTENANCE should be" " available</li>" @@ -291,6 +295,103 @@ ) @pyqtSlot() + def __showFirmwareVersions(self): + """ + Private slot to show the firmware version of the connected device and the + available firmware version. + """ + if self.microPython.isConnected(): + interface = self.microPython.commandsInterface() + if interface is not None: + impInfo = interface.getImplementation() + versionInfo = interface.version() + if impInfo["name"] != "micropython": + 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.""" + """ Aborting...""" + ), + ) + else: + impInfo["version"] = versionInfo["release"] + if self.getDeviceType() == "bbc_microbit": + if "nRF51822" in versionInfo["machine"]: + url = QUrl(FirmwareGithubUrls["microbit_v1"]) + elif "nRF52833" in versionInfo["machine"]: + 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 + 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, impInfo) + ) + + def __firmwareVersionResponse(self, reply, implementation): + """ + Private method handling the response of the latest version request. + + @param reply reference to the reply object + @type QNetworkReply + @param implementation dictionary containing the implementation data of the + connected device + @type dict + """ + 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 implementation["version"] == "unknown": + currentVersionStr = self.tr("unknown") + currentVersion = (0, 0, 0) + else: + currentVersionStr = implementation["version"] + currentVersion = Globals.versionToTuple(currentVersionStr) + + msg = self.tr( + "<h4>MicroPython Version Information<br/>" + "(BBC micro:bit v{2})</h4>" + "<table>" + "<tr><td>Installed:</td><td>{0}</td></tr>" + "<tr><td>Available:</td><td>{1}</td></tr>" + "</table>" + ).format(currentVersionStr, tag, currentVersionStr[0]) + if currentVersion < latestVersion: + msg += self.tr("<p><b>Update available!</b></p>") + + EricMessageBox.information( + None, + self.tr("MicroPython Version"), + msg, + ) + + @pyqtSlot() def __saveMain(self): """ Private slot to copy the current script as 'main.py' onto the