MicroPython mpy_network

Tue, 07 Mar 2023 16:22:07 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Tue, 07 Mar 2023 16:22:07 +0100
branch
mpy_network
changeset 9855
c9244db5566a
parent 9854
c1e298e5c588
child 9856
df2ff78bbc01

MicroPython
- Added support for Bluetooth enabled boards.

docs/changelog.md file | annotate | diff | comparison | revisions
eric7.epj file | annotate | diff | comparison | revisions
src/eric7/MicroPython/BluetoothDialogs/BluetoothController.py file | annotate | diff | comparison | revisions
src/eric7/MicroPython/BluetoothDialogs/BluetoothStatusDialog.py file | annotate | diff | comparison | revisions
src/eric7/MicroPython/BluetoothDialogs/BluetoothStatusDialog.ui file | annotate | diff | comparison | revisions
src/eric7/MicroPython/BluetoothDialogs/__init__.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/MicroPythonWidget.py file | annotate | diff | comparison | revisions
--- a/docs/changelog.md	Mon Mar 06 17:03:40 2023 +0100
+++ b/docs/changelog.md	Tue Mar 07 16:22:07 2023 +0100
@@ -6,6 +6,7 @@
     - Added functionality to search for known boot volumes in the UF2 flash dialog.
     - Added functionality to install packages using `mip` or `upip`.
     - Added support for WiFi enabled boards.
+    - Added support for Bluetooth enabled boards.
 - Third Party packages
     - Upgraded eradicate to version 2.2.0.
     - Upgraded pipdeptree to version 2.5.2.
--- a/eric7.epj	Mon Mar 06 17:03:40 2023 +0100
+++ b/eric7.epj	Tue Mar 07 16:22:07 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/BluetoothStatusDialog.ui",
       "src/eric7/MicroPython/BoardDataDialog.ui",
       "src/eric7/MicroPython/ConnectionSelectionDialog.ui",
       "src/eric7/MicroPython/Devices/CircuitPythonUpdater/RequirementsDialog.ui",
@@ -1289,6 +1290,9 @@
       "src/eric7/JediInterface/RefactoringPreviewDialog.py",
       "src/eric7/JediInterface/__init__.py",
       "src/eric7/MicroPython/AddEditDevicesDialog.py",
