--- a/src/eric7/MicroPython/Devices/CircuitPythonDevices.py Mon Feb 27 16:55:09 2023 +0100 +++ b/src/eric7/MicroPython/Devices/CircuitPythonDevices.py Mon Feb 27 17:43:11 2023 +0100 @@ -7,11 +7,13 @@ Module implementing the device interface class for CircuitPython boards. """ +import ast +import json import os import shutil -from PyQt6.QtCore import QProcess, QUrl, pyqtSlot -from PyQt6.QtNetwork import QNetworkRequest +from PyQt6.QtCore import QUrl, pyqtSlot +from PyQt6.QtNetwork import QNetworkReply, QNetworkRequest from PyQt6.QtWidgets import QMenu from eric7 import Globals, Preferences @@ -69,6 +71,9 @@ if not connected and self.__libraryMenu.isTearOffMenuVisible(): self.__libraryMenu.hideTearOffMenu() + if self.__flashMenu.isTearOffMenuVisible(): + self.__flashMenu.hideTearOffMenu() + super().setConnected(connected) def setButtons(self): @@ -262,33 +267,14 @@ self.__libraryMenu.aboutToShow.connect(self.__aboutToShowLibraryMenu) self.__libraryMenu.setTearOffEnabled(True) + self.__flashMenu = self.__createFlashMenus() + self.__cpyMenu = QMenu(self.tr("CircuitPython Functions")) - self.__cpyMenu.addAction( self.tr("Show CircuitPython Versions"), self.__showCircuitPythonVersions ) self.__cpyMenu.addSeparator() - - boardName = self.microPython.getCurrentBoard() - lBoardName = boardName.lower() if boardName else "" - 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.__flashCpyAct = self.__cpyMenu.addMenu(self.__flashMenu) self.__cpyMenu.addSeparator() self.__cpyMenu.addMenu(self.__libraryMenu) self.__cpyMenu.addSeparator() @@ -296,6 +282,48 @@ self.tr("Reset Device"), self.__resetDevice ) + def __createFlashMenus(self): + """ + Private method to create the various menus to flash a CircuitPython firmware. + + @return reference to the created top level flash menu + @rtype QMenu + """ + menu = QMenu(self.tr("Flash CircuitPython Firmware")) + menu.setTearOffEnabled(True) + + # UF2 devices + menu.addAction(self.tr("UF2 Device"), self.__flashCircuitPython) + menu.addSeparator() + + # ESP32 specific submenu + self.__esp32FlashMenu = QMenu(self.tr("ESP32 Device")) + self.__esp32FlashMenu.addAction(self.tr("Erase Flash"), self.__esp32EraseFlash) + self.__esp32FlashMenu.addAction( + self.tr("Flash MicroPython Firmware"), self.__esp32FlashPython + ) + self.__esp32FlashMenu.addSeparator() + self.__esp32FlashMenu.addAction( + self.tr("Flash Additional Firmware"), self.__esp32FlashAddons + ) + menu.addMenu(self.__esp32FlashMenu) + + # Teensy 4.0 and 4.1 specific submenu + self.__teensyFlashMenu = QMenu(self.tr("Teensy Device")) + self.__teensyFlashMenu.addAction( + self.tr("CircuitPython Flash Instructions"), + self.__showTeensyFlashInstructions, + ) + act = self.__teensyFlashMenu.addAction( + self.tr("Start 'Teensy Loader'"), self.__startTeensyLoader + ) + act.setToolTip( + self.tr("Start the 'Teensy Loader' application to flash the Teensy device.") + ) + menu.addMenu(self.__teensyFlashMenu) + + return menu + def addDeviceMenuEntries(self, menu): """ Public method to add device specific entries to the given menu. @@ -365,41 +393,54 @@ dlg = UF2FlashDialog(boardType="circuitpython") dlg.exec() + @pyqtSlot() def __showTeensyFlashInstructions(self): """ - Private method to show a message box because Teensy does not support + Private slot 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"), - ) + from .TeensyDevices import showTeensyFlashInstructions + showTeensyFlashInstructions() + + @pyqtSlot() def __startTeensyLoader(self): """ - Private method to start the 'Teensy Loader' application. + Private slot 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>""" - ), - ) + from .TeensyDevices import startTeensyLoader + + startTeensyLoader() + + @pyqtSlot() + def __esp32EraseFlash(self): + """ + Private slot to erase the flash of an ESP32 device. + """ + from .EspDevices import eraseFlash + + eraseFlash(self.microPython.getCurrentPort()) + + @pyqtSlot() + def __esp32FlashPython(self): + """ + Private slot to flash a MicroPython or CircuitPython firmware to an ESP32 + device. + """ + from .EspDevices import flashPythonFirmware + + flashPythonFirmware(self.microPython.getCurrentPort()) + + @pyqtSlot() + def __esp32FlashAddons(self): + """ + Private slot to flash additional firmware to an ESP32 device. + """ + from .EspDevices import flashAddonFirmware + + flashAddonFirmware(self.microPython.getCurrentPort()) @pyqtSlot() def __showCircuitPythonVersions(self): @@ -412,9 +453,10 @@ reply = ui.networkAccessManager().head(request) reply.finished.connect(lambda: self.__cpyVersionResponse(reply)) + @pyqtSlot(QNetworkReply) def __cpyVersionResponse(self, reply): """ - Private method handling the response of the latest version request. + Private slot handling the response of the latest version request. @param reply reference to the reply object @type QNetworkReply @@ -543,6 +585,104 @@ ), ] + ################################################################## + ## Methods below implement WiFi related methods + ################################################################## + + def hasWifi(self): + """ + Public method to check the availability of WiFi. + + @return tuple containing a flag indicating the availability of WiFi + and the WiFi type (picow or pimoroni) + @rtype tuple of (bool, str) + @exception OSError raised to indicate an issue with the device + """ + command = """ +def has_wifi(): + try: + import wifi + if hasattr(wifi, 'radio'): + return True, 'circuitpython' + except ImportError: + pass + + return False, '' + +print(has_wifi()) +del has_wifi +""" + out, err = self._interface.execute(command, mode=self.submitMode) + if err: + raise OSError(self._shortError(err)) + return ast.literal_eval(out.decode("utf-8")) + + def getWifiData(self): + """ + Public method to get data related to the current WiFi status. + + @return tuple of three dictionaries containing the WiFi status data + for the WiFi client, access point and overall data + @rtype tuple of (dict, dict, dict) + @exception OSError raised to indicate an issue with the device + """ + command = """ +def wifi_status(): + import binascii + import json + import wifi + + r = wifi.radio + + station = { + 'active': r.enabled and r.ipv4_address_ap is None, + 'connected': r.ipv4_address is not None, + 'ifconfig': ( + str(r.ipv4_address) if r.ipv4_address else'0.0.0.0', + str(r.ipv4_subnet) if r.ipv4_subnet else'0.0.0.0', + str(r.ipv4_gateway) if r.ipv4_gateway else'0.0.0.0', + str(r.ipv4_dns) if r.ipv4_dns else'0.0.0.0', + ), + 'mac': binascii.hexlify(r.mac_address, ':').decode(), + 'txpower': r.tx_power, + 'hostname': r.hostname, + } + print(json.dumps(station)) + + ap = { + 'active': r.enabled and r.ipv4_address_ap is not None, + 'connected': r.ipv4_address_ap is not None, + 'ifconfig': ( + str(r.ipv4_address_ap) if r.ipv4_address_ap else'0.0.0.0', + str(r.ipv4_subnet_ap) if r.ipv4_subnet_ap else'0.0.0.0', + str(r.ipv4_gateway_ap) if r.ipv4_gateway_ap else'0.0.0.0', + str(r.ipv4_dns) if r.ipv4_dns else'0.0.0.0', + ), + 'mac': binascii.hexlify(r.mac_address_ap, ':').decode(), + 'txpower': r.tx_power, + 'hostname': r.hostname, + } + print(json.dumps(ap)) + + overall = { + 'active': r.enabled + } + print(json.dumps(overall)) + +wifi_status() +del wifi_status +""" + + out, err = self._interface.execute(command, mode=self.submitMode) + if err: + raise OSError(self._shortError(err)) + + stationStr, apStr, overallStr = out.decode("utf-8").splitlines() + station = json.loads(stationStr) + ap = json.loads(apStr) + overall = json.loads(overallStr) + return station, ap, overall + def createDevice(microPythonWidget, deviceType, vid, pid, boardName, serialNumber): """