MicroPython mpy_network

Wed, 08 Mar 2023 14:25:24 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Wed, 08 Mar 2023 14:25:24 +0100
branch
mpy_network
changeset 9857
0122ae72618d
parent 9856
df2ff78bbc01
child 9858
6518c336fcd3

MicroPython
- Added support for Bluetooth scans for CircuitPython devices.

eric7.epj file | annotate | diff | comparison | revisions
src/eric7/MicroPython/BluetoothDialogs/BluetoothAdvertisement.py file | annotate | diff | comparison | revisions
src/eric7/MicroPython/BluetoothDialogs/BluetoothController.py file | annotate | diff | comparison | revisions
src/eric7/MicroPython/BluetoothDialogs/BluetoothScanWindow.py file | annotate | diff | comparison | revisions
src/eric7/MicroPython/BluetoothDialogs/BluetoothScanWindow.ui file | annotate | diff | comparison | revisions
src/eric7/MicroPython/BluetoothDialogs/BluetoothStatusDialog.py file | annotate | diff | comparison | revisions
src/eric7/MicroPython/Devices/CircuitPythonDevices.py 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/WifiDialogs/WifiNetworksWindow.py file | annotate | diff | comparison | revisions
src/eric7/MicroPython/WifiDialogs/WifiNetworksWindow.ui file | annotate | diff | comparison | revisions
--- a/eric7.epj	Tue Mar 07 16:23:03 2023 +0100
+++ b/eric7.epj	Wed Mar 08 14:25:24 2023 +0100
@@ -333,6 +333,7 @@
       "src/eric7/IconEditor/IconSizeDialog.ui",
       "src/eric7/JediInterface/RefactoringPreviewDialog.ui",
       "src/eric7/MicroPython/AddEditDevicesDialog.ui",
+      "src/eric7/MicroPython/BluetoothDialogs/BluetoothScanWindow.ui",
       "src/eric7/MicroPython/BluetoothDialogs/BluetoothStatusDialog.ui",
       "src/eric7/MicroPython/BoardDataDialog.ui",
       "src/eric7/MicroPython/ConnectionSelectionDialog.ui",
@@ -1290,7 +1291,9 @@
       "src/eric7/JediInterface/RefactoringPreviewDialog.py",
       "src/eric7/JediInterface/__init__.py",
       "src/eric7/MicroPython/AddEditDevicesDialog.py",
+      "src/eric7/MicroPython/BluetoothDialogs/BluetoothAdvertisement.py",
       "src/eric7/MicroPython/BluetoothDialogs/BluetoothController.py",