+      "src/eric7/MicroPython/BluetoothDialogs/BluetoothController.py",
+      "src/eric7/MicroPython/BluetoothDialogs/BluetoothStatusDialog.py",
+      "src/eric7/MicroPython/BluetoothDialogs/__init__.py",
       "src/eric7/MicroPython/BoardDataDialog.py",
       "src/eric7/MicroPython/ConnectionSelectionDialog.py",
       "src/eric7/MicroPython/Devices/CircuitPythonDevices.py",
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/MicroPython/BluetoothDialogs/BluetoothController.py	Tue Mar 07 16:22:07 2023 +0100
@@ -0,0 +1,120 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2023 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the Bluetooth related functionality.
+"""
+
+from PyQt6.QtCore import QObject, pyqtSlot
+from PyQt6.QtWidgets import QMenu
+
+from eric7.EricWidgets import EricMessageBox
+
+
+class BluetoothController(QObject):
+    """
+    Class implementing the Bluetooth related functionality.
+    """
+
+    def __init__(self, microPython, parent=None):
+        """
+        Constructor
+
+        @param microPython reference to the MicroPython widget
+        @type MicroPythonWidgep
+        @param parent reference to the parent object (defaults to None)
+        @type QObject (optional)
+        """
+        super().__init__(parent)
+
+        self.__mpy = microPython
+
+    def createMenu(self, menu):
+        """
+        Public method to create the Bluetooth submenu.
+
+        @param menu reference to the parent menu
+        @type QMenu
+        @return reference to the created menu
+        @rtype QMenu
+        """
+        btMenu = QMenu(self.tr("Bluetooth Functions"), menu)
+        btMenu.setTearOffEnabled(True)
+        btMenu.addAction(self.tr("Show Bluetooth Status"), self.__showBtStatus)
+        btMenu.addSeparator()
+        btMenu.addSeparator()
+        btMenu.addAction(
+            self.tr("Activate Bluetooth Interface"),
+            lambda: self.__activateInterface(),
+        )
+        btMenu.addAction(
+            self.tr("Deactivate Bluetooth Interface"),
+            lambda: self.__deactivateInterface(),
+        )
+
+        # add device specific entries (if there are any)
+        self.__mpy.getDevice().addDeviceBluetoothEntries(btMenu)
+
+        return btMenu
+
+    @pyqtSlot()
+    def __showBtStatus(self):
+        """
+        Private slot to show the status and some parameters of the Bluetooth interface.
+        """
+        from .BluetoothStatusDialog import BluetoothStatusDialog
+
+        try:
+            status = self.__mpy.getDevice().getBluetoothStatus()
+            # status is a list of user labels and associated values
+
+            dlg = BluetoothStatusDialog(status, self.__mpy)
+            dlg.exec()
+        except Exception as exc:
+            self.__mpy.showError("getBluetoothStatus()", str(exc))
+
+    @pyqtSlot()
+    def __activateInterface(self):
+        """
+        Private slot to activate the Bluetooth interface.
+        """
+        try:
+            status = self.__mpy.getDevice().activateBluetoothInterface()
+            if status:
+                EricMessageBox.information(
+                    None,
+                    self.tr("Activate Bluetooth Interface"),
+                    self.tr("""Bluetooth was activated successfully."""),
+                )
+            else:
+                EricMessageBox.warning(
+                    None,
+                    self.tr("Activate Bluetooth Interface"),
+                    self.tr("""Bluetooth could not be activated."""),
+                )
+        except Exception as exc:
+            self.__mpy.showError("activateBluetoothInterface()", str(exc))
+
+    @pyqtSlot()
+    def __deactivateInterface(self):
+        """
+        Private slot to deactivate the Bluetooth interface.
+        """
+        try:
+            status = self.__mpy.getDevice().deactivateBluetoothInterface()
+            if not status:
+                EricMessageBox.information(
+                    None,
+                    self.tr("Deactivate Bluetooth Interface"),
+                    self.tr("""Bluetooth was deactivated successfully."""),
+                )
+            else:
+                EricMessageBox.warning(
+                    None,
+                    self.tr("Deactivate Bluetooth Interface"),
+                    self.tr("""Bluetooth could not be deactivated."""),
+                )
+        except Exception as exc:
+            self.__mpy.showError("deactivateBluetoothInterface()", str(exc))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/MicroPython/BluetoothDialogs/BluetoothStatusDialog.py	Tue Mar 07 16:22:07 2023 +0100
@@ -0,0 +1,39 @@
+# -*- coding: utf-8 -*-
+
+"""
+Module implementing BluetoothStatusDialog.
+"""
+
+from PyQt6.QtCore import Qt
+from PyQt6.QtWidgets import QDialog, QDialogButtonBox, QTreeWidgetItem
+
+from .Ui_BluetoothStatusDialog import Ui_BluetoothStatusDialog
+
+
+class BluetoothStatusDialog(QDialog, Ui_BluetoothStatusDialog):
+    """
+    Class documentation goes here.
+    """
+
+    def __init__(self, status, parent=None):
+        """
+        Constructor
+
+        @param status status data to be show
+        @type list of tuples of (str, str)
+        @param parent reference to the parent widget (defaults to None)
+        @type QWidget (optional)
+        """
+        super().__init__(parent)
+        self.setupUi(self)
+
+        self.statusTree.setColumnCount(2)
+
+        for topic, value in status:
+            QTreeWidgetItem(self.statusTree, [topic, str(value)])
+
+        for col in range(self.statusTree.columnCount()):
+            self.statusTree.resizeColumnToContents(col)
+
+        self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setDefault(True)
+        self.buttonBox.setFocus(Qt.FocusReason.OtherFocusReason)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/MicroPython/BluetoothDialogs/BluetoothStatusDialog.ui	Tue Mar 07 16:22:07 2023 +0100
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>BluetoothStatusDialog</class>
+ <widget class="QDialog" name="BluetoothStatusDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>600</width>
+    <height>650</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Bluetooth 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>Bluetooth Status</string>
+     </property>
+     <property name="alignment">
+      <set>Qt::AlignCenter</set>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QTreeWidget" name="statusTree">
+     <property name="rootIsDecorated">
+      <bool>false</bool>
+     </property>
+     <property name="itemsExpandable">
+      <bool>false</bool>
+     </property>
+     <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::Close</set>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>BluetoothStatusDialog</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>BluetoothStatusDialog</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>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/MicroPython/BluetoothDialogs/__init__.py	Tue Mar 07 16:22:07 2023 +0100
@@ -0,0 +1,8 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2023 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Package implementing Bluetooth related dialogs.
+"""
--- a/src/eric7/MicroPython/Devices/CircuitPythonDevices.py	Mon Mar 06 17:03:40 2023 +0100
+++ b/src/eric7/MicroPython/Devices/CircuitPythonDevices.py	Tue Mar 07 16:22:07 2023 +0100
@@ -83,6 +83,12 @@
             6: "[wifi.AuthMode.WPA3, wifi.AuthMode.PSK]",
             7: "[wifi.AuthMode.WPA2, wifi.AuthMode.WPA3, wifi.AuthMode.PSK]",
         }
