MicroPython mpy_network

Fri, 24 Feb 2023 14:11:20 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Fri, 24 Feb 2023 14:11:20 +0100
branch
mpy_network
changeset 9797
3be7b2326e2c
parent 9795
11b4d39d7584
child 9798
4402d76c5fa9

MicroPython
- added functionality to start the access point interface with a given IPv4 configuration

eric7.epj file | annotate | diff | comparison | revisions
src/eric7/EricNetwork/EricIPv4InputWidget.py file | annotate | diff | comparison | revisions
src/eric7/EricNetwork/EricIPv4InputWidget.ui file | annotate | diff | comparison | revisions
src/eric7/MicroPython/Devices/DeviceBase.py file | annotate | diff | comparison | revisions
src/eric7/MicroPython/Devices/EspDevices.py file | annotate | diff | comparison | revisions
src/eric7/MicroPython/Devices/RP2040Devices.py file | annotate | diff | comparison | revisions
src/eric7/MicroPython/WifiDialogs/WifiApConfigDialog.py file | annotate | diff | comparison | revisions
src/eric7/MicroPython/WifiDialogs/WifiApConfigDialog.ui file | annotate | diff | comparison | revisions
src/eric7/MicroPython/WifiDialogs/WifiController.py file | annotate | diff | comparison | revisions
src/eric7/MicroPython/WifiDialogs/WifiStatusDialog.py file | annotate | diff | comparison | revisions
src/eric7/Preferences/ConfigurationPages/MicroPythonPage.py file | annotate | diff | comparison | revisions
src/eric7/Preferences/ConfigurationPages/MicroPythonPage.ui file | annotate | diff | comparison | revisions
src/eric7/Preferences/__init__.py file | annotate | diff | comparison | revisions
--- a/eric7.epj	Thu Feb 23 13:31:55 2023 +0100
+++ b/eric7.epj	Fri Feb 24 14:11:20 2023 +0100
@@ -310,6 +310,7 @@
       "src/eric7/Debugger/StartRunDialog.ui",
       "src/eric7/Debugger/VariableDetailDialog.ui",
       "src/eric7/Debugger/VariablesFilterDialog.ui",
+      "src/eric7/EricNetwork/EricIPv4InputWidget.ui",
       "src/eric7/EricNetwork/EricSslCertificateSelectionDialog.ui",
       "src/eric7/EricNetwork/EricSslCertificatesDialog.ui",
       "src/eric7/EricNetwork/EricSslCertificatesInfoDialog.ui",
@@ -1155,6 +1156,7 @@
       "src/eric7/EricNetwork/EricFtp.py",
       "src/eric7/EricNetwork/EricGoogleMail.py",
       "src/eric7/EricNetwork/EricGoogleMailHelpers.py",