+      "src/eric7/MicroPython/BluetoothDialogs/BluetoothScanWindow.py",
       "src/eric7/MicroPython/BluetoothDialogs/BluetoothStatusDialog.py",
       "src/eric7/MicroPython/BluetoothDialogs/__init__.py",
       "src/eric7/MicroPython/BoardDataDialog.py",
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/MicroPython/BluetoothDialogs/BluetoothAdvertisement.py	Wed Mar 08 14:25:24 2023 +0100
@@ -0,0 +1,236 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2023 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a class to parse and store the Bluetooth device advertisement data.
+"""
+
+import struct
+import uuid
+
+ADV_IND = 0
+ADV_SCAN_IND = 2
+ADV_NONCONN_IND = 3
+SCAN_RSP = 4
+
+ADV_TYPE_UUID16_INCOMPLETE = 0x02
+ADV_TYPE_UUID16_COMPLETE = 0x03
+ADV_TYPE_UUID32_INCOMPLETE = 0x04
+ADV_TYPE_UUID32_COMPLETE = 0x05
+ADV_TYPE_UUID128_INCOMPLETE = 0x06
+ADV_TYPE_UUID128_COMPLETE = 0x07
+ADV_TYPE_SHORT_NAME = 0x08
+ADV_TYPE_COMPLETE_NAME = 0x09
+ADV_TYPE_TX_POWER_LEVEL = 0x0A
+ADV_TYPE_SVC_DATA = 0x16
+ADV_TYPE_MANUFACTURER = 0xFF
+
+ManufacturerId = {
+    0x4C: "Apple, Inc.",
+    0xE0: "Google",
+    0x75: "Samsung Electronics Co. Ltd.",
+    0x87: "Garmin International Inc.",
+}
+
+
+class BluetoothAdvertisement:
+    """
+    Class to parse and store the Bluetooth device advertisement data.
+    """
+
+    def __init__(self, address):
+        """
+        Constructor
+
+        @param address address of the device advertisement
+        @type str
+        """
+        self.__address = address
+        self.__name = ""
+        self.__rssi = 0
+        self.__connectable = False
+
+        self.__advData = None
+        self.__respData = None
+
+    def update(self, advType, rssi, advData):
+        """
+        Public method to update the advertisement data.
+
+        @param advType type of advertisement data
+        @type int
+        @param rssi RSSI value in dBm
+        @type int
+        @param advData advertisement data
+        @type bytes
+        """
+        if rssi != self.__rssi:
+            self.__rssi = rssi
+
+        if advType in (ADV_IND, ADV_NONCONN_IND):
+            if advData != self.__advData:
+                self.__advData = advData
+                self.__connectable = advType == ADV_IND
+        elif advType == ADV_SCAN_IND:
+            self.__advData = advData
+        elif advType == SCAN_RSP and advData and advData != self.__respData:
+            self.__respData = advData
+
+    def __str__(self):
+        """
+        Special method to generate a string representation.
+
+        @return string representation
+        @rtype str
+        """
+        return "Scan result: {0} {1}".format(self.__address, self.__rssi)
+
+    def __decodeField(self, *advType):
+        """
+        Private method to get all fields of the specified types.
+
+        @param *advType type of fields to be extracted
+        @type int
+        @yield requested fields
+        @ytype bytes
+        """
+        # Advertising payloads are repeated packets of the following form:
+        #   1 byte data length (N + 1)
+        #   1 byte type (see constants below)
+        #   N bytes type-specific data
+        for payload in (self.__advData, self.__respData):
+            if not payload:
+                continue
+
+            i = 0
+            while i + 1 < len(payload):
+                if payload[i + 1] in advType:
+                    yield payload[i + 2 : i + payload[i] + 1]
+                i += 1 + payload[i]
+
+    def __splitBytes(self, data, chunkSize):
+        """
+        Private method to split some data into chunks of given size.
+
+        @param data data to be chunked
+        @type bytes, bytearray, str
+        @param chunkSize size for each chunk
+        @type int
+        @return list of chunks
+        @rtype list of bytes, bytearray, str
+        """
+        start = 0
+        dataChunks = []
+        while start < len(data):
+            end = start + chunkSize
+            dataChunks.append(data[start:end])
+            start = end
+        return dataChunks
+
+    @property
+    def name(self):
+        """
+        Public method to get the complete or shortened advertised name, if available.
+
+        @return advertised name
+        @rtype str
+        """
+        for n in self.__decodeField(ADV_TYPE_COMPLETE_NAME, ADV_TYPE_SHORT_NAME):
+            return str(n, "utf-8").replace("\x00", "") if n else ""
+
+        return ""
+
+    @property
+    def rssi(self):
+        """
+        Public method to get the RSSI value.
+
+        @return RSSI value in dBm
+        @rtype int
+        """
+        return self.__rssi
+
+    @property
+    def address(self):
+        """
+        Public method to get the address string.
+
+        @return address of the device
+        @rtype str
+        """
+        return self.__address
+
+    @property
+    def txPower(self):
+        """
+        Public method to get the advertised power level in dBm.
+
+        @return transmit power of the device (in dBm)
+        @rtype int
+        """
+        for txLevel in self.__decodeField(ADV_TYPE_TX_POWER_LEVEL):
+            return struct.unpack("<b", txLevel)
+
+        return 0
+
+    @property
+    def services(self):
+        """
+        Public method to get the service IDs.
+
+        @return list of tuples containing the advertised service ID and a
+            flag indicating a complete ID
+        @rtype list of tuple of (str, bool)
+        """
+        result = []
+
+        for u in self.__decodeField(ADV_TYPE_UUID16_INCOMPLETE):
+            for v in self.__splitBytes(u, 2):
+                result.append((hex(struct.unpack("<H", v)[0]), False))
+        for u in self.__decodeField(ADV_TYPE_UUID16_COMPLETE):
+            for v in self.__splitBytes(u, 2):
+                result.append((hex(struct.unpack("<H", v)[0]), True))
+
+        for u in self.__decodeField(ADV_TYPE_UUID32_INCOMPLETE):
+            for v in self.__splitBytes(u, 4):
+                result.append((hex(struct.unpack("<I", v)), False))
+        for u in self.__decodeField(ADV_TYPE_UUID32_COMPLETE):
+            for v in self.__splitBytes(u, 4):
+                result.append((hex(struct.unpack("<I", v)), True))
+
+        for u in self.__decodeField(ADV_TYPE_UUID128_INCOMPLETE):
+            for v in self.__splitBytes(u, 16):
+                uid = uuid.UUID(bytes=bytes(reversed(v)))
+                result.append((str(uid), False))
+        for u in self.__decodeField(ADV_TYPE_UUID128_COMPLETE):
+            for v in self.__splitBytes(u, 16):
+                uid = uuid.UUID(bytes=bytes(reversed(v)))
+                result.append((str(uid), True))
+
+        return result
+
+    def manufacturer(self, filterId=None, withName=False):
+        """
+        Public method to get the manufacturer data.
+
+        @param filterId manufacturer ID to filter on (defaults to None)
+        @type int (optional)
+        @param withName flag indicating to report the manufacturer name as well
+            (if available) (defaults to False)
+        @type bool
+        @return tuple containing the manufacturer ID, associated data and manufacturer
+            name
+        @rtype tuple of (int, bytes, str)
+        """
+        result = []
+        for u in self.__decodeField(ADV_TYPE_MANUFACTURER):
+            if len(u) < 2:
+                continue
+
+            m = struct.unpack("<H", u[0:2])[0]
+            if filter is None or m == filterId:
+                name = ManufacturerId.get(m, "") if withName else None
+                result.append((m, u[2:], name))
+        return result
--- a/src/eric7/MicroPython/BluetoothDialogs/BluetoothController.py	Tue Mar 07 16:23:03 2023 +0100
+++ b/src/eric7/MicroPython/BluetoothDialogs/BluetoothController.py	Wed Mar 08 14:25:24 2023 +0100
@@ -44,6 +44,7 @@
         btMenu.setTearOffEnabled(True)
         btMenu.addAction(self.tr("Show Bluetooth Status"), self.__showBtStatus)
         btMenu.addSeparator()
+        btMenu.addAction(self.tr("Perform Scan"), self.__scan)
         btMenu.addSeparator()
         btMenu.addAction(
             self.tr("Activate Bluetooth Interface"),
@@ -118,3 +119,13 @@
                 )
         except Exception as exc:
             self.__mpy.showError("deactivateBluetoothInterface()", str(exc))
+
+    @pyqtSlot()
+    def __scan(self):
+        """
+        Private slot to scan for Bluetooth devices.
+        """
+        from .BluetoothScanWindow import BluetoothScanWindow
+
+        win = BluetoothScanWindow(self.__mpy.getDevice(), self.__mpy)
+        win.show()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/MicroPython/BluetoothDialogs/BluetoothScanWindow.py	Wed Mar 08 14:25:24 2023 +0100
@@ -0,0 +1,131 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2023 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a dialog to scan for Bluetooth devices.
+"""
+
+from PyQt6.QtCore import Qt, pyqtSlot
+from PyQt6.QtWidgets import QHeaderView, QTreeWidgetItem, QWidget
+
+from eric7.EricGui.EricOverrideCursor import EricOverrideCursor
+from eric7.EricWidgets import EricMessageBox
+
+from .Ui_BluetoothScanWindow import Ui_BluetoothScanWindow
+
+
+class BluetoothScanWindow(QWidget, Ui_BluetoothScanWindow):
+    """
+    Class implementing a dialog to scan for Bluetooth devices.
+    """
+
+    def __init__(self, device, parent=None):
+        """
+        Constructor
+
+        @param device reference to the connected device
+        @type BaseDevice
+        @param parent reference to the parent widget (defaults to None)
+        @type QWidget (optional)
+        """
+        super().__init__(parent)
+        self.setupUi(self)
+
+        windowFlags = self.windowFlags()
+        windowFlags |= Qt.WindowType.Window
+        windowFlags |= Qt.WindowType.WindowContextHelpButtonHint
+        self.setWindowFlags(windowFlags)
+
+        self.__device = device
+
+        self.devicesList.setColumnCount(4)
+        self.devicesList.headerItem().setText(3, "")
+
+        self.scanButton.clicked.connect(self.scanDevices)
+
+        self.devicesList.sortByColumn(0, Qt.SortOrder.AscendingOrder)
+
+    @pyqtSlot()
+    def scanDevices(self):
+        """
+        Public slot to ask the device for a Bluetooth scan and display the result.
+        """
+        self.devicesList.clear()
+        self.statusLabel.clear()
+
+        self.scanButton.setEnabled(False)
+        with EricOverrideCursor():
+            scanResults, error = self.__device.getDeviceScan(
+                timeout=self.durationSpinBox.value()
+            )
+        self.scanButton.setEnabled(True)
+
+        if error:
+            EricMessageBox.warning(
+                self,
+                self.tr("Bluetooth Scan"),
+                self.tr(
+                    """<p>The scan for available devices failed.</p>"""
+                    """<p>Reason: {0}</p>"""
+                ).format(error),
+            )
+
+        else:
+            for res in scanResults.values():
+                name = res.name
+                if not name:
+                    name = self.tr("N/A")
+                itm = QTreeWidgetItem(
+                    self.devicesList, [name, res.address, str(res.rssi)]
+                )
+                itm.setTextAlignment(1, Qt.AlignmentFlag.AlignHCenter)
+                itm.setTextAlignment(2, Qt.AlignmentFlag.AlignHCenter)
+
+                for service, isComplete in res.services:
+                    if len(service) == 6:
+                        bits = 16
+                    elif len(service) == 10:
+                        bits = 32
+                    else:
+                        bits = 128
+                    template = (
+                        self.tr("Complete {0}-bit Service UUID: {1}")
+                        if isComplete
+                        else self.tr("Incomplete {0}-bit Service UUID: {1}")
+                    )
+                    sitm = QTreeWidgetItem(itm, [template.format(bits, service)])
+                    sitm.setFirstColumnSpanned(True)
+
+                for mid, _, mname in res.manufacturer(withName=True):
+                    mitm = QTreeWidgetItem(
+                        itm,
+                        [
+                            self.tr("Manufacturer ID: 0x{0:x} ({1})").format(mid, mname)
+                            if bool(mname)
+                            else self.tr("Manufacturer ID: 0x{0:x}").format(mid)
+                        ],
+                    )
+                    mitm.setFirstColumnSpanned(True)
+
+            self.__resizeColumns()
+            self.__resort()
+
+    def __resort(self):
+        """
+        Private method to resort the devices list.
+        """
+        self.devicesList.sortItems(
+            self.devicesList.sortColumn(),
+            self.devicesList.header().sortIndicatorOrder(),
+        )
+
+    def __resizeColumns(self):
+        """
+        Private method to resize the columns of the result list.
+        """
+        self.devicesList.header().resizeSections(
+            QHeaderView.ResizeMode.ResizeToContents
+        )
+        self.devicesList.header().setStretchLastSection(True)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/MicroPython/BluetoothDialogs/BluetoothScanWindow.ui	Wed Mar 08 14:25:24 2023 +0100
@@ -0,0 +1,135 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>BluetoothScanWindow</class>
+ <widget class="QWidget" name="BluetoothScanWindow">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>650</width>
+    <height>600</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Bluetooth Scan</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <widget class="QTreeWidget" name="devicesList">
+     <property name="alternatingRowColors">
+      <bool>true</bool>
+     </property>
+     <property name="selectionMode">
+      <enum>QAbstractItemView::NoSelection</enum>
+     </property>
+     <property name="sortingEnabled">
+      <bool>true</bool>
+     </property>
+     <column>
+      <property name="text">
+       <string>Name</string>
+      </property>
+     </column>
+     <column>
+      <property name="text">
+       <string>MAC-Address</string>
+      </property>
+     </column>
+     <column>
+      <property name="text">
+       <string>RSSI [dBm]</string>
+      </property>
+     </column>
+     <column>
+      <property name="text">
+       <string notr="true"/>
+      </property>
+     </column>
+    </widget>
+   </item>
+   <item>
+    <widget class="QLabel" name="statusLabel"/>
+   </item>
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout">
+     <item>
+      <widget class="QLabel" name="label">
+       <property name="text">
+        <string>Scan Duration:</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QSpinBox" name="durationSpinBox">
+       <property name="toolTip">
+        <string>Enter the scan duration in seconds</string>
+       </property>
+       <property name="alignment">
+        <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+       </property>
+       <property name="suffix">
+        <string> s</string>
+       </property>
+       <property name="minimum">
+        <number>1</number>
+       </property>
+       <property name="maximum">
+        <number>60</number>
+       </property>
+       <property name="value">
+        <number>10</number>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="scanButton">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="toolTip">
+        <string>Press to scan for available WiFi networks.</string>
+       </property>
+       <property name="text">
+        <string>Scan</string>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <widget class="QDialogButtonBox" name="buttonBox">
+     <property name="standardButtons">
+      <set>QDialogButtonBox::Close</set>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <tabstops>
+  <tabstop>devicesList</tabstop>
+  <tabstop>durationSpinBox</tabstop>
+  <tabstop>scanButton</tabstop>
+ </tabstops>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>rejected()</signal>
+   <receiver>BluetoothScanWindow</receiver>
+   <slot>close()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>486</x>
+     <y>581</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>647</x>
+     <y>534</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+</ui>
--- a/src/eric7/MicroPython/BluetoothDialogs/BluetoothStatusDialog.py	Tue Mar 07 16:23:03 2023 +0100
+++ b/src/eric7/MicroPython/BluetoothDialogs/BluetoothStatusDialog.py	Wed Mar 08 14:25:24 2023 +0100
@@ -1,5 +1,8 @@
 # -*- coding: utf-8 -*-
 
