Created new branch <mpy_network>. mpy_network

Sat, 18 Feb 2023 10:11:44 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Sat, 18 Feb 2023 10:11:44 +0100
branch
mpy_network
changeset 9775
c6806d24468b
parent 9774
c7b712056146
child 9776
210bf87ae5c7

Created new branch <mpy_network>.

eric7.epj file | annotate | diff | comparison | revisions
src/eric7/MicroPython/Devices/DeviceBase.py file | annotate | diff | comparison | revisions
src/eric7/MicroPython/Devices/RP2040Devices.py file | annotate | diff | comparison | revisions
src/eric7/MicroPython/MicroPythonDeviceInterface.py file | annotate | diff | comparison | revisions
src/eric7/MicroPython/MicroPythonSerialPort.py file | annotate | diff | comparison | revisions
src/eric7/MicroPython/MicroPythonWidget.py file | annotate | diff | comparison | revisions
src/eric7/MicroPython/WifiStatusDialog.py file | annotate | diff | comparison | revisions
src/eric7/MicroPython/WifiStatusDialog.ui file | annotate | diff | comparison | revisions
--- a/eric7.epj	Sat Feb 18 09:21:42 2023 +0100
+++ b/eric7.epj	Sat Feb 18 10:11:44 2023 +0100
@@ -347,6 +347,7 @@
       "src/eric7/MicroPython/ShowModulesDialog.ui",
       "src/eric7/MicroPython/UF2FlashDialog.ui",
       "src/eric7/MicroPython/UnknownDevicesDialog.ui",
+      "src/eric7/MicroPython/WifiStatusDialog.ui",
       "src/eric7/MultiProject/AddProjectDialog.ui",
       "src/eric7/MultiProject/PropertiesDialog.ui",
       "src/eric7/Network/IRC/IrcChannelEditDialog.ui",
@@ -1312,6 +1313,7 @@
       "src/eric7/MicroPython/ShowModulesDialog.py",
       "src/eric7/MicroPython/UF2FlashDialog.py",
       "src/eric7/MicroPython/UnknownDevicesDialog.py",
+      "src/eric7/MicroPython/WifiStatusDialog.py",
       "src/eric7/MicroPython/__init__.py",
       "src/eric7/MultiProject/AddProjectDialog.py",
       "src/eric7/MultiProject/MultiProject.py",
--- a/src/eric7/MicroPython/Devices/DeviceBase.py	Sat Feb 18 09:21:42 2023 +0100
+++ b/src/eric7/MicroPython/Devices/DeviceBase.py	Sat Feb 18 10:11:44 2023 +0100
@@ -93,6 +93,9 @@
         if connected:
             with contextlib.suppress(OSError):
                 self._deviceData = self.__getDeviceData()
+                self._deviceData["wifi"], self._deviceData["wifi_type"] = self.hasWifi()
+                self._deviceData["bluetooth"] = self.hasBluetooth()
+                self._deviceData["ethernet"] = self.hasEthernet()
 
     def getDeviceType(self):
         """
@@ -103,14 +106,19 @@
         """
         return self._deviceType
 
-    def getDeviceData(self):
+    def getDeviceData(self, key=None):
         """
         Public method to get a copy of the determined device data.
 
+        @param key name of the data to get (None to get all data) (defaults to None)
+        @type str (optional)
         @return dictionary containing the essential device data
         @rtype dict
         """