+      "src/eric7/EricNetwork/EricIPv4InputWidget.py",
       "src/eric7/EricNetwork/EricJsonClient.py",
       "src/eric7/EricNetwork/EricJsonServer.py",
       "src/eric7/EricNetwork/EricJsonStreamReader.py",
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/EricNetwork/EricIPv4InputWidget.py	Fri Feb 24 14:11:20 2023 +0100
@@ -0,0 +1,186 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2023 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a widget to enter an IPv4 address.
+"""
+
+import ipaddress
+
+from PyQt6.QtCore import pyqtSignal, pyqtSlot, QRegularExpression, QEvent, Qt
+from PyQt6.QtGui import QRegularExpressionValidator
+from PyQt6.QtWidgets import QWidget
+
+from eric7.EricGui import EricPixmapCache
+
+from .Ui_EricIPv4InputWidget import Ui_EricIPv4InputWidget
+
+
+class EricIPv4InputWidget(QWidget, Ui_EricIPv4InputWidget):
+    """
+    Class implementing a widget to enter an IPv4 address.
+
+    @signal addressChanged() emitted to indicate a change of the entered IPv4 address
+    """
+    addressChanged = pyqtSignal()
+
+    def __init__(self, parent=None):
+        """
+        Constructor
+
+        @param parent reference to the parent widget (defaults to None)
+        @type QWidget (optional)
+        """
+        super().__init__(parent)
+        self.setupUi(self)
+
+        self.clearButton.setIcon(EricPixmapCache.getIcon("clearLeft"))
+        self.clearButton.clicked.connect(self.__clear)
+
+        ipRange = r"(?:[0-1]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])"
+
+        self.ip1Edit.setValidator(
+            QRegularExpressionValidator(QRegularExpression(ipRange))
+        )
+        self.ip2Edit.setValidator(
+            QRegularExpressionValidator(QRegularExpression(ipRange))
+        )
+        self.ip3Edit.setValidator(
+            QRegularExpressionValidator(QRegularExpression(ipRange))
+        )
+        self.ip4Edit.setValidator(
+            QRegularExpressionValidator(QRegularExpression(ipRange))
+        )
+
+        self.ip1Edit.installEventFilter(self)
+        self.ip2Edit.installEventFilter(self)
+        self.ip3Edit.installEventFilter(self)
+
+        self.ip1Edit.textChanged.connect(self.addressChanged)
+        self.ip2Edit.textChanged.connect(self.addressChanged)
+        self.ip3Edit.textChanged.connect(self.addressChanged)
+        self.ip4Edit.textChanged.connect(self.addressChanged)
+
+    def eventFilter(self, obj, evt):
+        """
+        Public method to filter pressing '.' to give focus to the next input field.
+
+        @param obj reference to the object
+        @type QObject
+        @param evt reference to the event object
+        @type QEvent
+        """
+        if evt.type() == QEvent.Type.KeyPress:
+            if evt.text() == '.':
+                if obj is self.ip1Edit:
+                    nextWidget = self.ip2Edit
+                elif obj is self.ip2Edit:
+                    nextWidget = self.ip3Edit
+                elif obj is self.ip3Edit:
+                    nextWidget = self.ip4Edit
+                else:
+                    nextWidget = None
+                if nextWidget:
+                    nextWidget.setFocus(Qt.FocusReason.TabFocusReason)
+                    return True
+
+        return super().eventFilter(obj, evt)
+
+    def hasAcceptableInput(self):
+        """
+        Public method to check, if the input is acceptable.
+
+        @return flag indicating acceptable input
+        @rtype bool
+        """
+        try:
+            ipaddress.IPv4Address(self.text())
+        except ipaddress.AddressValueError:
+            # leading zeros are not allowed
+            return False
+
+        return (
+            self.ip1Edit.hasAcceptableInput()
+            and self.ip2Edit.hasAcceptableInput()
+            and self.ip3Edit.hasAcceptableInput()
+            and self.ip4Edit.hasAcceptableInput()
+        )
+
+    def text(self):
+        """
+        Public method to get the IPv4 address as a string.
+
+        @return IPv4 address
+        @rtype str
+        """
+        return "{0}.{1}.{2}.{3}".format(
+            self.ip1Edit.text(),
+            self.ip2Edit.text(),
+            self.ip3Edit.text(),
+            self.ip4Edit.text(),
+        )
+
+    def setText(self, address):
+        """
+        Public method to set the IPv4 address given a string.
+
+        @param address IPv4 address
+        @type str
+        """
+        if address:
+            try:
+                ipaddress.IPv4Address(address)
+            except ipaddress.AddressValueError as err:
+                raise ValueError(str(err))
+
+            addressParts = address.split(".")
+            self.ip1Edit.setText(addressParts[0])
+            self.ip2Edit.setText(addressParts[1])
+            self.ip3Edit.setText(addressParts[2])
+            self.ip4Edit.setText(addressParts[3])
+        else:
+            self.clear()
+
+    def address(self):
+        """
+        Public method to get the IPv4 address as an ipaddress.IPv4Address object.
+
+        @return IPv4 address
+        @rtype ipaddress.IPv4Address
+        """
+        try:
+            return ipaddress.IPv4Address(self.text())
+        except ipaddress.AddressValueError as err:
+            raise ValueError(str(err))
+
+    def setAddress(self, address):
+        """
+        Public method to set the IPv4 address given an ipaddress.IPv4Address object.
+
+        @param address IPv4 address
+        @type ipaddress.IPv4Address
+        """
+        if address:
+            self.setText(str(address))
+        else:
+            self.clear()
+
+    @pyqtSlot()
+    def clear(self):
+        """
+        Public slot to clear the input fields.
+        """
+        self.ip1Edit.clear()
+        self.ip2Edit.clear()
+        self.ip3Edit.clear()
+        self.ip4Edit.clear()
+
+    @pyqtSlot()
+    def __clear(self):
+        """
+        Private slot to handle the clear button press.
+        """
+        self.clear()
+        self.ip1Edit.setFocus(Qt.FocusReason.OtherFocusReason)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/EricNetwork/EricIPv4InputWidget.ui	Fri Feb 24 14:11:20 2023 +0100
@@ -0,0 +1,122 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>EricIPv4InputWidget</class>
+ <widget class="QWidget" name="EricIPv4InputWidget">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>283</width>
+    <height>26</height>
+   </rect>
+  </property>
+  <layout class="QHBoxLayout" name="horizontalLayout">
+   <property name="leftMargin">
+    <number>0</number>
+   </property>
+   <property name="topMargin">
+    <number>0</number>
+   </property>
+   <property name="rightMargin">
+    <number>0</number>
+   </property>
+   <property name="bottomMargin">
+    <number>0</number>
+   </property>
+   <item>
+    <widget class="QLineEdit" name="ip1Edit">
+     <property name="maximumSize">
+      <size>
+       <width>50</width>
+       <height>16777215</height>
+      </size>
+     </property>
+     <property name="maxLength">
+      <number>3</number>
+     </property>
+     <property name="alignment">
+      <set>Qt::AlignCenter</set>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QLabel" name="label">
+     <property name="text">
+      <string> . </string>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QLineEdit" name="ip2Edit">
+     <property name="maximumSize">
+      <size>
+       <width>50</width>
+       <height>16777215</height>
+      </size>
+     </property>
+     <property name="maxLength">
+      <number>3</number>
+     </property>
+     <property name="alignment">
+      <set>Qt::AlignCenter</set>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QLabel" name="label_2">
+     <property name="text">
+      <string> . </string>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QLineEdit" name="ip3Edit">
+     <property name="maximumSize">
+      <size>
+       <width>50</width>
+       <height>16777215</height>
+      </size>
+     </property>
+     <property name="maxLength">
+      <number>3</number>
+     </property>
+     <property name="alignment">
+      <set>Qt::AlignCenter</set>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QLabel" name="label_3">
+     <property name="text">
+      <string> . </string>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QLineEdit" name="ip4Edit">
+     <property name="maximumSize">
+      <size>
+       <width>50</width>
+       <height>16777215</height>
+      </size>
+     </property>
+     <property name="maxLength">
+      <number>3</number>
+     </property>
+     <property name="alignment">
+      <set>Qt::AlignCenter</set>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QToolButton" name="clearButton">
+     <property name="toolTip">
+      <string>Press to clear the entered IPv4 address</string>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
--- a/src/eric7/MicroPython/Devices/DeviceBase.py	Thu Feb 23 13:31:55 2023 +0100
+++ b/src/eric7/MicroPython/Devices/DeviceBase.py	Fri Feb 24 14:11:20 2023 +0100
@@ -1285,7 +1285,7 @@
         """
         return True, ""
 
