Added a connect timeout function with a settable timeout value.

Sun, 09 Sep 2018 17:32:54 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Sun, 09 Sep 2018 17:32:54 +0200
changeset 31
40582e448c4b
parent 30
17ef10819773
child 32
a71e5b294ebf

Added a connect timeout function with a settable timeout value.

MqttMonitor/MqttClient.py file | annotate | diff | comparison | revisions
MqttMonitor/MqttConnectionOptionsDialog.py file | annotate | diff | comparison | revisions
MqttMonitor/MqttConnectionOptionsDialog.ui file | annotate | diff | comparison | revisions
MqttMonitor/MqttConnectionProfilesDialog.py file | annotate | diff | comparison | revisions
MqttMonitor/MqttConnectionProfilesDialog.ui file | annotate | diff | comparison | revisions
MqttMonitor/MqttMonitorWidget.py file | annotate | diff | comparison | revisions
MqttMonitor/MqttMonitorWidget.ui file | annotate | diff | comparison | revisions
--- a/MqttMonitor/MqttClient.py	Sun Sep 09 12:21:19 2018 +0200
+++ b/MqttMonitor/MqttClient.py	Sun Sep 09 17:32:54 2018 +0200
@@ -9,7 +9,8 @@
 
 from __future__ import unicode_literals
 
-from PyQt5.QtCore import pyqtSignal, QObject, QCoreApplication
+from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QCoreApplication, \
+    QTimer
 
 import paho.mqtt.client as mqtt
 
@@ -24,6 +25,7 @@
         broker
     @signal onDisconnected(rc) emitted after the client has disconnected from
         the broker
+    @signal onLog(level, message) emitted to send client log data
     @signal onMessage(topic, payload, qos, retain) emitted after a message has
         been received by the client
     @signal onPublish(mid) emitted after a message has been published
@@ -34,11 +36,29 @@
     """
     onConnect = pyqtSignal(dict, int)
     onDisconnected = pyqtSignal(int)
+    onLog = pyqtSignal(int, str)
     onMessage = pyqtSignal(str, bytes, int, bool)
     onPublish = pyqtSignal(int)
     onSubscribe = pyqtSignal(int, tuple)
     onUnsubscribe = pyqtSignal(int)
     
+    connectTimeout = pyqtSignal()
+    
+    DefaultConnectTimeout = 15      # connect timeout in seconds
+    
+    LogDebug = 0x01
+    LogInfo = 0x02
+    LogNotice = 0x04
+    LogWarning = 0x08
+    LogError = 0x10
+    LogLevelMap = {
+        mqtt.MQTT_LOG_DEBUG: LogDebug,
+        mqtt.MQTT_LOG_INFO: LogInfo,
+        mqtt.MQTT_LOG_NOTICE: LogNotice,
+        mqtt.MQTT_LOG_WARNING: LogWarning,                        # __NO-TASK__
+        mqtt.MQTT_LOG_ERR: LogError,
+    }
+    
     def __init__(self, clientId="", cleanSession=True, userdata=None,
                  protocol=mqtt.MQTTv311, transport="tcp", parent=None):
         """
@@ -61,6 +81,14 @@
         
         self.__loopStarted = False
         
+        self.__connectTimeoutTimer = QTimer(self)
+        self.__connectTimeoutTimer.setSingleShot(True)
+        self.__connectTimeoutTimer.setInterval(
+            MqttClient.DefaultConnectTimeout * 1000)
+        self.__connectTimeoutTimer.timeout.connect(self.__connectTimeout)
+        
+        self.onConnect.connect(self.__connectTimeoutTimer.stop)
+        
         self.__mqttClient = mqtt.Client(
             client_id=clientId, clean_session=cleanSession, userdata=None,
             protocol=mqtt.MQTTv311, transport="tcp")
@@ -76,6 +104,8 @@
                 flags, rc)
         self.__mqttClient.on_disconnect = \
             lambda client, userdata, rc: self.onDisconnected.emit(rc)