-        return copy.deepcopy(self._deviceData)
+        if key is None:
+            return copy.deepcopy(self._deviceData)
+        else:
+            return self._deviceData[key]
 
     def checkDeviceData(self):
         """
@@ -1147,6 +1155,65 @@
             if err:
                 raise OSError(self._shortError(err))
 
+    ##################################################################
+    ## 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
+        """
+        return False, ""
+
+    def getWifiData(self):
+        """
+        Public method to get data related to the current WiFi status
+
+        @return tuple of two dictionaries containing the WiFi status data
+            for the WiFi client and access point
+        @rtype tuple of (dict, dict)
+        """
+        return {}, {}
+
+    def addDeviceWifiEntries(self, menu):
+        """
+        Public method to add device specific entries to the given menu.
+
+        @param menu reference to the context menu
+        @type QMenu
+        """
+        pass
+
+    ##################################################################
+    ## Methods below implement Ethernet related methods
+    ##################################################################
+
+    def hasEthernet(self):
+        """
+        Public method to check the availability of Ethernet.
+
+        @return flag indicating the availability of Ethernet
+        @rtype bool
+        """
+        return False
+
+    ##################################################################
+    ## Methods below implement Bluetooth related methods
+    ##################################################################
+
+    def hasBluetooth(self):
+        """
+        Public method to check the availability of Bluetooth.
+
+        @return flag indicating the availability of Bluetooth
+        @rtype bool
+        """
+        return False
 
 #
 # eflag: noqa = M613
--- a/src/eric7/MicroPython/Devices/RP2040Devices.py	Sat Feb 18 09:21:42 2023 +0100
+++ b/src/eric7/MicroPython/Devices/RP2040Devices.py	Sat Feb 18 10:11:44 2023 +0100
@@ -8,6 +8,9 @@
 (e.g. Raspberry Pi Pico).
 """
 
+import ast
+import json
+
 from PyQt6.QtCore import QUrl, pyqtSlot
 from PyQt6.QtNetwork import QNetworkRequest
 from PyQt6.QtWidgets import QMenu
@@ -41,6 +44,20 @@
 
         self.__createRP2040Menu()
 
+        self.__statusTranslations = {
+            "picow": {
+                -3: self.tr('wrong password'),
+                -2: self.tr('no access point found'),
+                -1: self.tr('connection failed'),
+                0: self.tr('idle'),
+                1: self.tr('connecting'),
+                3: self.tr('connection successful'),
+            },
+            "pimoroni": {
+            # TODO: not yet implemented
+            },
+        }
+
     def setButtons(self):
         """
         Public method to enable the supported action buttons.
@@ -340,6 +357,113 @@
     rtc.datetime(rtc_time[:7] + (0,))
 """
 
+    ##################################################################
+    ## Methods below implement network 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 network
+        if hasattr(network, 'WLAN'):
+            return True, 'picow'
+    except ImportError:
+        try:
+            import picowireless
+            if picowireless.get_fw_version() != '':
+                return True, 'pimoroni'
+        except ImportError:
+            pass
+
+    return False, ''
+
+print(has_wifi())
+del has_wifi
+"""
+        out, err = self._interface.execute(command, timeout=10000)
+        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 two dictionaries containing the WiFi status data
+            for the WiFi client and access point
+        @rtype tuple of (dict, dict)
+        """
+        if self._deviceData["wifi_type"] == "picow":
+            command = """
+def wifi_status():
+    import ubinascii
+    import ujson
+    import network
+
+    wifi = network.WLAN(network.STA_IF)
+    station = {
+        'active': wifi.active(),
+        'connected': wifi.isconnected(),
+        'status': wifi.status(),
+        'ifconfig': wifi.ifconfig(),
+        'mac': ubinascii.hexlify(wifi.config('mac'), ':').decode(),
+        'channel': wifi.config('channel'),
+        'txpower': wifi.config('txpower'),
+    }
+    print(ujson.dumps(station))
+
+    wifi = network.WLAN(network.AP_IF)
+    ap = {
+        'active': wifi.active(),
+        'connected': wifi.isconnected(),
+        'status': wifi.status(),
+        'ifconfig': wifi.ifconfig(),
+        'mac': ubinascii.hexlify(wifi.config('mac'), ':').decode(),
+        'channel': wifi.config('channel'),
+        'txpower': wifi.config('txpower'),
+        'essid': wifi.config('essid'),
+    }
+    print(ujson.dumps(ap))
+
+wifi_status()
+del wifi_status
+"""
+        elif self._deviceData["wifi_type"] == "pimoroni":
+            # TODO: not yet implemented
+            pass
+        else:
+            return super().getWifiData()
+
+        out, err = self._interface.execute(command, timeout=10000)
+        if err:
+            raise OSError(self._shortError(err))
+
+        stationStr, apStr = out.decode("utf-8").splitlines()
+        station = json.loads(stationStr)
+        ap = json.loads(apStr)
+        try:
+            station["status"] = self.__statusTranslations[
+                self._deviceData["wifi_type"]
+            ][station["status"]]
+        except KeyError:
+            station["status"] = str(station["status"])
+        try:
+            ap["status"] = self.__statusTranslations[
+                self._deviceData["wifi_type"]
+            ][ap["status"]]
+        except KeyError:
+            ap["status"] = str(ap["status"])
+        return station, ap
+
 
 def createDevice(microPythonWidget, deviceType, vid, pid, boardName, serialNumber):
     """