-    def startAccessPoint(self, ssid, security=None, password=None):
+    def startAccessPoint(self, ssid, security=None, password=None, ifconfig=None):
         """
         Public method to start the access point interface.
 
@@ -1295,6 +1295,9 @@
         @type int (optional)
         @param password password (defaults to None)
         @type str (optional)
+        @param ifconfig IPv4 configuration for the access point if not default
+            (IPv4 address, netmask, gateway address, DNS server address)
+        @type tuple of (str, str, str, str)
         @return tuple containing a flag indicating success and an error message
         @rtype tuple of (bool, str)
         """
--- a/src/eric7/MicroPython/Devices/EspDevices.py	Thu Feb 23 13:31:55 2023 +0100
+++ b/src/eric7/MicroPython/Devices/EspDevices.py	Fri Feb 24 14:11:20 2023 +0100
@@ -685,9 +685,10 @@
         'mac': ubinascii.hexlify(wifi.config('mac'), ':').decode(),
     }
     if wifi.active():
-        station['txpower'] = wifi.config('txpower')
-    else:
-        station['txpower'] = 0
+        try:
+            station['txpower'] = wifi.config('txpower')
+        except ValueError:
+            pass
     print(ujson.dumps(station))
 
     wifi = network.WLAN(network.AP_IF)
@@ -701,9 +702,10 @@
         'essid': wifi.config('essid'),
     }
     if wifi.active():
-        ap['txpower'] = wifi.config('txpower')
-    else:
-        ap['txpower'] = 0
+        try:
+            ap['txpower'] = wifi.config('txpower')
+        except ValueError:
+            pass
     print(ujson.dumps(ap))
 
 wifi_status()
@@ -1017,7 +1019,7 @@
         else:
             return out.decode("utf-8").strip() == "True", ""
 
-    def startAccessPoint(self, ssid, security=None, password=None):
+    def startAccessPoint(self, ssid, security=None, password=None, ifconfig=None):
         """
         Public method to start the access point interface.
 
@@ -1027,6 +1029,9 @@
         @type int (optional)
         @param password password (defaults to None)
         @type str (optional)
+        @param ifconfig IPv4 configuration for the access point if not default
+            (IPv4 address, netmask, gateway address, DNS server address)
+        @type tuple of (str, str, str, str)
         @return tuple containing a flag indicating success and an error message
         @rtype tuple of (bool, str)
         """