+        self.__bleAddressType = {
+            0: self.tr("Public"),
+            1: self.tr("Random Static"),
+            2: self.tr("Random Private Resolvable"),
+            3: self.tr("Random Private Non-Resolvable"),
+        }
 
     def setConnected(self, connected):
         """
@@ -1137,6 +1143,142 @@
             self.tr("CircuitPython does not support reporting of connected clients."),
         )
 
+    ##################################################################
+    ## 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
+        """
+        command = """
+def has_bt():
+    try:
+        import _bleio
+        if hasattr(_bleio, 'adapter'):
+            return True
+    except ImportError:
+        pass
+
+    return False
+
+print(has_bt())
+del has_bt
+"""
+        out, err = self._interface.execute(
+            command, mode=self._submitMode, timeout=10000
+        )
+        if err:
+            raise OSError(self._shortError(err))
+        return out.strip() == b"True"
+
+    def getBluetoothStatus(self):
+        """
+        Public method to get Bluetooth status data of the connected board.
+
+        @return list of tuples containing the translated status data label and
+            the associated value
+        @rtype list of tuples of (str, str)
+        """
+        command = """
+def ble_status():
+    import _bleio
+    import binascii
+    import json
+
+    a = _bleio.adapter
+
+    ble_enabled = a.enabled
+    if not ble_enabled:
+        a.enabled = True
+
+    res = {
+        'active': ble_enabled,
+        'mac': binascii.hexlify(bytes(reversed(a.address.address_bytes)), ':').decode(),
+        'addr_type': a.address.type,
+        'name': a.name,
+        'advertising': a.advertising,
+        'connected': a.connected,
+    }
+
+    if not ble_enabled:
+        a.enabled = False
+
+    print(json.dumps(res))
+
+ble_status()
+del ble_status
+"""
+        out, err = self._interface.execute(command, mode=self._submitMode)
+        if err:
+            raise OSError(self._shortError(err))
+
+        status = []
+        bleStatus = json.loads(out.decode("utf-8"))
+        status.append((self.tr("Active"), self.bool2str(bleStatus["active"])))
+        status.append((self.tr("Name"), bleStatus["name"]))
+        status.append((self.tr("MAC-Address"), bleStatus["mac"]))
+        status.append(
+            (self.tr("Address Type"), self.__bleAddressType[bleStatus["addr_type"]])
+        )
+        status.append((self.tr("Connected"), self.bool2str(bleStatus["connected"])))
+        status.append((self.tr("Advertising"), self.bool2str(bleStatus["advertising"])))
+
+        return status
+
+    def activateBluetoothInterface(self):
+        """
+        Public method to activate the Bluetooth interface.
+
+        @return flag indicating the new state of the Bluetooth interface
+        @rtype bool
+        """
+        command = """
+def activate_ble():
+    import _bleio
+
+    a = _bleio.adapter
+    if not a.enabled:
+        a.enabled = True
+    print(a.enabled)
+
+activate_ble()
+del activate_ble
+"""
+        out, err = self._interface.execute(command, mode=self._submitMode)
+        if err:
+            raise OSError(self._shortError(err))
+
+        return out.strip() == b"True"
+
+    def deactivateBluetoothInterface(self):
+        """
+        Public method to deactivate the Bluetooth interface.
+
+        @return flag indicating the new state of the Bluetooth interface
+        @rtype bool
+        """
+        command = """
+def deactivate_ble():
+    import _bleio
+
+    a = _bleio.adapter
+    if a.enabled:
+        a.enabled = False
+    print(a.enabled)
+
+deactivate_ble()
+del deactivate_ble
+"""
+        out, err = self._interface.execute(command, mode=self._submitMode)
+        if err:
+            raise OSError(self._shortError(err))
+
+        return out.strip() == b"True"
+
 
 def createDevice(microPythonWidget, deviceType, vid, pid, boardName, serialNumber):
     """
--- a/src/eric7/MicroPython/Devices/DeviceBase.py	Mon Mar 06 17:03:40 2023 +0100
+++ b/src/eric7/MicroPython/Devices/DeviceBase.py	Tue Mar 07 16:22:07 2023 +0100
@@ -76,6 +76,13 @@
     <li>stopAccessPoint: stop the access point</li>
     <li>getConnectedClients: get a list of connected WiFi clients</li>
     </ul>
+
+    Supported Bluetooth commands are:
+    <ul>
+    <li>hasBluetooth: check, if the board has Bluetooth functionality</li>
+    <li>getBluetoothStatus: get Bluetooth status data</li>
+    <li>deactivateBluetoothInterface: deactivate a Bluetooth interface</li>
+    </ul>
     """
 
     def __init__(self, microPythonWidget, deviceType, parent=None):