--- a/src/eric7/MicroPython/MicroPythonDeviceInterface.py	Sat Feb 18 09:21:42 2023 +0100
+++ b/src/eric7/MicroPython/MicroPythonDeviceInterface.py	Sat Feb 18 10:11:44 2023 +0100
@@ -189,7 +189,7 @@
 
         return True
 
-    def execute(self, commands):
+    def execute(self, commands, timeout=0):
         """
         Public method to send commands to the connected device and return the
         result.
@@ -198,6 +198,9 @@
 
         @param commands list of commands to be executed
         @type str or list of str
+        @param timeout per command timeout in milliseconds (0 for configured default)
+            (defaults to 0)
+        @type int (optional)
         @return tuple containing stdout and stderr output of the device
         @rtype tuple of (bytes, bytes)
         """
@@ -239,7 +242,7 @@
                     )
 
                 # read until prompt
-                response = self.__serial.readUntil(b"\x04>")
+                response = self.__serial.readUntil(b"\x04>", timeout=timeout)
                 if self.__serial.hasTimedOut():
                     self.__blockReadyRead = False
                     return b"", b"Timeout while processing commands."
--- a/src/eric7/MicroPython/MicroPythonSerialPort.py	Sat Feb 18 09:21:42 2023 +0100
+++ b/src/eric7/MicroPython/MicroPythonSerialPort.py	Sat Feb 18 10:11:44 2023 +0100
@@ -92,21 +92,27 @@
         """
         return self.__timedOut
 
-    def readUntil(self, expected=b"\n", size=None):
+    def readUntil(self, expected=b"\n", size=None, timeout=0):
         r"""
         Public method to read data until an expected sequence is found
         (default: \n) or a specific size is exceeded.
 
         @param expected expected bytes sequence
         @type bytes