@@ -1037,21 +1042,23 @@
             security = 4  # security >4 cause an error thrown by the ESP32
 
         command = """
-def start_ap():
+def start_ap(ssid, authmode, password, ifconfig):
     import network
 
     ap = network.WLAN(network.AP_IF)
     ap.active(False)
+    if ifconfig:
+        ap.ifconfig(ifconfig)
     ap.active(True)
     try:
-        ap.config(ssid={0}, authmode={1}, password={2})
+        ap.config(ssid=ssid, authmode=authmode, password=password)
     except:
-        ap.config(essid={0}, authmode={1}, password={2})
+        ap.config(essid=ssid, authmode=authmode, password=password)
 
-start_ap()
+start_ap({0}, {1}, {2}, {3})
 del start_ap
 """.format(
-                repr(ssid), security, repr(password)
+                repr(ssid), security, repr(password), ifconfig
             )
 
         out, err = self._interface.execute(command, timeout=15000)
--- a/src/eric7/MicroPython/Devices/RP2040Devices.py	Thu Feb 23 13:31:55 2023 +0100
+++ b/src/eric7/MicroPython/Devices/RP2040Devices.py	Fri Feb 24 14:11:20 2023 +0100
@@ -813,7 +813,7 @@
         else:
             return out.decode("utf-8").strip() == "True", ""
 
-    def startAccessPoint(self, ssid, security=None, password=None):
+    def startAccessPoint(self, ssid, security=None, password=None, ifconfig=None):
         """
         Public method to start the access point interface.
 
@@ -823,6 +823,9 @@
         @type int (optional)
         @param password password (defaults to None)
         @type str (optional)
+        @param ifconfig IPv4 configuration for the access point if not default
+            (IPv4 address, netmask, gateway address, DNS server address)
+        @type tuple of (str, str, str, str)
         @return tuple containing a flag indicating success and an error message
         @rtype tuple of (bool, str)
         """
@@ -835,23 +838,29 @@
             if security:
                 security = 4  # Pico W supports just WPA/WPA2
             command = """
-def start_ap():
+def start_ap(ssid, security, password, ifconfig, country):
     import network
     import rp2
     from time import sleep
 
-    rp2.country({3})
+    rp2.country(country)
 
     ap = network.WLAN(network.AP_IF)
-    ap.config(ssid={0}, security={1}, password={2})
     ap.active(True)
+    if ifconfig:
+        ap.ifconfig(ifconfig)
+    ap.config(ssid=ssid, security=security, password=password)
     sleep(0.1)
     print(ap.isconnected())
 
-start_ap()
+start_ap({0}, {1}, {2}, {3}, {4})
 del start_ap
 """.format(
-                repr(ssid), security, repr(password), repr(country if country else "XX")
+                repr(ssid),
+                security,
+                repr(password),
+                ifconfig,
+                repr(country if country else "XX"),
             )
         elif self._deviceData["wifi_type"] == "pimoroni":
             # TODO: not yet implemented
--- a/src/eric7/MicroPython/WifiDialogs/WifiApConfigDialog.py	Thu Feb 23 13:31:55 2023 +0100
+++ b/src/eric7/MicroPython/WifiDialogs/WifiApConfigDialog.py	Fri Feb 24 14:11:20 2023 +0100
@@ -21,10 +21,12 @@
     Class implementing a dialog to configure the Access Point interface.
     """
 
-    def __init__(self, parent=None):
+    def __init__(self, withIP, parent=None):
         """
         Constructor
 