+        self.__mqttClient.on_log = \
+            lambda client, userdata, level, buf: self.onLog.emit(level, buf)
         self.__mqttClient.on_message = \
             lambda client, userdata, message: self.onMessage.emit(
                 message.topic, message.payload, message.qos, message.retain)
@@ -87,6 +117,14 @@
         self.__mqttClient.on_unsubscribe = \
             lambda client, userdata, mid: self.onUnsubscribe.emit(mid)
     
+    @pyqtSlot()
+    def __connectTimeout(self):
+        """
+        Privat slot handling a failed connection attempt.
+        """
+        self.stopLoop()
+        self.connectTimeout.emit()
+    
     def reinitialise(self, clientId="", cleanSession=True, userdata=None):
         """
         Public method to reinitialize the client with given data.
@@ -103,6 +141,15 @@
         
         self.__initCallbacks()
     
+    def setConnectionTimeout(self, timeout):
+        """
+        Public method to set the connection timeout value.
+        
+        @param timeout timeout value to be set in seconds
+        @type int
+        """
+        self.__connectTimeoutTimer.setInterval(timeout * 1000)
+    
     def setMaxInflightMessages(self, inflight=20):
         """
         Public method to set the maximum number of messages with QoS > 0 that
@@ -202,7 +249,8 @@
         self.__mqttClient.loop_stop()
         self.__loopStarted = False
     
-    def connectToServer(self, host, port=1883, keepalive=60, bindAddress=""):
+    def connectToServer(self, host, port=1883, keepalive=60, bindAddress="",
+                        reinit=True):
         """
         Public method to connect to a remote MQTT broker.
         
@@ -218,11 +266,13 @@
             this client to
         @type str
         """
-        # TODO: get this fixed or allow to interrupt
-        self.__mqttClient.reconnect_delay_set(max_delay=16)
+        if reinit:
+            self.reinitialise()
         self.__mqttClient.connect_async(
             host, port=port, keepalive=keepalive, bind_address=bindAddress)
         
+        self.__connectTimeoutTimer.start()
+        
         if not self.__loopStarted:
             self.startLoop()
     
@@ -243,7 +293,7 @@
             dictionary should contain the keys "ClientId", "Keepalive",
             "CleanSession", "Username", "Password", "WillTopic", "WillMessage",
             "WillQos", "WillRetain", "TlsEnable", "TlsCaCert", "TlsClientCert",