+# Copyright (c) 2023 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
 """
 Module implementing BluetoothStatusDialog.
 """
--- a/src/eric7/MicroPython/Devices/CircuitPythonDevices.py	Tue Mar 07 16:23:03 2023 +0100
+++ b/src/eric7/MicroPython/Devices/CircuitPythonDevices.py	Wed Mar 08 14:25:24 2023 +0100
@@ -1153,6 +1153,7 @@
 
         @return flag indicating the availability of Bluetooth
         @rtype bool
+        @exception OSError raised to indicate an issue with the device
         """
         command = """
 def has_bt():
@@ -1182,6 +1183,7 @@
         @return list of tuples containing the translated status data label and
             the associated value
         @rtype list of tuples of (str, str)
+        @exception OSError raised to indicate an issue with the device
         """
         command = """
 def ble_status():
@@ -1235,6 +1237,7 @@
 
         @return flag indicating the new state of the Bluetooth interface
         @rtype bool
+        @exception OSError raised to indicate an issue with the device
         """
         command = """
 def activate_ble():
@@ -1260,6 +1263,7 @@
 
         @return flag indicating the new state of the Bluetooth interface
         @rtype bool
+        @exception OSError raised to indicate an issue with the device
         """
         command = """
 def deactivate_ble():
@@ -1279,6 +1283,83 @@
 
         return out.strip() == b"True"
 