+        @param withIP flag indicating to ask the user for an IP configuration
+        @type bool
         @param parent reference to the parent widget (defaults to None)
         @type QWidget (optional)
         """
@@ -45,11 +47,7 @@
 
         self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setEnabled(False)
 
-        self.apSsidEdit.textChanged.connect(self.__updateOk)
-        self.apPasswordEdit.textChanged.connect(self.__updateOk)
-        self.apSecurityComboBox.currentIndexChanged.connect(self.__updateOk)
-
-        # populate the field with data saved to the preferences
+        # populate the WiFi fields with data saved to the preferences
         self.apSsidEdit.setText(Preferences.getMicroPython("WifiApName"))
         self.apPasswordEdit.setText(Preferences.getMicroPython("WifiApPassword"))
         index = self.apSecurityComboBox.findData(
@@ -59,6 +57,29 @@
             index = 5  # default it to WPA/WPA2 in case of an issue
         self.apSecurityComboBox.setCurrentIndex(index)
 
+        self.__withIP = withIP
+
+        self.ipv4GroupBox.setVisible(withIP)
+        if withIP:
+            # populate the IPv4 configuration with data saved to the preferences
+            self.addressEdit.setText(Preferences.getMicroPython("WifiApAddress"))
+            self.netmaskEdit.setText(Preferences.getMicroPython("WifiApNetmask"))
+            self.gatewayEdit.setText(Preferences.getMicroPython("WifiApGateway"))
+            self.dnsEdit.setText(Preferences.getMicroPython("WifiApDNS"))
+
+            # connect the IPv4 fields
+            self.addressEdit.addressChanged.connect(self.__updateOk)
+            self.netmaskEdit.addressChanged.connect(self.__updateOk)
+            self.gatewayEdit.addressChanged.connect(self.__updateOk)
+            self.dnsEdit.addressChanged.connect(self.__updateOk)
+
+        # connect the WiFi fields
+        self.apSsidEdit.textChanged.connect(self.__updateOk)
+        self.apPasswordEdit.textChanged.connect(self.__updateOk)
+        self.apSecurityComboBox.currentIndexChanged.connect(self.__updateOk)
+
+        self.__updateOk()
+
         msh = self.minimumSizeHint()
         self.resize(max(self.width(), msh.width()), msh.height())
 
@@ -71,6 +92,13 @@
         if self.apSecurityComboBox.currentData() != 0:
             # security needs a password
             enable &= bool(self.apPasswordEdit.text())
+        if self.__withIP:
+            enable &= (
+                self.addressEdit.hasAcceptableInput()
+                and self.netmaskEdit.hasAcceptableInput()
+                and self.gatewayEdit.hasAcceptableInput()
+                and self.dnsEdit.hasAcceptableInput()
+            )
 
         self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setEnabled(enable)
 
@@ -99,9 +127,20 @@
             and a flag indicating to save the parameters to the settings
         @rtype tuple of (str, str, int, bool)
         """
+        if self.__withIP:
+            ifconfig = (
+                self.addressEdit.text(),
+                self.netmaskEdit.text(),
+                self.gatewayEdit.text(),
+                self.dnsEdit.text(),
+            )
+        else:
+            ifconfig = ("", "", "", "")
+
         return (
             self.apSsidEdit.text(),
             self.apPasswordEdit.text(),
             self.apSecurityComboBox.currentData(),
             self.rememberCheckBox.isChecked(),
+            ifconfig,
         )
--- a/src/eric7/MicroPython/WifiDialogs/WifiApConfigDialog.ui	Thu Feb 23 13:31:55 2023 +0100
+++ b/src/eric7/MicroPython/WifiDialogs/WifiApConfigDialog.ui	Fri Feb 24 14:11:20 2023 +0100
@@ -7,7 +7,7 @@
     <x>0</x>
     <y>0</y>
     <width>400</width>
-    <height>172</height>
+    <height>315</height>
    </rect>
   </property>
   <property name="windowTitle">
@@ -16,7 +16,7 @@
   <property name="sizeGripEnabled">
    <bool>true</bool>
   </property>
-  <layout class="QGridLayout" name="gridLayout">
+  <layout class="QGridLayout" name="gridLayout_2">
    <item row="0" column="0">
     <widget class="QLabel" name="label_10">
      <property name="text">
@@ -96,6 +96,88 @@
     </layout>
    </item>
    <item row="3" column="0" colspan="3">
+    <widget class="QGroupBox" name="ipv4GroupBox">
+     <property name="title">
+      <string>IPv4 Configuration</string>
+     </property>
+     <layout class="QHBoxLayout" name="horizontalLayout">
+      <item>
+       <layout class="QGridLayout" name="gridLayout">
+        <item row="0" column="0">
+         <widget class="QLabel" name="label">
+          <property name="text">
+           <string>Address:</string>
+          </property>
+         </widget>
+        </item>
+        <item row="0" column="1">
+         <widget class="EricIPv4InputWidget" name="addressEdit" native="true">
+          <property name="focusPolicy">
+           <enum>Qt::NoFocus</enum>
+          </property>
+         </widget>
+        </item>
+        <item row="1" column="0">
+         <widget class="QLabel" name="label_2">
+          <property name="text">
+           <string>Netmask:</string>
+          </property>
+         </widget>
+        </item>
+        <item row="1" column="1">
+         <widget class="EricIPv4InputWidget" name="netmaskEdit" native="true">
+          <property name="focusPolicy">
+           <enum>Qt::NoFocus</enum>
+          </property>
+         </widget>
+        </item>
+        <item row="2" column="0">
+         <widget class="QLabel" name="label_3">
+          <property name="text">
+           <string>Gateway:</string>
+          </property>
+         </widget>
+        </item>
+        <item row="2" column="1">
+         <widget class="EricIPv4InputWidget" name="gatewayEdit" native="true">
+          <property name="focusPolicy">
+           <enum>Qt::NoFocus</enum>
+          </property>
+         </widget>
+        </item>
+        <item row="3" column="0">
+         <widget class="QLabel" name="label_4">
+          <property name="text">
+           <string>DNS:</string>
+          </property>
+         </widget>
+        </item>
+        <item row="3" column="1">
+         <widget class="EricIPv4InputWidget" name="dnsEdit" native="true">
+          <property name="focusPolicy">
+           <enum>Qt::NoFocus</enum>
+          </property>
+         </widget>
+        </item>
+       </layout>
+      </item>
+      <item>
+       <spacer name="horizontalSpacer">
+        <property name="orientation">
+         <enum>Qt::Horizontal</enum>
+        </property>
+        <property name="sizeHint" stdset="0">
+         <size>
+          <width>273</width>
+          <height>20</height>
+         </size>
+        </property>
+       </spacer>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item row="4" column="0" colspan="3">
     <widget class="QCheckBox" name="rememberCheckBox">
      <property name="toolTip">
       <string>Select to remember the entered connection parameters</string>