-        @param size maximum data to be read
-        @type int
+        @param size maximum data to be read (defaults to None)
+        @type int (optional)
+        @param timeout timeout in milliseconds (0 for configured default)
+            (defaults to 0)
+        @type int (optional)
         @return bytes read from the device including the expected sequence
         @rtype bytes
         """
         data = bytearray()
         self.__timedOut = False
 
+        if timeout == 0:
+            timeout = self.__timeout
+
         t = QTime.currentTime()
         while True:
             QCoreApplication.processEvents(
@@ -119,7 +125,7 @@
                     break
                 if size is not None and len(data) >= size:
                     break
-            if t.msecsTo(QTime.currentTime()) > self.__timeout:
+            if t.msecsTo(QTime.currentTime()) > timeout:
                 self.__timedOut = True
                 break
 
--- a/src/eric7/MicroPython/MicroPythonWidget.py	Sat Feb 18 09:21:42 2023 +0100
+++ b/src/eric7/MicroPython/MicroPythonWidget.py	Sat Feb 18 10:11:44 2023 +0100
@@ -1436,6 +1436,13 @@
             else:
                 downloadMenu = None
 
+        # prepare the WiFi menu
+        if self.__device and self.__connected and self.__device.getDeviceData("wifi"):
+            wifiMenu = QMenu(self.tr("WiFi Functions"), self.__superMenu)
+            wifiMenu.addAction(self.tr("Show WiFi Status"), self.__showWifiStatus)
+        else:
+            wifiMenu = None
+
         # populate the super menu
         hasTime = self.__device.hasTimeCommands() if self.__device else False
 
@@ -1479,6 +1486,9 @@
         if self.__device:
             self.__device.addDeviceMenuEntries(self.__superMenu)
             self.__superMenu.addSeparator()
+            if wifiMenu is not None:
+                self.__superMenu.addMenu(wifiMenu)
+                self.__superMenu.addSeparator()
             if downloadMenu is None:
                 # generic download action
                 self.__superMenu.addAction(
@@ -1988,3 +1998,22 @@
                 dlg.show()
             except Exception as exc:
                 self.__showError("getModules()", str(exc))
+
+    ############################################################################
+    ## WiFi related methods below
+    ############################################################################
+
+    def __showWifiStatus(self):
+        """
+        Private method to show a dialog with the WiFi status of the current device.
+        """
+        from .WifiStatusDialog import WifiStatusDialog
+
+        try:
+            clientStatus, apStatus = self.__device.getWifiData()
+
+            dlg = WifiStatusDialog(clientStatus, apStatus)
+            dlg.exec()
+        except Exception as exc:
+            self.__showError("getWifiData()", str(exc))
+        
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/MicroPython/WifiStatusDialog.py	Sat Feb 18 10:11:44 2023 +0100
@@ -0,0 +1,136 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2023 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a dialog to show the WiFi status of the connected device.
+"""
+
+from PyQt6.QtWidgets import QDialog, QTreeWidgetItem
+
+from .Ui_WifiStatusDialog import Ui_WifiStatusDialog
+
+
+class WifiStatusDialog(QDialog, Ui_WifiStatusDialog):
+    """
+    Class implementing a dialog to show the WiFi status of the connected device.
+    """
+
+    def __init__(self, clientStatus, apStatus, parent=None):
+        """
+        Constructor
+
+        @param clientStatus dictionary containing the WiFi status data of the
+            client interface
+        @type dict
+        @param apStatus dictionary containing the WiFi status data of the
+            access point interface
+        @type dict
+        @param parent reference to the parent widget (defaults to None)
+        @type QWidget (optional)
+        """
+        super().__init__(parent)
+        self.setupUi(self)
+
+        self.statusTree.setColumnCount(2)
+
+        # client interface
+        if clientStatus:
+            header = self.__createHeader(self.tr("Client"))
+            QTreeWidgetItem(
+                header,
+                [
+                    self.tr("Active"),
+                    self.tr("Yes") if clientStatus["active"] else self.tr("No"),
+                ],
+            )
+            if clientStatus["active"]:
+                QTreeWidgetItem(
+                    header,
+                    [
+                        self.tr("Connected"),
+                        self.tr("Yes") if clientStatus["connected"] else self.tr("No"),
+                    ],
+                )
+                QTreeWidgetItem(header, [self.tr("Status"), clientStatus["status"]])
+                QTreeWidgetItem(
+                    header, [self.tr("IPv4 Address"), clientStatus["ifconfig"][0]]
+                )
+                QTreeWidgetItem(
+                    header, [self.tr("Netmask"), clientStatus["ifconfig"][1]]
+                )
+                QTreeWidgetItem(
+                    header, [self.tr("Gateway"), clientStatus["ifconfig"][2]]
+                )
+                QTreeWidgetItem(header, [self.tr("DNS"), clientStatus["ifconfig"][3]])
+                QTreeWidgetItem(header, [self.tr("MAC-Address"), clientStatus["mac"]])
+                QTreeWidgetItem(
+                    header, [self.tr("Channel"), str(clientStatus["channel"])]
+                )
+                QTreeWidgetItem(
+                    header,
+                    [
+                        self.tr("Tx-Power"),
+                        self.tr("{0} dBm").format(clientStatus["txpower"]),
+                    ],
+                )
+
+        # access point interface
+        if apStatus:
+            header = self.__createHeader(self.tr("Access Point"))
+            QTreeWidgetItem(
+                header,
+                [
+                    self.tr("Active"),
+                    self.tr("Yes") if apStatus["active"] else self.tr("No"),
+                ],
+            )
+            if apStatus["active"]:
+                QTreeWidgetItem(
+                    header,
+                    [
+                        self.tr("Connected"),
+                        self.tr("Yes") if apStatus["connected"] else self.tr("No"),
+                    ],
+                )
+                QTreeWidgetItem(header, [self.tr("Status"), apStatus["status"]])
+                QTreeWidgetItem(
+                    header, [self.tr("IPv4 Address"), apStatus["ifconfig"][0]]
+                )
+                QTreeWidgetItem(header, [self.tr("Netmask"), apStatus["ifconfig"][1]])
+                QTreeWidgetItem(header, [self.tr("Gateway"), apStatus["ifconfig"][2]])
+                QTreeWidgetItem(header, [self.tr("DNS"), apStatus["ifconfig"][3]])
+                QTreeWidgetItem(header, [self.tr("SSID"), apStatus["essid"]])
+                QTreeWidgetItem(header, [self.tr("MAC-Address"), apStatus["mac"]])
+                QTreeWidgetItem(header, [self.tr("Channel"), str(apStatus["channel"])])
+                QTreeWidgetItem(
+                    header,
+                    [
+                        self.tr("Tx-Power"),
+                        self.tr("{0} dBm").format(apStatus["txpower"]),
+                    ],
+                )
+
+        for col in range(self.statusTree.columnCount()):
+            self.statusTree.resizeColumnToContents(col)
+
+    def __createHeader(self, headerText):
+        """
+        Private method to create a header item.
+
+        @param headerText text for the header item
+        @type str
+        @return reference to the created header item
+        @rtype QTreeWidgetItem
+        """
+        headerItem = QTreeWidgetItem(self.statusTree, [headerText])
+        headerItem.setExpanded(True)
+        headerItem.setFirstColumnSpanned(True)
+
+        font = headerItem.font(0)
+        font.setBold(True)
+
+        headerItem.setFont(0, font)
+
+        return headerItem
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/MicroPython/WifiStatusDialog.ui	Sat Feb 18 10:11:44 2023 +0100
@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>WifiStatusDialog</class>
+ <widget class="QDialog" name="WifiStatusDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>600</width>
+    <height>650</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>WiFi Status</string>
+  </property>
+  <property name="sizeGripEnabled">
+   <bool>true</bool>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <widget class="QLabel" name="label">
+     <property name="font">
+      <font>
+       <pointsize>14</pointsize>
+       <bold>true</bold>
+      </font>
+     </property>
+     <property name="text">
+      <string>WiFi Status</string>
+     </property>
+     <property name="alignment">
+      <set>Qt::AlignCenter</set>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QTreeWidget" name="statusTree">
+     <property name="headerHidden">
+      <bool>true</bool>
+     </property>
+     <column>
+      <property name="text">
+       <string notr="true">1</string>
+      </property>
+     </column>
+    </widget>
+   </item>
+   <item>
+    <widget class="QDialogButtonBox" name="buttonBox">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="standardButtons">
+      <set>QDialogButtonBox::Ok</set>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>WifiStatusDialog</receiver>
+   <slot>accept()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>248</x>
+     <y>254</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>157</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>rejected()</signal>
+   <receiver>WifiStatusDialog</receiver>
+   <slot>reject()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>316</x>
+     <y>260</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>286</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+</ui>

eric ide

mercurial