+    def getDeviceScan(self, timeout=10):
+        """
+        Public method to perform a Bluetooth device scan.
+
+        @param timeout duration of the device scan in seconds (defaults
+            to 10)
+        @type int (optional)
+        @return tuple containing a dictionary with the scan results and
+            an error string
+        @rtype tuple of (dict, str)
+        """
+        from ..BluetoothDialogs.BluetoothAdvertisement import (
+            ADV_IND,
+            ADV_SCAN_IND,
+            SCAN_RSP,
+            BluetoothAdvertisement,
+        )
+
+        command = """
+def ble_scan():
+    import _bleio
+    import binascii
+    import time
+
+    a = _bleio.adapter
+
+    ble_enabled = a.enabled
+    if not ble_enabled:
+        a.enabled = True
+
+    scanResults = a.start_scan(
+        buffer_size=1024, extended=True, timeout={0}, minimum_rssi=-100, active=True
+    )
+    time.sleep(10)
+    a.stop_scan()
+
+    for res in scanResults:
+        print({{
+            'address': binascii.hexlify(
+                bytes(reversed(res.address.address_bytes)), ':'
+            ).decode(),
+            'advertisement': res.advertisement_bytes,
+            'connectable': res.connectable,
+            'rssi': res.rssi,
+            'scan_response': res.scan_response,
+        }})
+
+    if not ble_enabled:
+        a.enabled = False
+
+ble_scan()
+del ble_scan
+""".format(
+            timeout
+        )
+        out, err = self._interface.execute(
+            command, mode=self._submitMode, timeout=(timeout + 5) * 1000
+        )
+        if err:
+            return {}, err
+
+        scanResults = {}
+        for line in out.decode("utf-8").splitlines():
+            res = ast.literal_eval(line)
+            address = res["address"]
+            if address not in scanResults:
+                scanResults[address] = BluetoothAdvertisement(address)
+            if res["scan_response"]:
+                advType = SCAN_RSP
+            elif res["connectable"]:
+                advType = ADV_IND
+            else:
+                advType = ADV_SCAN_IND
+            scanResults[address].update(advType, res["rssi"], res["advertisement"])
+
+        return scanResults, ""
+
 
 def createDevice(microPythonWidget, deviceType, vid, pid, boardName, serialNumber):
     """
--- a/src/eric7/MicroPython/Devices/DeviceBase.py	Tue Mar 07 16:23:03 2023 +0100
+++ b/src/eric7/MicroPython/Devices/DeviceBase.py	Wed Mar 08 14:25:24 2023 +0100
@@ -1510,6 +1510,19 @@
         """
         return False
 