@@ -105,7 +187,7 @@
      </property>
     </widget>
    </item>
-   <item row="4" column="0" colspan="3">
+   <item row="5" column="0" colspan="3">
     <widget class="QDialogButtonBox" name="buttonBox">
      <property name="orientation">
       <enum>Qt::Horizontal</enum>
@@ -117,11 +199,23 @@
    </item>
   </layout>
  </widget>
+ <customwidgets>
+  <customwidget>
+   <class>EricIPv4InputWidget</class>
+   <extends>QWidget</extends>
+   <header>eric7/EricNetwork/EricIPv4InputWidget.h</header>
+   <container>1</container>
+  </customwidget>
+ </customwidgets>
  <tabstops>
   <tabstop>apSsidEdit</tabstop>
   <tabstop>apPasswordEdit</tabstop>
   <tabstop>apShowPasswordButton</tabstop>
   <tabstop>apSecurityComboBox</tabstop>
+  <tabstop>addressEdit</tabstop>
+  <tabstop>netmaskEdit</tabstop>
+  <tabstop>gatewayEdit</tabstop>
+  <tabstop>dnsEdit</tabstop>
   <tabstop>rememberCheckBox</tabstop>
  </tabstops>
  <resources/>
--- a/src/eric7/MicroPython/WifiDialogs/WifiController.py	Thu Feb 23 13:31:55 2023 +0100
+++ b/src/eric7/MicroPython/WifiDialogs/WifiController.py	Fri Feb 24 14:11:20 2023 +0100
@@ -57,6 +57,9 @@
         wifiMenu.addSeparator()
         wifiMenu.addAction(self.tr("Start WiFi Access Point"), self.__startAccessPoint)
         wifiMenu.addAction(
+            self.tr("Start WiFi Access Point with IP"), self.__startAccessPointIP
+        )
+        wifiMenu.addAction(
             self.tr("Show Connected Clients"), self.__showConnectedClients
         )
         wifiMenu.addAction(self.tr("Stop WiFi Access Point"), self.__stopAccessPoint)
@@ -256,23 +259,34 @@
                 )
 
     @pyqtSlot()
-    def __startAccessPoint(self):
+    def __startAccessPoint(self, withIP=False):
         """
         Private slot to start the Access Point interface of the connected device.
+
+        @param withIP flag indicating to start the access point with an IP configuration
+        @type bool
         """
         from .WifiApConfigDialog import WifiApConfigDialog
 
-        dlg = WifiApConfigDialog()
+        dlg = WifiApConfigDialog(withIP=withIP)
         if dlg.exec() == QDialog.DialogCode.Accepted:
-            ssid, password, security, remember = dlg.getApConfig()
+            ssid, password, security, remember, ifconfig = dlg.getApConfig()
 
             if remember:
                 Preferences.setMicroPython("WifiApName", ssid)
                 Preferences.setMicroPython("WifiApPassword", password)
                 Preferences.setMicroPython("WifiApAuthMode", security)
+                if withIP:
+                    Preferences.setMicroPython("WifiApAddress", ifconfig[0])
+                    Preferences.setMicroPython("WifiApNetmask", ifconfig[1])
+                    Preferences.setMicroPython("WifiApGateway", ifconfig[2])
+                    Preferences.setMicroPython("WifiApDNS", ifconfig[3])
 
             ok, err = self.__mpy.getDevice().startAccessPoint(
-                ssid, security=security, password=password
+                ssid,
+                security=security,
+                password=password,
+                ifconfig=ifconfig if withIP else None,
             )
             if ok:
                 EricMessageBox.information(
@@ -293,6 +307,14 @@
                 )
 
     @pyqtSlot()
