--- a/src/eric7/MicroPython/EspDevices.py Sun Feb 12 18:11:20 2023 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,593 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (c) 2019 - 2023 Detlev Offenbach <detlev@die-offenbachs.de> -# - -""" -Module implementing the device interface class for ESP32 and ESP8266 based -boards. -""" - -from PyQt6.QtCore import QProcess, QUrl, pyqtSlot -from PyQt6.QtNetwork import QNetworkRequest -from PyQt6.QtWidgets import QDialog, QMenu - -from eric7 import Globals, Preferences -from eric7.EricWidgets import EricMessageBox -from eric7.EricWidgets.EricApplication import ericApp -from eric7.EricWidgets.EricProcessDialog import EricProcessDialog -from eric7.SystemUtilities import PythonUtilities - -from .MicroPythonDevices import FirmwareGithubUrls, MicroPythonDevice -from .MicroPythonWidget import HAS_QTCHART - - -class EspDevice(MicroPythonDevice): - """ - Class implementing the device for ESP32 and ESP8266 based boards. - """ - - def __init__(self, microPythonWidget, deviceType, 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 parent reference to the parent object - @type QObject - """ - super().__init__(microPythonWidget, deviceType, parent) - - self.__createEsp32Submenu() - - 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 - """ - return self.tr("ESP8266, ESP32") - - 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 __createEsp32Submenu(self): - """ - Private method to create the ESP32 submenu. - """ - self.__espMenu = QMenu(self.tr("ESP32 Functions")) - - self.__showMpyAct = self.__espMenu.addAction( - self.tr("Show MicroPython Versions"), self.__showFirmwareVersions - ) - self.__espMenu.addSeparator() - self.__eraseFlashAct = self.__espMenu.addAction( - self.tr("Erase Flash"), self.__eraseFlash - ) - self.__flashMpyAct = self.__espMenu.addAction( - self.tr("Flash MicroPython Firmware"), self.__flashMicroPython - ) - self.__espMenu.addSeparator() - self.__flashAdditionalAct = self.__espMenu.addAction( - self.tr("Flash Additional Firmware"), self.__flashAddons - ) - self.__espMenu.addSeparator() - self.__backupAct = self.__espMenu.addAction( - self.tr("Backup Firmware"), self.__backupFlash - ) - self.__restoreAct = self.__espMenu.addAction( - self.tr("Restore Firmware"), self.__restoreFlash - ) - self.__espMenu.addSeparator() - self.__chipIdAct = self.__espMenu.addAction( - self.tr("Show Chip ID"), self.__showChipID - ) - self.__flashIdAct = self.__espMenu.addAction( - self.tr("Show Flash ID"), self.__showFlashID - ) - self.__macAddressAct = self.__espMenu.addAction( - self.tr("Show MAC Address"), self.__showMACAddress - ) - self.__espMenu.addSeparator() - self.__resetAct = self.__espMenu.addAction( - self.tr("Reset Device"), self.__resetDevice - ) - self.__espMenu.addSeparator() - self.__espMenu.addAction(self.tr("Install 'esptool.py'"), self.__installEspTool) - - 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) - self.__eraseFlashAct.setEnabled(not linkConnected) - self.__flashMpyAct.setEnabled(not linkConnected) - self.__flashAdditionalAct.setEnabled(not linkConnected) - self.__backupAct.setEnabled(not linkConnected) - self.__restoreAct.setEnabled(not linkConnected) - self.__chipIdAct.setEnabled(not linkConnected) - self.__flashIdAct.setEnabled(not linkConnected) - self.__macAddressAct.setEnabled(not linkConnected) - self.__resetAct.setEnabled(connected or not linkConnected) - - menu.addMenu(self.__espMenu) - - 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 __eraseFlash(self): - """ - Private slot to erase the device flash memory. - """ - ok = EricMessageBox.yesNo( - self.microPython, - self.tr("Erase Flash"), - self.tr("""Shall the flash of the selected device really be erased?"""), - ) - if ok: - flashArgs = [ - "-u", - "-m", - "esptool", - "--port", - self.microPython.getCurrentPort(), - "erase_flash", - ] - dlg = EricProcessDialog( - self.tr("'esptool erase_flash' Output"), - self.tr("Erase Flash"), - showProgress=True, - ) - res = dlg.startProcess(PythonUtilities.getPythonExecutable(), flashArgs) - if res: - dlg.exec() - - @pyqtSlot() - def __flashMicroPython(self): - """ - Private slot to flash a MicroPython firmware to the device. - """ - from .EspFirmwareSelectionDialog import EspFirmwareSelectionDialog - - dlg = EspFirmwareSelectionDialog() - if dlg.exec() == QDialog.DialogCode.Accepted: - chip, firmware, baudRate, flashMode, flashAddress = dlg.getData() - flashArgs = [ - "-u", - "-m", - "esptool", - "--chip", - chip, - "--port", - self.microPython.getCurrentPort(), - ] - if baudRate != "115200": - flashArgs += ["--baud", baudRate] - flashArgs.append("write_flash") - if flashMode: - flashArgs += ["--flash_mode", flashMode] - flashArgs += [ - flashAddress, - firmware, - ] - dlg = EricProcessDialog( - self.tr("'esptool write_flash' Output"), - self.tr("Flash MicroPython Firmware"), - showProgress=True, - ) - res = dlg.startProcess(PythonUtilities.getPythonExecutable(), flashArgs) - if res: - dlg.exec() - - @pyqtSlot() - def __flashAddons(self): - """ - Private slot to flash some additional firmware images. - """ - from .EspFirmwareSelectionDialog import EspFirmwareSelectionDialog - - dlg = EspFirmwareSelectionDialog(addon=True) - if dlg.exec() == QDialog.DialogCode.Accepted: - chip, firmware, baudRate, flashMode, flashAddress = dlg.getData() - flashArgs = [ - "-u", - "-m", - "esptool", - "--chip", - chip, - "--port", - self.microPython.getCurrentPort(), - ] - if baudRate != "115200": - flashArgs += ["--baud", baudRate] - flashArgs.append("write_flash") - if flashMode: - flashArgs += ["--flash_mode", flashMode] - flashArgs += [ - flashAddress.lower(), - firmware, - ] - dlg = EricProcessDialog( - self.tr("'esptool write_flash' Output"), - self.tr("Flash Additional Firmware"), - showProgress=True, - ) - res = dlg.startProcess(PythonUtilities.getPythonExecutable(), flashArgs) - if res: - dlg.exec() - - @pyqtSlot() - def __backupFlash(self): - """ - Private slot to backup the currently flashed firmware. - """ - from .EspBackupRestoreFirmwareDialog import EspBackupRestoreFirmwareDialog - - dlg = EspBackupRestoreFirmwareDialog(backupMode=True) - if dlg.exec() == QDialog.DialogCode.Accepted: - chip, flashSize, baudRate, flashMode, firmware = dlg.getData() - flashArgs = [ - "-u", - "-m", - "esptool", - "--chip", - chip, - "--port", - self.microPython.getCurrentPort(), - "--baud", - baudRate, - "read_flash", - "0x0", - flashSize, - firmware, - ] - dlg = EricProcessDialog( - self.tr("'esptool read_flash' Output"), - self.tr("Backup Firmware"), - showProgress=True, - ) - res = dlg.startProcess(PythonUtilities.getPythonExecutable(), flashArgs) - if res: - dlg.exec() - - @pyqtSlot() - def __restoreFlash(self): - """ - Private slot to restore a previously saved firmware. - """ - from .EspBackupRestoreFirmwareDialog import EspBackupRestoreFirmwareDialog - - dlg = EspBackupRestoreFirmwareDialog(backupMode=False) - if dlg.exec() == QDialog.DialogCode.Accepted: - chip, flashSize, baudRate, flashMode, firmware = dlg.getData() - flashArgs = [ - "-u", - "-m", - "esptool", - "--chip", - chip, - "--port", - self.microPython.getCurrentPort(), - "--baud", - baudRate, - "write_flash", - ] - if flashMode: - flashArgs.extend( - [ - "--flash_mode", - flashMode, - ] - ) - if bool(flashSize): - flashArgs.extend( - [ - "--flash_size", - flashSize, - ] - ) - flashArgs.extend( - [ - "0x0", - firmware, - ] - ) - dlg = EricProcessDialog( - self.tr("'esptool write_flash' Output"), - self.tr("Restore Firmware"), - showProgress=True, - ) - res = dlg.startProcess(PythonUtilities.getPythonExecutable(), flashArgs) - if res: - dlg.exec() - - @pyqtSlot() - def __showFirmwareVersions(self): - """ - Private slot to show the firmware version of the connected device and the - available firmware version. - """ - if self.microPython.isConnected(): - if self._deviceData["mpy_name"] == "micropython": - url = QUrl(FirmwareGithubUrls["micropython"]) - elif self._deviceData["mpy_name"] == "circuitpython": - url = QUrl(FirmwareGithubUrls["circuitpython"]) - else: - 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...""" - ), - ) - 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["mpy_version"] == "unknown": - currentVersionStr = self.tr("unknown") - currentVersion = (0, 0, 0) - else: - currentVersionStr = self._deviceData["mpy_version"] - currentVersion = Globals.versionToTuple(currentVersionStr) - - if self._deviceData["mpy_name"] == "circuitpython": - kind = "CircuitPython" - elif self._deviceData["mpy_name"] == "micropython": - kind = "MicroPython" - - msg = self.tr( - "<h4>{0} Version Information</h4>" - "<table>" - "<tr><td>Installed:</td><td>{1}</td></tr>" - "<tr><td>Available:</td><td>{2}</td></tr>" - "</table>" - ).format(kind, 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 __showChipID(self): - """ - Private slot to show the ID of the ESP chip. - """ - args = [ - "-u", - "-m", - "esptool", - "--port", - self.microPython.getCurrentPort(), - "chip_id", - ] - dlg = EricProcessDialog( - self.tr("'esptool chip_id' Output"), self.tr("Show Chip ID") - ) - res = dlg.startProcess(PythonUtilities.getPythonExecutable(), args) - if res: - dlg.exec() - - @pyqtSlot() - def __showFlashID(self): - """ - Private slot to show the ID of the ESP flash chip. - """ - args = [ - "-u", - "-m", - "esptool", - "--port", - self.microPython.getCurrentPort(), - "flash_id", - ] - dlg = EricProcessDialog( - self.tr("'esptool flash_id' Output"), self.tr("Show Flash ID") - ) - res = dlg.startProcess(PythonUtilities.getPythonExecutable(), args) - if res: - dlg.exec() - - @pyqtSlot() - def __showMACAddress(self): - """ - Private slot to show the MAC address of the ESP chip. - """ - args = [ - "-u", - "-m", - "esptool", - "--port", - self.microPython.getCurrentPort(), - "read_mac", - ] - dlg = EricProcessDialog( - self.tr("'esptool read_mac' Output"), self.tr("Show MAC Address") - ) - res = dlg.startProcess(PythonUtilities.getPythonExecutable(), args) - if res: - dlg.exec() - - @pyqtSlot() - def __resetDevice(self): - """ - Private slot to reset the connected device. - """ - if self.microPython.isConnected(): - self.microPython.commandsInterface().execute( - [ - "import machine", - "machine.reset()", - ] - ) - else: - # perform a reset via esptool using flash_id command ignoring - # the output - args = [ - "-u", - "-m", - "esptool", - "--port", - self.microPython.getCurrentPort(), - "flash_id", - ] - proc = QProcess() - proc.start(PythonUtilities.getPythonExecutable(), args) - procStarted = proc.waitForStarted(10000) - if procStarted: - proc.waitForFinished(10000) - - @pyqtSlot() - def __installEspTool(self): - """ - Private slot to install the esptool package via pip. - """ - pip = ericApp().getObject("Pip") - pip.installPackages( - ["esptool"], interpreter=PythonUtilities.getPythonExecutable() - ) - - def getDocumentationUrl(self): - """ - Public method to get the device documentation URL. - - @return documentation URL of the device - @rtype str - """ - return Preferences.getMicroPython("MicroPythonDocuUrl") - - def getFirmwareUrl(self): - """ - Public method to get the device firmware download URL. - - @return firmware download URL of the device - @rtype str - """ - return Preferences.getMicroPython("MicroPythonFirmwareUrl") - - -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 EspDevice - """ - return EspDevice(microPythonWidget, deviceType)