-            "TlsClientKey"
+            "TlsClientKey", "ConnectionTimeout"
         @type dict
         """
         if options:
@@ -255,6 +305,7 @@
                 clientId=parametersDict["ClientId"],
                 cleanSession=parametersDict["CleanSession"]
             )
+            self.setConnectionTimeout(parametersDict["ConnectionTimeout"])
             
             # step 2: set username and password
             if parametersDict["Username"]:
@@ -294,7 +345,8 @@
             
             # step 5: connect to server
             self.connectToServer(host, port=port,
-                                 keepalive=parametersDict["Keepalive"])
+                                 keepalive=parametersDict["Keepalive"],
+                                 reinit=False)
         else:
             keepalive = self.defaultConnectionOptions["Keepalive"]
             self.connectToServer(host, port=port, keepalive=keepalive,
@@ -308,11 +360,13 @@
         @return dictionary containing the default connection options. It has
             the keys "ClientId", "Keepalive", "CleanSession", "Username",
             "Password", "WillTopic", "WillMessage", "WillQos", "WillRetain",
-            "TlsEnable", "TlsCaCert", "TlsClientCert", "TlsClientKey".
+            "TlsEnable", "TlsCaCert", "TlsClientCert", "TlsClientKey",
+            "ConnectionTimeout".
         @rtype dict
         """
         return {
             "ClientId": "ERIC6_MQTT_MONITOR_CLIENT",
+            "ConnectionTimeout": MqttClient.DefaultConnectTimeout,
             "Keepalive": 60,
             "CleanSession": True,
             "Username": "",
@@ -331,6 +385,8 @@
         """
         Public method to reconnect the client with the same parameters.
         """
+        self.__connectTimeoutTimer.start()
+        
         self.__mqttClient.reconnect()
         
         if not self.__loopStarted:
@@ -340,6 +396,8 @@
         """
         Public method to disconnect the client from the remote broker.
         """
+        self.__connectTimeoutTimer.stop()
+        
         self.__mqttClient.disconnect()
     
     def subscribe(self, topic, qos=0):
@@ -389,7 +447,7 @@
 
 def mqttConnackMessage(connackCode):
     """
-    Public method to get the string associated with a CONNACK result.
+    Module function to get the string associated with a CONNACK result.
     
     @param connackCode result code of the connection request
     @type int
@@ -428,7 +486,7 @@
 
 def mqttErrorMessage(mqttErrno):
     """
-    Public method to get the error string associated with an MQTT error
+    Module function to get the error string associated with an MQTT error
     number.
     
     @param mqttErrno result code of a MQTT request
@@ -501,3 +559,37 @@
         return QCoreApplication.translate(
             "MqttErrorMessage",
             "Unknown error.")
+
+
+def mqttLogLevelString(mqttLogLevel, isMqttLogLevel=True):
+    """
+    Module function to get the log level string associated with a log level.
+    
+    @param mqttLogLevel log level of the paho-mqtt client
+    @type int
+    @param isMqttLogLevel flag indicating a MQTT log level is given (if
+        False it is the MqttClient variant, i.e. Debug being lowest)
+    @type bool
+    @return textual representation of the log level
+    @rtype str
+    """
+    if isMqttLogLevel:
+        try:
+            logLevel = MqttClient.LogLevelMap[mqttLogLevel]
+        except KeyError:
+            return QCoreApplication.translate("MqttLogLevelString", "Unknown")
+    else:
+        logLevel = mqttLogLevel
+    
+    if logLevel == MqttClient.LogInfo:
+        return QCoreApplication.translate("MqttLogLevelString", "Info")
+    elif logLevel == MqttClient.LogNotice:
+        return QCoreApplication.translate("MqttLogLevelString", "Notice")
+    elif logLevel == MqttClient.LogWarning:
+        return QCoreApplication.translate("MqttLogLevelString", "Warning")
+    elif logLevel == MqttClient.LogError:
+        return QCoreApplication.translate("MqttLogLevelString", "Error")
+    elif logLevel == MqttClient.LogDebug:
+        return QCoreApplication.translate("MqttLogLevelString", "Debug")
+    else:
+        return QCoreApplication.translate("MqttLogLevelString", "Unknown")
--- a/MqttMonitor/MqttConnectionOptionsDialog.py	Sun Sep 09 12:21:19 2018 +0200
+++ b/MqttMonitor/MqttConnectionOptionsDialog.py	Sun Sep 09 17:32:54 2018 +0200
@@ -34,7 +34,8 @@
         @param options dictionary containing the connection options to
             populate the dialog with. It must have the keys "ClientId",
             "Keepalive", "CleanSession", "Username", "Password", "WillTopic",
-            "WillMessage", "WillQos", "WillRetain", "TlsEnable", "TlsCaCert".
+            "WillMessage", "WillQos", "WillRetain", "TlsEnable", "TlsCaCert",
+            "ConnectionTimeout".
         @type dict
         @param parent reference to the parent widget
         @type QWidget
@@ -97,7 +98,8 @@
         @param options dictionary containing the connection options to populate
             the dialog with. It must have the keys "ClientId", "Keepalive",
             "CleanSession", "Username", "Password", "WillTopic", "WillMessage",
-            "WillQos", "WillRetain", "TlsEnable", "TlsCaCert".
+            "WillQos", "WillRetain", "TlsEnable", "TlsCaCert",
+            "ConnectionTimeout".
         @type dict
         """
         if options is None:
@@ -105,6 +107,7 @@
         
         # general
         self.clientIdEdit.setText(options["ClientId"])
+        self.connectionTimeoutSpinBox.setValue(options["ConnectionTimeout"])
         self.keepaliveSpinBox.setValue(options["Keepalive"])
         self.cleanSessionCheckBox.setChecked(options["CleanSession"])
         
@@ -129,11 +132,12 @@
         @return dictionary containing the connection options. It has the keys
             "ClientId", "Keepalive", "CleanSession", "Username", "Password",
             "WillTopic", "WillMessage", "WillQos", "WillRetain", "TlsEnable",
-            "TlsCaCert".
+            "TlsCaCert", "ConnectionTimeout".
         @rtype tuple of (int, dict)
         """
         return {
             "ClientId": self.clientIdEdit.text(),
+            "ConnectionTimeout": self.connectionTimeoutSpinBox.value(),
             "Keepalive": self.keepaliveSpinBox.value(),
             "CleanSession": self.cleanSessionCheckBox.isChecked(),
             "Username": self.usernameEdit.text(),
--- a/MqttMonitor/MqttConnectionOptionsDialog.ui	Sun Sep 09 12:21:19 2018 +0200
+++ b/MqttMonitor/MqttConnectionOptionsDialog.ui	Sun Sep 09 17:32:54 2018 +0200
@@ -7,7 +7,7 @@
     <x>0</x>
     <y>0</y>
     <width>550</width>
-    <height>600</height>
+    <height>650</height>
    </rect>
   </property>
   <property name="windowTitle">
@@ -48,13 +48,59 @@
        </widget>
       </item>
       <item row="1" column="0">
+       <widget class="QLabel" name="label_6">
+        <property name="text">
+         <string>Connection Timeout:</string>
+        </property>
+       </widget>
+      </item>
+      <item row="1" column="1" colspan="2">
+       <layout class="QHBoxLayout" name="horizontalLayout_2">
+        <item>
+         <widget class="QSpinBox" name="connectionTimeoutSpinBox">
+          <property name="toolTip">
+           <string>Enter the connection timeout 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="maximum">
+           <number>300</number>
+          </property>
+          <property name="singleStep">
+           <number>5</number>
+          </property>
+          <property name="value">
+           <number>15</number>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <spacer name="horizontalSpacer_2">
+          <property name="orientation">
+           <enum>Qt::Horizontal</enum>
+          </property>
+          <property name="sizeHint" stdset="0">
+           <size>
+            <width>148</width>
+            <height>20</height>
+           </size>
+          </property>
+         </spacer>
+        </item>
+       </layout>
+      </item>
+      <item row="2" column="0">
        <widget class="QLabel" name="label_2">
         <property name="text">
          <string>Keep Alive Interval:</string>
         </property>
        </widget>
       </item>
-      <item row="1" column="1" colspan="2">
+      <item row="2" column="1" colspan="2">
        <layout class="QHBoxLayout" name="horizontalLayout">
         <item>
          <widget class="QSpinBox" name="keepaliveSpinBox">
@@ -93,7 +139,7 @@
         </item>
        </layout>
       </item>
-      <item row="2" column="0" colspan="3">
+      <item row="3" column="0" colspan="3">
        <widget class="QCheckBox" name="cleanSessionCheckBox">
         <property name="toolTip">
          <string>Select to start with a clean session</string>
@@ -278,6 +324,7 @@
  <tabstops>
   <tabstop>clientIdEdit</tabstop>
   <tabstop>generateIdButton</tabstop>
+  <tabstop>connectionTimeoutSpinBox</tabstop>
   <tabstop>keepaliveSpinBox</tabstop>
   <tabstop>cleanSessionCheckBox</tabstop>
   <tabstop>usernameEdit</tabstop>
@@ -286,6 +333,8 @@
   <tabstop>willMessageEdit</tabstop>
   <tabstop>willQosSpinBox</tabstop>
   <tabstop>willRetainCheckBox</tabstop>
+  <tabstop>tlsEnableCheckBox</tabstop>
+  <tabstop>tlsCertsFilePicker</tabstop>
  </tabstops>
  <resources/>
  <connections>
@@ -296,8 +345,8 @@
    <slot>accept()</slot>
    <hints>
     <hint type="sourcelabel">
-     <x>257</x>
-     <y>590</y>
+     <x>266</x>
+     <y>640</y>
     </hint>
     <hint type="destinationlabel">
      <x>157</x>
@@ -312,8 +361,8 @@
    <slot>reject()</slot>
    <hints>
     <hint type="sourcelabel">
-     <x>325</x>
-     <y>590</y>
+     <x>334</x>
+     <y>640</y>
     </hint>
     <hint type="destinationlabel">
      <x>286</x>
@@ -328,12 +377,12 @@
    <slot>setEnabled(bool)</slot>
    <hints>
     <hint type="sourcelabel">
-     <x>82</x>
-     <y>523</y>
+     <x>103</x>
+     <y>572</y>
     </hint>
     <hint type="destinationlabel">
-     <x>92</x>
-     <y>544</y>
+     <x>164</x>
+     <y>596</y>
     </hint>
    </hints>
   </connection>
--- a/MqttMonitor/MqttConnectionProfilesDialog.py	Sun Sep 09 12:21:19 2018 +0200
+++ b/MqttMonitor/MqttConnectionProfilesDialog.py	Sun Sep 09 17:32:54 2018 +0200
@@ -40,7 +40,7 @@
             "BrokerAddress", "BrokerPort", "ClientId",
             "Keepalive", "CleanSession", "Username", "Password", "WillTopic",
             "WillMessage", "WillQos", "WillRetain", "TlsEnable", "TlsCaCert",
-            "TlsClientCert", "TlsClientKey".
+            "TlsClientCert", "TlsClientKey", "ConnectionTimeout".
         @type dict
         @param parent reference to the parent widget
         @type QWidget
@@ -231,7 +231,8 @@
             connection profiles. Each entry have the keys "BrokerAddress",
             "BrokerPort", "ClientId", "Keepalive", "CleanSession", "Username",
             "Password", "WillTopic", "WillMessage", "WillQos", "WillRetain",
-            "TlsEnable", "TlsCaCert", "TlsClientCert", "TlsClientKey".
+            "TlsEnable", "TlsCaCert", "TlsClientCert", "TlsClientKey",
+            "ConnectionTimeout".
         @rtype dict
         """
         profilesDict = {}
@@ -250,6 +251,7 @@
             "BrokerAddress": self.brokerAddressEdit.text(),
             "BrokerPort": self.brokerPortSpinBox.value(),
             "ClientId": self.clientIdEdit.text(),
+            "ConnectionTimeout": self.connectionTimeoutSpinBox.value(),
             "Keepalive": self.keepaliveSpinBox.value(),
             "CleanSession": self.cleanSessionCheckBox.isChecked(),
             "Username": self.usernameEdit.text(),
@@ -336,6 +338,7 @@
         self.brokerAddressEdit.setText(profile["BrokerAddress"])
         self.brokerPortSpinBox.setValue(profile["BrokerPort"])
         self.clientIdEdit.setText(profile["ClientId"])
+        self.connectionTimeoutSpinBox.setValue(profile["ConnectionTimeout"])
         self.keepaliveSpinBox.setValue(profile["Keepalive"])
         self.cleanSessionCheckBox.setChecked(profile["CleanSession"])
         self.usernameEdit.setText(profile["Username"])
@@ -427,6 +430,8 @@
                 self.brokerAddressEdit.text() != profile["BrokerAddress"] or
                 self.brokerPortSpinBox.value() != profile["BrokerPort"] or
                 self.clientIdEdit.text() != profile["ClientId"] or
+                self.connectionTimeoutSpinBox.value() !=
+                profile["ConnectionTimeout"] or
                 self.keepaliveSpinBox.value() != profile["Keepalive"] or
                 self.cleanSessionCheckBox.isChecked() !=
                 profile["CleanSession"] or
--- a/MqttMonitor/MqttConnectionProfilesDialog.ui	Sun Sep 09 12:21:19 2018 +0200
+++ b/MqttMonitor/MqttConnectionProfilesDialog.ui	Sun Sep 09 17:32:54 2018 +0200
@@ -208,7 +208,7 @@
        <item>
         <widget class="QTabWidget" name="profileTabWidget">
          <property name="currentIndex">
-          <number>0</number>
+          <number>3</number>
          </property>
          <widget class="QWidget" name="generalTab">
           <attribute name="title">
@@ -216,13 +216,59 @@
           </attribute>
           <layout class="QGridLayout" name="gridLayout_2">
            <item row="0" column="0">
+            <widget class="QLabel" name="label_12">
+             <property name="text">
+              <string>Connection Timeout:</string>
+             </property>
+            </widget>
+           </item>
+           <item row="0" column="1">
+            <layout class="QHBoxLayout" name="horizontalLayout_5">
+             <item>
+              <widget class="QSpinBox" name="connectionTimeoutSpinBox">
+               <property name="toolTip">
+                <string>Enter the connection timeout 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="maximum">
+                <number>300</number>
+               </property>
+               <property name="singleStep">
+                <number>5</number>
+               </property>
+               <property name="value">
+                <number>15</number>
+               </property>
+              </widget>
+             </item>
+             <item>
+              <spacer name="horizontalSpacer_6">
+               <property name="orientation">
+                <enum>Qt::Horizontal</enum>
+               </property>
+               <property name="sizeHint" stdset="0">
+                <size>
+                 <width>148</width>
+                 <height>20</height>
+                </size>
+               </property>
+              </spacer>
+             </item>
+            </layout>
+           </item>
+           <item row="1" column="0">
             <widget class="QLabel" name="label_5">
              <property name="text">
               <string>Keep Alive Interval:</string>
              </property>
             </widget>
            </item>
-           <item row="0" column="1">
+           <item row="1" column="1">
             <layout class="QHBoxLayout" name="horizontalLayout_4">
              <item>
               <widget class="QSpinBox" name="keepaliveSpinBox">
@@ -261,7 +307,7 @@
              </item>
             </layout>
            </item>
-           <item row="1" column="0" colspan="2">
+           <item row="2" column="0" colspan="2">
             <widget class="QCheckBox" name="cleanSessionCheckBox">
              <property name="toolTip">
               <string>Select to start with a clean session</string>
@@ -271,7 +317,7 @@
              </property>
             </widget>
            </item>
-           <item row="2" column="1">
+           <item row="3" column="1">
             <spacer name="verticalSpacer">
              <property name="orientation">
               <enum>Qt::Vertical</enum>
@@ -673,6 +719,7 @@
   <tabstop>clientIdEdit</tabstop>
   <tabstop>generateIdButton</tabstop>
   <tabstop>profileTabWidget</tabstop>
+  <tabstop>connectionTimeoutSpinBox</tabstop>
   <tabstop>keepaliveSpinBox</tabstop>
   <tabstop>cleanSessionCheckBox</tabstop>
   <tabstop>usernameEdit</tabstop>
--- a/MqttMonitor/MqttMonitorWidget.py	Sun Sep 09 12:21:19 2018 +0200
+++ b/MqttMonitor/MqttMonitorWidget.py	Sun Sep 09 17:32:54 2018 +0200
@@ -26,7 +26,8 @@
 
 from .Ui_MqttMonitorWidget import Ui_MqttMonitorWidget
 
-from .MqttClient import MqttClient, mqttConnackMessage, mqttErrorMessage
+from .MqttClient import MqttClient, mqttConnackMessage, mqttErrorMessage, \
+    mqttLogLevelString
 
 import UI.PixmapCache
 import Utilities
@@ -60,6 +61,16 @@
         self.pixmapLabel.setPixmap(UI.PixmapCache.getPixmap(
             os.path.join("MqttMonitor", "icons", "mqtt48.png")))
         
+        for logLevel in (MqttClient.LogDebug,
+                         MqttClient.LogInfo,
+                         MqttClient.LogNotice,
+                         MqttClient.LogWarning,
+                         MqttClient.LogError):
+            self.logLevelComboBox.addItem(mqttLogLevelString(
+                logLevel, isMqttLogLevel=False), logLevel)
+        self.logLevelComboBox.setCurrentIndex(
+            self.logLevelComboBox.count() - 1)
+        
         self.brokerWidget.setCurrentIndex(0)
         
         self.__connectionModeProfile = True
@@ -135,10 +146,13 @@
         # connect the MQTT client signals
         self.__client.onConnect.connect(self.__brokerConnected)
         self.__client.onDisconnected.connect(self.__brokerDisconnected)
+        self.__client.onLog.connect(self.__clientLog)
         self.__client.onMessage.connect(self.__messageReceived)
         self.__client.onPublish.connect(self.__messagePublished)
         self.__client.onSubscribe.connect(self.__topicSubscribed)
         self.__client.onUnsubscribe.connect(self.__topicUnsubscribed)
+        
+        self.__client.connectTimeout.connect(self.__connectTimeout)
     
     #######################################################################
     ## Slots handling MQTT related signals
@@ -183,6 +197,14 @@
         else:
             self.__client.stopLoop()
     
+    @pyqtSlot()
+    def __connectTimeout(self):
+        """
+        Private slot handling a timeout during a connection attempt.
+        """
+        self.__flashBrokerStatusLabel(self.tr("Connection timed out"))
+        self.__setConnectButtonState()
+    
     @pyqtSlot(int)
     def __brokerDisconnected(self, rc):
         """
@@ -203,7 +225,7 @@
         self.__flashBrokerStatusLabel(msg)
         
         self.connectButton.setIcon(UI.PixmapCache.getIcon("ircConnect.png"))
-        self.connectButton.setEnabled(True)
+        self.__setConnectButtonState()
 
         self.__subscribedTopics = []
         self.__topicQueue = {}
@@ -217,6 +239,34 @@
         
         self.__statusLoadValues.clear()
     
+    @pyqtSlot(int, str)
+    def __clientLog(self, level, message):
+        """
+        Private slot to handle the receipt of a log message.
+        
+        @param level log level
+        @type int
+        @param message log message
+        @type str
+        """
+        try:
+            if MqttClient.LogLevelMap[level] < self.logLevelComboBox.itemData(
+                    self.logLevelComboBox.currentIndex()):
+                return
+        except KeyError:
+            # always show unknown log levels
+            pass
+        
+        txt = self.tr("{0}: {1}").format(mqttLogLevelString(level), message)
+        if not txt.endswith(("\r\n", "\r", "\n")):
+            txt += "\n"
+        
+        tc = self.logEdit.textCursor()
+        tc.movePosition(QTextCursor.End)
+        self.logEdit.setTextCursor(tc)
+        self.logEdit.insertPlainText(Utilities.filterAnsiSequences(txt))
+        self.logEdit.ensureCursorVisible()
+    
     @pyqtSlot(str, bytes, int, bool)
     def __messageReceived(self, topic, payload, qos, retain):
         """
@@ -309,6 +359,16 @@
         self.__setConnectionMode(not self.__connectionModeProfile)
     
     @pyqtSlot(str)
+    def on_profileComboBox_currentIndexChanged(self, profileName):
+        """
+        Private slot handling the change of the selected profile.
+        
+        @param profileName name of the selected profile
+        @type str
+        """
+        self.__setConnectButtonState()
+    
+    @pyqtSlot(str)
     def on_brokerComboBox_editTextChanged(self, host):
         """
         Private slot to handling entering or selecting a broker host name.
@@ -730,7 +790,7 @@
         if host:
             self.brokerStatusLabel.setText(
                 self.tr("Connecting to {0}:{1} ...").format(
-                host, port))
+                    host, port))
             self.brokerStatusLabel.show()
             
             self.__addBrokerToRecent(host, port)
