src/eric7/MicroPython/Devices/CircuitPythonDevices.py

branch
mpy_network
changeset 9820
67597e003373
parent 9805
4a2657e29a32
child 9828
32c8a5b57332
--- 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):
     """

eric ide

mercurial