Mon, 27 Aug 2018 19:26:27 +0200
Fixed some code style issues and implemented the broker connect/disconnect group of the MqttMonitorWidget.
--- a/MqttMonitor/MqttClient.py Sun Aug 26 19:40:15 2018 +0200 +++ b/MqttMonitor/MqttClient.py Mon Aug 27 19:26:27 2018 +0200 @@ -174,106 +174,118 @@ return self.__mqttClient.publish(topic, payload=payload, qos=qos, retain=retain) + def mqttConnackMessage(connackCode): """ Public method to get the string associated with a CONNACK result. + + @param connackCode result code of the connection request + @type int + @return textual representation for the result code + @rtype str """ if connackCode == mqtt.CONNACK_ACCEPTED: return QCoreApplication.translate( - "MqttConnackMessage", + "MqttConnackMessage", "Connection Accepted.") elif connackCode == mqtt.CONNACK_REFUSED_PROTOCOL_VERSION: return QCoreApplication.translate( - "MqttConnackMessage", + "MqttConnackMessage", "Connection Refused: unacceptable protocol version.") elif connackCode == mqtt.CONNACK_REFUSED_IDENTIFIER_REJECTED: return QCoreApplication.translate( - "MqttConnackMessage", + "MqttConnackMessage", "Connection Refused: identifier rejected.") elif connackCode == mqtt.CONNACK_REFUSED_SERVER_UNAVAILABLE: return QCoreApplication.translate( - "MqttConnackMessage", + "MqttConnackMessage", "Connection Refused: broker unavailable.") elif connackCode == mqtt.CONNACK_REFUSED_BAD_USERNAME_PASSWORD: return QCoreApplication.translate( - "MqttConnackMessage", + "MqttConnackMessage", "Connection Refused: bad user name or password.") elif connackCode == mqtt.CONNACK_REFUSED_NOT_AUTHORIZED: return QCoreApplication.translate( - "MqttConnackMessage", + "MqttConnackMessage", "Connection Refused: not authorised.") else: return QCoreApplication.translate( - "MqttConnackMessage", + "MqttConnackMessage", "Connection Refused: unknown reason.") + def mqttErrorMessage(self, mqttErrno): """ Public method to get the error string associated with an MQTT error number. + + @param mqttErrno result code of a MQTT request + @type int + @return textual representation of the result code + @rtype str """ if mqttErrno == mqtt.MQTT_ERR_SUCCESS: return QCoreApplication.translate( - "MqttErrorMessage", + "MqttErrorMessage", "No error.") elif mqttErrno == mqtt.MQTT_ERR_NOMEM: return QCoreApplication.translate( - "MqttErrorMessage", + "MqttErrorMessage", "Out of memory.") elif mqttErrno == mqtt.MQTT_ERR_PROTOCOL: return QCoreApplication.translate( - "MqttErrorMessage", + "MqttErrorMessage", "A network protocol error occurred when communicating with" " the broker.") elif mqttErrno == mqtt.MQTT_ERR_INVAL: return QCoreApplication.translate( - "MqttErrorMessage", + "MqttErrorMessage", "Invalid function arguments provided.") elif mqttErrno == mqtt.MQTT_ERR_NO_CONN: return QCoreApplication.translate( - "MqttErrorMessage", + "MqttErrorMessage", "The client is not currently connected.") elif mqttErrno == mqtt.MQTT_ERR_CONN_REFUSED: return QCoreApplication.translate( - "MqttErrorMessage", + "MqttErrorMessage", "The connection was refused.") elif mqttErrno == mqtt.MQTT_ERR_NOT_FOUND: return QCoreApplication.translate( - "MqttErrorMessage", + "MqttErrorMessage", "Message not found (internal error).") elif mqttErrno == mqtt.MQTT_ERR_CONN_LOST: return QCoreApplication.translate( - "MqttErrorMessage", + "MqttErrorMessage", "The connection was lost.") elif mqttErrno == mqtt.MQTT_ERR_TLS: return QCoreApplication.translate( - "MqttErrorMessage", + "MqttErrorMessage", "A TLS error occurred.") elif mqttErrno == mqtt.MQTT_ERR_PAYLOAD_SIZE: return QCoreApplication.translate( - "MqttErrorMessage", + "MqttErrorMessage", "Payload too large.") elif mqttErrno == mqtt.MQTT_ERR_NOT_SUPPORTED: return QCoreApplication.translate( - "MqttErrorMessage", + "MqttErrorMessage", "This feature is not supported.") elif mqttErrno == mqtt.MQTT_ERR_AUTH: return QCoreApplication.translate( - "MqttErrorMessage", + "MqttErrorMessage", "Authorisation failed.") elif mqttErrno == mqtt.MQTT_ERR_ACL_DENIED: return QCoreApplication.translate( - "MqttErrorMessage", + "MqttErrorMessage", "Access denied by ACL.") elif mqttErrno == mqtt.MQTT_ERR_UNKNOWN: return QCoreApplication.translate( - "MqttErrorMessage", + "MqttErrorMessage", "Unknown error.") elif mqttErrno == mqtt.MQTT_ERR_ERRNO: return QCoreApplication.translate( - "MqttErrorMessage", + "MqttErrorMessage", "Error defined by errno.") else: return QCoreApplication.translate( - "MqttErrorMessage", + "MqttErrorMessage", "Unknown error.")
--- a/MqttMonitor/MqttMonitorWidget.py Sun Aug 26 19:40:15 2018 +0200 +++ b/MqttMonitor/MqttMonitorWidget.py Mon Aug 27 19:26:27 2018 +0200 @@ -1,34 +1,213 @@ # -*- coding: utf-8 -*- +# Copyright (c) 2018 Detlev Offenbach <detlev@die-offenbachs.de> +# + """ -Module implementing MqttMonitorWidget. +Module implementing the MQTT Monitor widget. """ from __future__ import unicode_literals import os -from PyQt5.QtCore import pyqtSlot +from PyQt5.QtCore import pyqtSlot, QTimer from PyQt5.QtWidgets import QWidget from .Ui_MqttMonitorWidget import Ui_MqttMonitorWidget +from .MqttClient import MqttClient, mqttConnackMessage, mqttErrorMessage + import UI.PixmapCache class MqttMonitorWidget(QWidget, Ui_MqttMonitorWidget): """ - Class documentation goes here. + Class implementing the MQTT Monitor widget. """ - def __init__(self, parent=None): + def __init__(self, plugin, parent=None): """ Constructor + @param plugin reference to the plug-in object + @type MqttMonitorPlugin @param parent reference to the parent widget @type QWidget """ super(MqttMonitorWidget, self).__init__(parent) self.setupUi(self) + self.__plugin = plugin + + self.__connectedToBroker = False + self.pixmapLabel.setPixmap(UI.PixmapCache.getPixmap( os.path.join("MqttMonitor", "icons", "mqtt48.png"))) + + self.connectButton.setIcon(UI.PixmapCache.getIcon("ircConnect.png")) + self.brokerComboBox.addItems( + self.__plugin.getPreferences("RecentBrokers")) + self.brokerStatusLabel.hide() + + self.__client = MqttClient() + + # connect the MQTT client signals + self.__client.onConnect.connect(self.__brokerConnected) + self.__client.onDisconnected.connect(self.__brokerDisconnected) + self.__client.onMessage.connect(self.__messageReceived) + self.__client.onPublish.connect(self.__messagePublished) + self.__client.onSubscribe.connect(self.__topicSubscribed) + self.__client.onUnsubscribe.connect(self.__topicUnsubscribed) + + ####################################################################### + ## Slots handling MQTT related signals + ####################################################################### + + @pyqtSlot(dict, int) + def __brokerConnected(self, flags, rc): + """ + Private slot to handle being connected to a broker. + + @param flags flags set for the connection + @type dict + @param rc CONNACK result code + @type int + """ + if rc == 0: + self.__connectedToBroker = True + + msg = mqttConnackMessage(rc) + self.__flashBrokerStatusLabel(msg) + + self.connectButton.setIcon(UI.PixmapCache.getIcon("ircDisconnect.png")) + + @pyqtSlot(int) + def __brokerDisconnected(self, rc): + """ + Private slot to handle a disconnection from the broker. + + @param rc MQTT error result code + @type int + """ + self.__connectedToBroker = False + + if rc != 0: + msg = mqttErrorMessage(rc) + else: + msg = self.tr("Connection to Broker shut down cleanly.") + self.__flashBrokerStatusLabel(msg) + + self.connectButton.setIcon(UI.PixmapCache.getIcon("ircConnect.png")) + + self.__client.stopLoop() + + @pyqtSlot(str, bytes, int, bool) + def __messageReceived(self, topic, payload, qos, retain): + """ + Private slot to handle the receipt of a message. + + @param topic topic of the message + @type str + @param payload payload (i.e. data) of the message + @type bytes + @param qos quality of service indicator + @type int + @param retain flag indicating a retained message + @type bool + """ + pass + + @pyqtSlot(int) + def __messagePublished(self, mid): + """ + Private slot to handle a message being published. + + @param mid ID of the published message + @type int + """ + pass + + @pyqtSlot(int, tuple) + def __topicSubscribed(self, mid, grantedQos): + """ + Private slot to handle being subscribed to topics. + + @param mid ID of the subscribe request + @type int + @param grantedQos tuple of granted quality of service + @type tuple of int + """ + pass + + @pyqtSlot(int) + def __topicUnsubscribed(self, mid): + """ + Private slot to handle being unsubcribed from a topic. + + @param mid ID of the unsubscribe request + @type int + """ + pass + + ####################################################################### + ## Slots handling UI interactions + ####################################################################### + + @pyqtSlot() + def __flashBrokerStatusLabel(self, message): + """ + Private slot to show the broker status label with some text for + 5 seconds. + + @param message message to be shown + @type str + """ + self.brokerStatusLabel.setText(message) + self.brokerStatusLabel.show() + QTimer.singleShot(5000, self.brokerStatusLabel.hide) + + @pyqtSlot(str) + def on_brokerComboBox_editTextChanged(self, host): + """ + Private slot to handling entering or selecting a broker host name. + + @param host host name of the broker + @type str + """ + if not self.__connectedToBroker and not host: + self.connectButton.setEnabled(False) + else: + self.connectButton.setEnabled(True) + + @pyqtSlot() + def on_connectButton_clicked(self): + """ + Private slot to handle a connect or disconnect request. + """ + if self.__connectedToBroker: + self.__client.disconnectFromServer() + else: + host = self.brokerComboBox.currentText() + self.__addBrokerToRecent(host) + self.__client.connectToServer(host) # use standard port at 1883 + + ####################################################################### + ## Utility methods + ####################################################################### + + def __addBrokerToRecent(self, host): + """ + Private method to add a host name to the list of recently connected + brokers. + + @param host host name of broker + @type str + """ + brokerList = self.__plugin.getPreferences("RecentBrokers") + if host in brokerList: + brokerList.remove(host) + brokerList.insert(0, host) + self.__plugin.setPreferences("RecentBrokers", brokerList) + + self.brokerComboBox.clear() + self.brokerComboBox.addItems(brokerList)
--- a/MqttMonitor/MqttMonitorWidget.ui Sun Aug 26 19:40:15 2018 +0200 +++ b/MqttMonitor/MqttMonitorWidget.ui Mon Aug 27 19:26:27 2018 +0200 @@ -35,6 +35,48 @@ </layout> </item> <item> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>Broker</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <widget class="QComboBox" name="brokerComboBox"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="toolTip"> + <string>Enter the host name of the broker</string> + </property> + <property name="editable"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QToolButton" name="connectButton"> + <property name="toolTip"> + <string>Press to connect to/disconnect from the broker</string> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item row="1" column="0" colspan="2"> + <widget class="QLabel" name="brokerStatusLabel"> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> <spacer name="verticalSpacer"> <property name="orientation"> <enum>Qt::Vertical</enum>
--- a/PluginMqttMonitor.e4p Sun Aug 26 19:40:15 2018 +0200 +++ b/PluginMqttMonitor.e4p Mon Aug 27 19:26:27 2018 +0200 @@ -157,4 +157,165 @@ <FiletypeAssociation pattern="Ui_*.py" type="__IGNORE__"/> <FiletypeAssociation pattern="makefile" type="OTHERS"/> </FiletypeAssociations> + <Checkers> + <CheckersParams> + <dict> + <key> + <string>Pep8Checker</string> + </key> + <value> + <dict> + <key> + <string>BlankLines</string> + </key> + <value> + <tuple> + <int>2</int> + <int>1</int> + </tuple> + </value> + <key> + <string>BuiltinsChecker</string> + </key> + <value> + <dict> + <key> + <string>bytes</string> + </key> + <value> + <list> + <string>unicode</string> + </list> + </value> + <key> + <string>chr</string> + </key> + <value> + <list> + <string>unichr</string> + </list> + </value> + <key> + <string>str</string> + </key> + <value> + <list> + <string>unicode</string> + </list> + </value> + </dict> + </value> + <key> + <string>CopyrightAuthor</string> + </key> + <value> + <string></string> + </value> + <key> + <string>CopyrightMinFileSize</string> + </key> + <value> + <int>0</int> + </value> + <key> + <string>DocstringType</string> + </key> + <value> + <string>eric</string> + </value> + <key> + <string>ExcludeFiles</string> + </key> + <value> + <string>*/Ui_*.py, */*_rc.py</string> + </value> + <key> + <string>ExcludeMessages</string> + </key> + <value> + <string>C101, E265, E266, E305, E402, M201, M811, N802, N803, N807, N808, N821, W293, W504</string> + </value> + <key> + <string>FixCodes</string> + </key> + <value> + <string></string> + </value> + <key> + <string>FixIssues</string> + </key> + <value> + <bool>False</bool> + </value> + <key> + <string>FutureChecker</string> + </key> + <value> + <string>unicode_literals</string> + </value> + <key> + <string>HangClosing</string> + </key> + <value> + <bool>False</bool> + </value> + <key> + <string>IncludeMessages</string> + </key> + <value> + <string></string> + </value> + <key> + <string>LineComplexity</string> + </key> + <value> + <int>20</int> + </value> + <key> + <string>LineComplexityScore</string> + </key> + <value> + <int>10</int> + </value> + <key> + <string>MaxCodeComplexity</string> + </key> + <value> + <int>10</int> + </value> + <key> + <string>MaxLineLength</string> + </key> + <value> + <int>79</int> + </value> + <key> + <string>NoFixCodes</string> + </key> + <value> + <string>E501</string> + </value> + <key> + <string>RepeatMessages</string> + </key> + <value> + <bool>True</bool> + </value> + <key> + <string>ShowIgnored</string> + </key> + <value> + <bool>False</bool> + </value> + <key> + <string>ValidEncodings</string> + </key> + <value> + <string>latin-1, utf-8</string> + </value> + </dict> + </value> + </dict> + </CheckersParams> + </Checkers> </Project>
--- a/PluginMqttMonitor.py Sun Aug 26 19:40:15 2018 +0200 +++ b/PluginMqttMonitor.py Mon Aug 27 19:26:27 2018 +0200 @@ -18,13 +18,14 @@ from E5Gui.E5Action import E5Action import UI.PixmapCache +import Preferences # Start-Of-Header name = "MQTT Monitor Plugin" author = "Detlev Offenbach <detlev@die-offenbachs.de>" autoactivate = True deactivateable = True -version = "1.x" +version = "1.0.0" className = "MqttMonitorPlugin" packageName = "MqttMonitor" shortDescription = "Plug-in implementing a tool to connect to a MQTT broker" @@ -66,10 +67,19 @@ return data +def prepareUninstall(): + """ + Module function to prepare for an uninstallation. + """ + Preferences.Prefs.settings.remove(MqttMonitorPlugin.PreferencesKey) + + class MqttMonitorPlugin(QObject): """ Class implementing the MQTT Monitor plug-in. """ + PreferencesKey = "MqttMonitor" + def __init__(self, ui): """ Constructor @@ -81,6 +91,10 @@ self.__ui = ui self.__initialize() + self.__defaults = { + "RecentBrokers": [], + } + self.__translator = None self.__loadTranslator() @@ -106,12 +120,9 @@ error = self.tr("The 'paho-mqtt' package is not available.") return None, False -## self.__object = MqttMonitorWidget(self, self.__ui) -## self.__object.activate() -## e5App().registerPluginObject("MqttMonitor", self.__object) from MqttMonitor.MqttMonitorWidget import MqttMonitorWidget - self.__widget = MqttMonitorWidget()##self.__ui) + self.__widget = MqttMonitorWidget(self) self.__ui.addSideWidget( self.__ui.RightSide, self.__widget, UI.PixmapCache.getIcon( @@ -143,8 +154,6 @@ """ Public method to deactivate this plug-in. """ -## e5App().unregisterPluginObject("TimeTracker") -## self.__object.deactivate() menu = self.__ui.getMenu("subwindow") menu.removeAction(self.__activateAct) self.__ui.removeE5Actions([self.__activateAct], 'ui') @@ -191,3 +200,30 @@ else: self.__widget.show() self.__widget.setFocus(Qt.ActiveWindowFocusReason) + + def getPreferences(self, key): + """ + Public method to retrieve the various settings. + + @param key the key of the value to get + @return the requested setting + """ + if key in ["RecentBrokers"]: + return Preferences.toList(Preferences.Prefs.settings.value( + self.PreferencesKey + "/" + key, self.__defaults[key])) + else: + return Preferences.Prefs.settings.value( + self.PreferencesKey + "/" + key, self.__defaults[key]) + + def setPreferences(self, key, value): + """ + Public method to store the various settings. + + @param key the key of the setting to be set (string) + @param value the value to be set + """ + Preferences.Prefs.settings.setValue( + self.PreferencesKey + "/" + key, value) + +# +# eflag: noqa = M801