@@ -756,7 +816,7 @@
             
             self.brokerStatusLabel.setText(
                 self.tr("Connecting to {0}:{1} ...").format(
-                host, port))
+                    host, port))
             self.brokerStatusLabel.show()
             
             self.connectButton.setEnabled(False)
--- a/MqttMonitor/MqttMonitorWidget.ui	Sun Sep 09 12:21:19 2018 +0200
+++ b/MqttMonitor/MqttMonitorWidget.ui	Sun Sep 09 17:32:54 2018 +0200
@@ -144,7 +144,7 @@
    <item>
     <widget class="QTabWidget" name="brokerWidget">
      <property name="currentIndex">
-      <number>0</number>
+      <number>3</number>
      </property>
      <widget class="QWidget" name="pubSubTab">
       <attribute name="title">
@@ -479,8 +479,8 @@
            <rect>
             <x>0</x>
             <y>0</y>
-            <width>339</width>
-            <height>670</height>
+            <width>178</width>
+            <height>840</height>
            </rect>
           </property>
           <layout class="QFormLayout" name="formLayout">
@@ -1197,6 +1197,80 @@
        </item>
       </layout>
      </widget>
+     <widget class="QWidget" name="logTab">
+      <attribute name="title">
+       <string>Log</string>
+      </attribute>
+      <layout class="QVBoxLayout" name="verticalLayout_6">
+       <item>
+        <layout class="QHBoxLayout" name="horizontalLayout_17">
+         <item>
+          <widget class="QLabel" name="label_41">
+           <property name="text">
+            <string>Max. Log Level:</string>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QComboBox" name="logLevelComboBox">
+           <property name="sizePolicy">
+            <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+             <horstretch>0</horstretch>
+             <verstretch>0</verstretch>
+            </sizepolicy>
+           </property>
+           <property name="toolTip">
+            <string>Select the maximum log level to show</string>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </item>
+       <item>
+        <widget class="QPlainTextEdit" name="logEdit">
+         <property name="tabChangesFocus">
+          <bool>true</bool>
+         </property>
+         <property name="lineWrapMode">
+          <enum>QPlainTextEdit::NoWrap</enum>
+         </property>
+         <property name="readOnly">
+          <bool>true</bool>
+         </property>
+         <property name="textInteractionFlags">
+          <set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <layout class="QHBoxLayout" name="horizontalLayout_16">
+         <item>
+          <spacer name="horizontalSpacer_4">
+           <property name="orientation">
+            <enum>Qt::Horizontal</enum>
+           </property>
+           <property name="sizeHint" stdset="0">
+            <size>
+             <width>40</width>
+             <height>20</height>
+            </size>
+           </property>
+          </spacer>
+         </item>
+         <item>
+          <widget class="QPushButton" name="logClearButton">
+           <property name="toolTip">
+            <string>Press to clear the list of received log messages</string>
+           </property>
+           <property name="text">
+            <string>Clear</string>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </item>
+      </layout>
+     </widget>
     </widget>
    </item>
   </layout>
@@ -1232,10 +1306,13 @@
   <tabstop>publishRetainCheckBox</tabstop>
   <tabstop>publishClearButton</tabstop>
   <tabstop>publishButton</tabstop>
+  <tabstop>clearPublishCheckBox</tabstop>
   <tabstop>messagesEdit</tabstop>
   <tabstop>messagesClearButton</tabstop>
   <tabstop>brokerStatusButton</tabstop>
   <tabstop>scrollArea</tabstop>
+  <tabstop>logEdit</tabstop>
+  <tabstop>logClearButton</tabstop>
  </tabstops>
  <resources/>
  <connections>
@@ -1271,5 +1348,21 @@
     </hint>
    </hints>
   </connection>
+  <connection>
+   <sender>logClearButton</sender>
+   <signal>clicked()</signal>
+   <receiver>logEdit</receiver>
+   <slot>clear()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>354</x>
+     <y>559</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>274</x>
+     <y>456</y>
+    </hint>
+   </hints>
+  </connection>
  </connections>
 </ui>

eric ide

mercurial