@@ -1466,6 +1473,62 @@
         """
         return False
 
+    def addDeviceBluetoothEntries(self, menu):
+        """
+        Public method to add device specific entries to the given menu.
+
+        @param menu reference to the context menu
+        @type QMenu
+        """
+        pass
+
+    def getBluetoothStatus(self):
+        """
+        Public method to get Bluetooth status data of the connected board.
+
+        @return list of tuples containing the translated status data label and
+            the associated value
+        @rtype list of tuples of (str, str)
+        """
+        return []
+
+    def activateBluetoothInterface(self):
+        """
+        Public method to activate the Bluetooth interface.
+
+        @return flag indicating the new state of the Bluetooth interface
+        @rtype bool
+        """
+        return False
+
+    def deactivateBluetoothInterface(self):
+        """
+        Public method to deactivate the Bluetooth interface.
+
+        @return flag indicating the new state of the Bluetooth interface
+        @rtype bool
+        """
+        return False
+
+    ##################################################################
+    ## Methods below implement some utility methods
+    ##################################################################
+
+    def bool2str(self, val, capitalized=True):
+        """
+        Public method to generate a yes/no string given a truth value.
+
+        @param val truth value to be converted
+        @type bool
+        @param capitalized flag indicating a capitalized variant
+        @type bool
+        @return string with 'yes' or 'no'
+        @rtype str
+        """
+        if capitalized:
+            return self.tr("Yes") if val else self.tr("No")
+        else:
+            return self.tr("yes") if val else self.tr("no")
 
 #
 # eflag: noqa = M613
--- a/src/eric7/MicroPython/Devices/EspDevices.py	Mon Mar 06 17:03:40 2023 +0100
+++ b/src/eric7/MicroPython/Devices/EspDevices.py	Tue Mar 07 16:22:07 2023 +0100
@@ -1038,6 +1038,149 @@
         clientsList = ast.literal_eval(out.decode("utf-8"))
         return clientsList, ""
 
+    ##################################################################
+    ## 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
+        """
+        command = """
+def has_bt():
+    try:
+        import bluetooth
+        if hasattr(bluetooth, 'BLE'):
+            return True
+    except ImportError:
+        pass
+
+    return False
+
+print(has_bt())
+del has_bt
+"""
+        out, err = self._interface.execute(
+            command, mode=self._submitMode, timeout=10000
+        )
+        if err:
+            raise OSError(self._shortError(err))
+        return out.strip() == b"True"
+
+    def getBluetoothStatus(self):
+        """
+        Public method to get Bluetooth status data of the connected board.
+
+        @return list of tuples containing the translated status data label and
+            the associated value
+        @rtype list of tuples of (str, str)
+        """
+        command = """
+def ble_status():
+    import bluetooth
+    import ubinascii
+    import ujson
+
+    ble = bluetooth.BLE()
+
+    ble_active = ble.active()
+    if not ble_active:
+        ble.active(True)
+
+    res = {
+        'active': ble_active,
+        'mac': ubinascii.hexlify(ble.config('mac')[1], ':').decode(),
+        'addr_type': ble.config('mac')[0],
+        'name': ble.config('gap_name'),
+        'rxbuf': ble.config('rxbuf'),
+        'mtu': ble.config('mtu'),
+    }
+
+    if not ble_active:
+        ble.active(False)
+
+    print(ujson.dumps(res))
+
+ble_status()
+del ble_status
+"""
+        out, err = self._interface.execute(command, mode=self._submitMode)
+        if err:
+            raise OSError(self._shortError(err))
+
+        status = []
+        bleStatus = json.loads(out.decode("utf-8"))
+        status.append((self.tr("Active"), self.bool2str(bleStatus["active"])))
+        status.append((self.tr("Name"), bleStatus["name"]))
+        status.append((self.tr("MAC-Address"), bleStatus["mac"]))
+        status.append(
+            (
+                self.tr("Address Type"),
+                self.tr("Public") if bleStatus == 0 else self.tr("Random"),
+            )
+        )
+        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"]))
+        )
+
+        return status
+
+    def activateBluetoothInterface(self):
+        """
+        Public method to activate the Bluetooth interface.
+
+        @return flag indicating the new state of the Bluetooth interface
+        @rtype bool
+        """
+        command = """
+def activate_ble():
+    import bluetooth
+
+    ble = bluetooth.BLE()
+    if not ble.active():
+        ble.active(True)
+    print(ble.active())
+
+activate_ble()
+del activate_ble
+"""
+        out, err = self._interface.execute(command, mode=self._submitMode)
+        if err:
+            raise OSError(self._shortError(err))
+
+        return out.strip() == b"True"
+
+    def deactivateBluetoothInterface(self):
+        """
+        Public method to deactivate the Bluetooth interface.
+
+        @return flag indicating the new state of the Bluetooth interface
+        @rtype bool
+        """
+        command = """
+def deactivate_ble():
+    import bluetooth
+
+    ble = bluetooth.BLE()
+    if ble.active():
+        ble.active(False)
+    print(ble.active())
+
+deactivate_ble()
+del deactivate_ble
+"""
+        out, err = self._interface.execute(command, mode=self._submitMode)
+        if err:
+            raise OSError(self._shortError(err))
+
+        return out.strip() == b"True"
+
 
 def createDevice(microPythonWidget, deviceType, vid, pid, boardName, serialNumber):
     """
--- a/src/eric7/MicroPython/MicroPythonWidget.py	Mon Mar 06 17:03:40 2023 +0100
+++ b/src/eric7/MicroPython/MicroPythonWidget.py	Tue Mar 07 16:22:07 2023 +0100
@@ -42,6 +42,7 @@
 from eric7.UI.Info import BugAddress
 
 from . import Devices, UF2FlashDialog
+from .BluetoothDialogs.BluetoothController import BluetoothController
 from .MicroPythonFileManager import MicroPythonFileManager
 from .MicroPythonFileManagerWidget import MicroPythonFileManagerWidget
 from .Ui_MicroPythonWidget import Ui_MicroPythonWidget
@@ -231,6 +232,9 @@
         self.__wifiController = WifiController(self, self)
         self.__wifiMenu = None
 
+        self.__bluetoothController = BluetoothController(self, self)
+        self.__btMenu = None
+
         self.__superMenu = QMenu(self)
         self.__superMenu.aboutToShow.connect(self.__aboutToShowSuperMenu)
 
@@ -1494,6 +1498,18 @@
         else:
             self.__wifiMenu = None
 
+        # prepare the Bluetooth menu
+        if (
+            self.__device
+            and self.__connected
+            and self.__device.getDeviceData("bluetooth")
+        ):
+            if self.__btMenu is not None:
+                self.__btMenu.deleteLater()
+            self.__btMenu = self.__bluetoothController.createMenu(self.__superMenu)
+        else:
+            self.__btMenu = None
+
         # populate the super menu
         hasTime = self.__device.hasTimeCommands() if self.__device else False
 
@@ -1547,6 +1563,9 @@
             self.__superMenu.addSeparator()
             if self.__wifiMenu is not None:
                 self.__superMenu.addMenu(self.__wifiMenu)
+            if self.__btMenu is not None:
+                self.__superMenu.addMenu(self.__btMenu)
+            if self.__wifiMenu is not None or self.__btMenu is not None:
                 self.__superMenu.addSeparator()
             if downloadMenu is None:
                 # generic download action

eric ide

mercurial