+    def __startAccessPointIP(self):
+        """
+        Private slot to start the Access Point interface of the connected device
+        with given IP parameters.
+        """
+        self.__startAccessPoint(withIP=True)
+
+    @pyqtSlot()
     def __stopAccessPoint(self):
         """
         Private slot to stop the Access Point interface of the connected device.
--- a/src/eric7/MicroPython/WifiDialogs/WifiStatusDialog.py	Thu Feb 23 13:31:55 2023 +0100
+++ b/src/eric7/MicroPython/WifiDialogs/WifiStatusDialog.py	Fri Feb 24 14:11:20 2023 +0100
@@ -75,13 +75,14 @@
                     QTreeWidgetItem(
                         header, [self.tr("Country"), clientStatus["country"]]
                     )
-                QTreeWidgetItem(
-                    header,
-                    [
-                        self.tr("Tx-Power"),
-                        self.tr("{0} dBm").format(clientStatus["txpower"]),
-                    ],
-                )
+                with contextlib.suppress(KeyError):
+                    QTreeWidgetItem(
+                        header,
+                        [
+                            self.tr("Tx-Power"),
+                            self.tr("{0} dBm").format(clientStatus["txpower"]),
+                        ],
+                    )
 
         # access point interface
         if apStatus:
@@ -113,13 +114,14 @@
                 QTreeWidgetItem(header, [self.tr("Channel"), str(apStatus["channel"])])
                 with contextlib.suppress(KeyError):
                     QTreeWidgetItem(header, [self.tr("Country"), apStatus["country"]])
-                QTreeWidgetItem(
-                    header,
-                    [
-                        self.tr("Tx-Power"),
-                        self.tr("{0} dBm").format(apStatus["txpower"]),
-                    ],
-                )
+                with contextlib.suppress(KeyError):
+                    QTreeWidgetItem(
+                        header,
+                        [
+                            self.tr("Tx-Power"),
+                            self.tr("{0} dBm").format(apStatus["txpower"]),
+                        ],
+                    )
 
         for col in range(self.statusTree.columnCount()):
             self.statusTree.resizeColumnToContents(col)
--- a/src/eric7/Preferences/ConfigurationPages/MicroPythonPage.py	Thu Feb 23 13:31:55 2023 +0100
+++ b/src/eric7/Preferences/ConfigurationPages/MicroPythonPage.py	Fri Feb 24 14:11:20 2023 +0100
@@ -142,6 +142,10 @@
         if index == -1:
             index = 5  # default it to WPA/WPA2 in case of an issue
         self.apSecurityComboBox.setCurrentIndex(index)
+        self.apAddressEdit.setText(Preferences.getMicroPython("WifiApAddress"))
+        self.apNetmaskEdit.setText(Preferences.getMicroPython("WifiApNetmask"))
+        self.apGatewayEdit.setText(Preferences.getMicroPython("WifiApGateway"))
+        self.apDnsEdit.setText(Preferences.getMicroPython("WifiApDNS"))
 
         # MPY Cross Compiler
         self.mpyCrossPicker.setText(Preferences.getMicroPython("MpyCrossCompiler"))
@@ -232,6 +236,10 @@
         Preferences.setMicroPython(
             "WifiApAuthMode", self.apSecurityComboBox.currentData()
         )
+        Preferences.setMicroPython("WifiApAddress", self.apAddressEdit.text())
+        Preferences.setMicroPython("WifiApNetmask", self.apNetmaskEdit.text())
+        Preferences.setMicroPython("WifiApGateway", self.apGatewayEdit.text())
+        Preferences.setMicroPython("WifiApDNS", self.apDnsEdit.text())
 
         # MPY Cross Compiler
         Preferences.setMicroPython("MpyCrossCompiler", self.mpyCrossPicker.text())
--- a/src/eric7/Preferences/ConfigurationPages/MicroPythonPage.ui	Thu Feb 23 13:31:55 2023 +0100
+++ b/src/eric7/Preferences/ConfigurationPages/MicroPythonPage.ui	Fri Feb 24 14:11:20 2023 +0100
@@ -7,7 +7,7 @@
     <x>0</x>
     <y>0</y>
     <width>541</width>
-    <height>1617</height>
+    <height>1847</height>
    </rect>
   </property>
   <layout class="QVBoxLayout" name="verticalLayout_3">
@@ -317,7 +317,7 @@
         <property name="title">
          <string>Access Point</string>
         </property>
-        <layout class="QGridLayout" name="gridLayout_9">
+        <layout class="QGridLayout" name="gridLayout_10">
          <item row="0" column="0">
           <widget class="QLabel" name="label_10">
            <property name="text">
@@ -396,6 +396,91 @@
            </item>
           </layout>
          </item>
+         <item row="3" column="0" colspan="3">
+          <widget class="QGroupBox" name="ipv4GroupBox">
+           <property name="focusPolicy">
+            <enum>Qt::NoFocus</enum>
+           </property>
+           <property name="title">
+            <string>IPv4 Configuration</string>
+           </property>
+           <layout class="QHBoxLayout" name="horizontalLayout_7">
+            <item>
+             <layout class="QGridLayout" name="gridLayout_9">
+              <item row="0" column="0">
+               <widget class="QLabel" name="label_24">
+                <property name="text">
+                 <string>Address:</string>
+                </property>
+               </widget>
+              </item>
+              <item row="0" column="1">
+               <widget class="EricIPv4InputWidget" name="apAddressEdit" native="true">
+                <property name="focusPolicy">
+                 <enum>Qt::NoFocus</enum>
+                </property>
+               </widget>
+              </item>
+              <item row="1" column="0">
+               <widget class="QLabel" name="label_25">
+                <property name="text">
+                 <string>Netmask:</string>
+                </property>
+               </widget>
+              </item>
+              <item row="1" column="1">
+               <widget class="EricIPv4InputWidget" name="apNetmaskEdit" native="true">
+                <property name="focusPolicy">
+                 <enum>Qt::NoFocus</enum>
+                </property>
+               </widget>
+              </item>
+              <item row="2" column="0">
+               <widget class="QLabel" name="label_26">
+                <property name="text">
+                 <string>Gateway:</string>
+                </property>
+               </widget>
+              </item>
+              <item row="2" column="1">
+               <widget class="EricIPv4InputWidget" name="apGatewayEdit" native="true">
+                <property name="focusPolicy">
+                 <enum>Qt::NoFocus</enum>
+                </property>
+               </widget>
+              </item>
+              <item row="3" column="0">
+               <widget class="QLabel" name="label_27">
+                <property name="text">
+                 <string>DNS:</string>
+                </property>
+               </widget>
+              </item>
+              <item row="3" column="1">
+               <widget class="EricIPv4InputWidget" name="apDnsEdit" native="true">
+                <property name="focusPolicy">
+                 <enum>Qt::NoFocus</enum>
+                </property>
+               </widget>
+              </item>
+             </layout>
+            </item>
+            <item>
+             <spacer name="horizontalSpacer_4">
+              <property name="orientation">
+               <enum>Qt::Horizontal</enum>
+              </property>
+              <property name="sizeHint" stdset="0">
+               <size>
+                <width>273</width>
+                <height>20</height>
+               </size>
+              </property>
+             </spacer>
+            </item>
+           </layout>
+          </widget>
+         </item>
         </layout>
        </widget>
       </item>
@@ -740,6 +825,12 @@
    <header>eric7/EricWidgets/EricPathPicker.h</header>
    <container>1</container>
   </customwidget>
+  <customwidget>
+   <class>EricIPv4InputWidget</class>
+   <extends>QWidget</extends>
+   <header>eric7/EricNetwork/EricIPv4InputWidget.h</header>
+   <container>1</container>
+  </customwidget>
  </customwidgets>
  <tabstops>
   <tabstop>workspacePicker</tabstop>
@@ -757,6 +848,7 @@
   <tabstop>apPasswordEdit</tabstop>
   <tabstop>apShowPasswordButton</tabstop>
   <tabstop>apSecurityComboBox</tabstop>
+  <tabstop>ipv4GroupBox</tabstop>
   <tabstop>mpyCrossPicker</tabstop>
   <tabstop>dfuUtilPathPicker</tabstop>
   <tabstop>micropythonFirmwareUrlLineEdit</tabstop>
--- a/src/eric7/Preferences/__init__.py	Thu Feb 23 13:31:55 2023 +0100
+++ b/src/eric7/Preferences/__init__.py	Fri Feb 24 14:11:20 2023 +0100
@@ -1581,6 +1581,10 @@
         "WifiApName": "",
         "WifiApPassword": "",
         "WifiApAuthMode": 4,  # WPA/WPA2
+        "WifiApAddress": "",
+        "WifiApNetmask": "",
+        "WifiApGateway": "",
+        "WifiApDNS": "",
         "WifiCountry": "",
         # MicroPython URLs
         "MicroPythonDocuUrl": "https://docs.micropython.org/en/latest/",

eric ide

mercurial