+    def getDeviceScan(self, timeout=10):
+        """
+        Public method to perform a Bluetooth device scan.
+
+        @param timeout duration of the device scan in seconds (defaults
+            to 10)
+        @type int (optional)
+        @return tuple containing a dictionary with the scan results and
+            an error string
+        @rtype tuple of (dict, str)
+        """
+        return {}, ""
+
     ##################################################################
     ## Methods below implement some utility methods
     ##################################################################
@@ -1530,5 +1543,6 @@
         else:
             return self.tr("yes") if val else self.tr("no")
 
+
 #
 # eflag: noqa = M613
--- a/src/eric7/MicroPython/Devices/EspDevices.py	Tue Mar 07 16:23:03 2023 +0100
+++ b/src/eric7/MicroPython/Devices/EspDevices.py	Wed Mar 08 14:25:24 2023 +0100
@@ -1048,6 +1048,7 @@
 
         @return flag indicating the availability of Bluetooth
         @rtype bool
+        @exception OSError raised to indicate an issue with the device
         """
         command = """
 def has_bt():
@@ -1077,6 +1078,7 @@
         @return list of tuples containing the translated status data label and
             the associated value
         @rtype list of tuples of (str, str)
+        @exception OSError raised to indicate an issue with the device
         """
         command = """
 def ble_status():
@@ -1125,9 +1127,7 @@
         status.append(
             (self.tr("Rx-Buffer"), self.tr("{0} Bytes").format(bleStatus["rxbuf"]))
         )
-        status.append(
-            (self.tr("MTU"), self.tr("{0} Bytes").format(bleStatus["mtu"]))
-        )
+        status.append((self.tr("MTU"), self.tr("{0} Bytes").format(bleStatus["mtu"])))
 
         return status
 
@@ -1137,6 +1137,7 @@
 
         @return flag indicating the new state of the Bluetooth interface
         @rtype bool
+        @exception OSError raised to indicate an issue with the device
         """
         command = """
 def activate_ble():
@@ -1162,6 +1163,7 @@
 
         @return flag indicating the new state of the Bluetooth interface
         @rtype bool
+        @exception OSError raised to indicate an issue with the device
         """
         command = """
 def deactivate_ble():
--- a/src/eric7/MicroPython/WifiDialogs/WifiNetworksWindow.py	Tue Mar 07 16:23:03 2023 +0100
+++ b/src/eric7/MicroPython/WifiDialogs/WifiNetworksWindow.py	Wed Mar 08 14:25:24 2023 +0100
@@ -47,9 +47,10 @@
 
         self.networkList.sortByColumn(0, Qt.SortOrder.AscendingOrder)
 
+    @pyqtSlot()
     def scanNetworks(self):
         """
-        Public method to ask the device for a network scan and display the result.
+        Public slot to ask the device for a network scan and display the result.
         """
         self.networkList.clear()
         self.statusLabel.clear()
--- a/src/eric7/MicroPython/WifiDialogs/WifiNetworksWindow.ui	Tue Mar 07 16:23:03 2023 +0100
+++ b/src/eric7/MicroPython/WifiDialogs/WifiNetworksWindow.ui	Wed Mar 08 14:25:24 2023 +0100
@@ -95,6 +95,9 @@
      </item>
      <item>
       <widget class="QSpinBox" name="intervalSpinBox">
+       <property name="toolTip">
+        <string>Enter the scan interval in seconds</string>
+       </property>
        <property name="alignment">
         <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
        </property>
@@ -114,6 +117,9 @@
      </item>
      <item>
       <widget class="QCheckBox" name="periodicCheckBox">
+       <property name="toolTip">
+        <string>Select to perform a periodic WiFi network scan</string>
+       </property>
        <property name="text">
         <string>Periodic Scan</string>
        </property>
@@ -143,8 +149,8 @@
    <slot>close()</slot>
    <hints>
     <hint type="sourcelabel">
-     <x>269</x>
-     <y>563</y>
+     <x>278</x>
+     <y>590</y>
     </hint>
     <hint type="destinationlabel">
      <x>271</x>

eric ide

mercurial