--- a/MqttMonitor/MqttMonitorWidget.py Thu Dec 30 16:36:40 2021 +0100 +++ b/MqttMonitor/MqttMonitorWidget.py Wed Sep 21 09:42:33 2022 +0200 @@ -23,7 +23,10 @@ from .Ui_MqttMonitorWidget import Ui_MqttMonitorWidget from .MqttClient import ( - MqttClient, mqttConnackMessage, mqttErrorMessage, mqttLogLevelString + MqttClient, + mqttConnackMessage, + mqttErrorMessage, + mqttLogLevelString, ) from .MqttReasonCodes import mqttReasonCode from .MqttProtocols import MqttProtocols @@ -36,14 +39,15 @@ """ Class implementing the MQTT Monitor widget. """ + BrokerStatusTopicPrefix = "$SYS/broker/" BrokerStatusTopic = "$SYS/broker/#" BrokerStatusTopicLoadPrefix = "$SYS/broker/load/" - + def __init__(self, plugin, usesDarkPalette, parent=None): """ Constructor - + @param plugin reference to the plug-in object @type MqttMonitorPlugin @param usesDarkPalette flag indicating the use of a dark application @@ -54,30 +58,34 @@ """ super().__init__(parent) self.setupUi(self) - + self.layout().setContentsMargins(0, 3, 0, 0) - + self.__plugin = plugin self.__iconSuffix = "dark" if usesDarkPalette else "light" - + self.__connectedToBroker = False self.__brokerStatusTopicSubscribed = False - + with contextlib.suppress(AttributeError): # backward compatibility if not ericApp().usesSmallScreen(): - self.pixmapLabel.setPixmap(UI.PixmapCache.getPixmap( - os.path.join("MqttMonitor", "icons", - "mqtt48-{0}".format(self.__iconSuffix)) - )) - - self.publishPayloadFilePicker.setMode( - EricPathPickerModes.OPEN_FILE_MODE) + self.pixmapLabel.setPixmap( + UI.PixmapCache.getPixmap( + os.path.join( + "MqttMonitor", + "icons", + "mqtt48-{0}".format(self.__iconSuffix), + ) + ) + ) + + self.publishPayloadFilePicker.setMode(EricPathPickerModes.OPEN_FILE_MODE) self.publishPayloadFilePicker.setFilters(self.tr("All Files (*)")) - + self.brokerComboBox.lineEdit().setClearButtonEnabled(True) self.publishTopicComboBox.lineEdit().setClearButtonEnabled(True) - + self.__messagesFormat = self.messagesEdit.currentCharFormat() self.__messagesTopicFormat = self.messagesEdit.currentCharFormat() self.__messagesTopicFormat.setFontWeight(QFont.Weight.Bold) @@ -85,30 +93,32 @@ self.__messagesQosFormat.setFontItalic(True) self.__messagesSubheaderFormat = self.messagesEdit.currentCharFormat() self.__messagesSubheaderFormat.setFontUnderline(True) - + self.__propertiesFormat = self.propertiesEdit.currentCharFormat() self.__propertiesTopicFormat = self.propertiesEdit.currentCharFormat() self.__propertiesTopicFormat.setFontWeight(QFont.Weight.Bold) self.__propertiesNameFormat = self.propertiesEdit.currentCharFormat() self.__propertiesNameFormat.setFontItalic(True) - + self.messagesSearchWidget.attachTextEdit(self.messagesEdit) self.messagesSearchWidget.setWidthForHeight(False) - + self.__isMessageAlternate = False self.__isPropertiesAlternate = False - - for logLevel in (MqttClient.LogDisabled, - 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) - + + for logLevel in ( + MqttClient.LogDisabled, + 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) + if usesDarkPalette: self.__logMessagesBackgrounds = { MqttClient.LogDebug: QBrush(QColor("#2f2f2f")), @@ -129,61 +139,65 @@ MqttClient.LogDisabled: QBrush(Qt.GlobalColor.magenta) # reuse LogDisabled for unknown log levels } - + self.logSearchWidget.attachTextEdit(self.logEdit) self.logSearchWidget.setWidthForHeight(False) - + self.brokerWidget.setCurrentIndex(0) - + self.__connectionModeProfile = True self.__setConnectionMode(True) # initial mode is 'profile connection' self.__populateProfileComboBox() - + self.connectButton.setIcon(UI.PixmapCache.getIcon("ircConnect")) - self.brokerConnectionOptionsButton.setIcon(UI.PixmapCache.getIcon( - os.path.join("MqttMonitor", "icons", - "connectionOptions-{0}".format(self.__iconSuffix)) - )) + self.brokerConnectionOptionsButton.setIcon( + UI.PixmapCache.getIcon( + os.path.join( + "MqttMonitor", + "icons", + "connectionOptions-{0}".format(self.__iconSuffix), + ) + ) + ) self.__populateBrokerComboBoxes() self.brokerStatusLabel.hide() - self.clearWillButton.setIcon( - UI.PixmapCache.getIcon("certificateDelete")) - + self.clearWillButton.setIcon(UI.PixmapCache.getIcon("certificateDelete")) + self.subscribeTopicComboBox.lineEdit().setClearButtonEnabled(True) self.subscribeTopicComboBox.lineEdit().returnPressed.connect( - self.on_subscribeButton_clicked) + self.on_subscribeButton_clicked + ) self.__populateSubscribeTopicComboBox() - + self.subscribeButton.setIcon(UI.PixmapCache.getIcon("plus")) self.subscribeButton.setEnabled(False) - self.subscribePropertiesButton.setIcon( - UI.PixmapCache.getIcon("listSelection")) + self.subscribePropertiesButton.setIcon(UI.PixmapCache.getIcon("listSelection")) self.subscribePropertiesButton.setEnabled(False) self.subscribePropertiesButton.setVisible(False) - + self.unsubscribeButton.setIcon(UI.PixmapCache.getIcon("minus")) self.unsubscribeButton.setEnabled(False) self.unsubscribePropertiesButton.setIcon( - UI.PixmapCache.getIcon("listSelection")) + UI.PixmapCache.getIcon("listSelection") + ) self.unsubscribePropertiesButton.setEnabled(False) self.unsubscribePropertiesButton.setVisible(False) - + self.__initPropertiesEditMenu() - + self.__subscribedTopics = [] self.__topicQueue = {} self.__updateUnsubscribeTopicComboBox() - + self.__publishedTopics = [] self.__updatePublishTopicComboBox() self.publishButton.setEnabled(False) - self.publishPropertiesButton.setIcon( - UI.PixmapCache.getIcon("listSelection")) + self.publishPropertiesButton.setIcon(UI.PixmapCache.getIcon("listSelection")) self.publishPropertiesButton.setEnabled(False) self.publishPropertiesButton.setVisible(False) - + self.__connectionOptions = None - + prefix = MqttMonitorWidget.BrokerStatusTopicPrefix self.__statusLabelMapping = { # broker @@ -206,10 +220,8 @@ prefix + "retained messages/count": self.messagesRetainedLabel, # publish messages prefix + "publish/messages/sent": self.publishMessagesSentLabel, - prefix + "publish/messages/received": - self.publishMessagesReceivedLabel, - prefix + "publish/messages/dropped": - self.publishMessagesDroppedLabel, + prefix + "publish/messages/received": self.publishMessagesReceivedLabel, + prefix + "publish/messages/dropped": self.publishMessagesDroppedLabel, # traffic prefix + "bytes/sent": self.bytesSentLabel, prefix + "bytes/received": self.bytesReceivedLabel, @@ -224,15 +236,13 @@ prefix + "load/connections": self.loadConnectionsLabel, prefix + "load/sockets": self.loadSocketsLabel, } - - self.__statusLoadValues = collections.defaultdict( - self.__loadDefaultDictFactory) - - def __createClient(self, clientId="", cleanSession=None, - protocol=None): + + self.__statusLoadValues = collections.defaultdict(self.__loadDefaultDictFactory) + + def __createClient(self, clientId="", cleanSession=None, protocol=None): """ Private method to instantiate a MQTT client for a given protocol. - + @param clientId ID to be used for the client @type str @param cleanSession flag indicating to start a clean session @@ -244,10 +254,11 @@ """ if protocol is None: protocol = self.__plugin.getPreferences("DefaultProtocol") - - client = MqttClient(clientId=clientId, cleanSession=cleanSession, - protocol=protocol) - + + client = MqttClient( + clientId=clientId, cleanSession=cleanSession, protocol=protocol + ) + # connect the MQTT client signals client.onConnectV3.connect(self.__brokerConnected) client.onConnectV5.connect(self.__brokerConnected) @@ -261,11 +272,11 @@ client.onSubscribeV5.connect(self.__topicSubscribedV5) client.onUnsubscribeV3.connect(self.__topicUnsubscribed) client.onUnsubscribeV5.connect(self.__topicUnsubscribedV5) - + client.connectTimeout.connect(self.__connectTimeout) - + return client - + def __initPropertiesEditMenu(self): """ Private method to create the properties output context menu. @@ -273,31 +284,36 @@ self.__propertiesEditMenu = QMenu(self) self.__copyPropertiesAct = self.__propertiesEditMenu.addAction( UI.PixmapCache.getIcon("editCopy"), - self.tr("Copy"), self.propertiesEdit.copy) + self.tr("Copy"), + self.propertiesEdit.copy, + ) self.__propertiesEditMenu.addSeparator() self.__selectAllPropertiesAct = self.__propertiesEditMenu.addAction( UI.PixmapCache.getIcon("editSelectAll"), - self.tr("Select All"), self.propertiesEdit.selectAll) + self.tr("Select All"), + self.propertiesEdit.selectAll, + ) self.__propertiesEditMenu.addSeparator() self.__clearPropertiesAct = self.__propertiesEditMenu.addAction( UI.PixmapCache.getIcon("editDelete"), - self.tr("Clear"), self.propertiesEdit.clear) - - self.propertiesEdit.copyAvailable.connect( - self.__copyPropertiesAct.setEnabled) - + self.tr("Clear"), + self.propertiesEdit.clear, + ) + + self.propertiesEdit.copyAvailable.connect(self.__copyPropertiesAct.setEnabled) + self.__copyPropertiesAct.setEnabled(False) - + ####################################################################### ## Slots handling MQTT related signals ####################################################################### - + @pyqtSlot(dict, int) @pyqtSlot(dict, int, int, dict) def __brokerConnected(self, flags, rc, packetType=None, properties=None): """ Private slot to handle being connected to a broker. - + @param flags flags set for the connection @type dict @param rc CONNACK result code or MQTTv5 reason code @@ -309,53 +325,55 @@ @type dict """ self.brokerStatusLabel.hide() - + if rc == 0: self.__connectedToBroker = True self.__connectionOptions = None - + try: sessionPresent = flags["session present"] == 1 except KeyError: sessionPresent = False - + msg = ( mqttReasonCode(rc, packetType) - if packetType is not None else - mqttConnackMessage(rc) + if packetType is not None + else mqttConnackMessage(rc) ) if sessionPresent: msg = self.tr("{0} - Session still present").format(msg) self.__flashBrokerStatusLabel(msg) - + if properties: self.__showProperties("Connect", properties) - + self.connectButton.setEnabled(True) if rc == 0: self.__connectedToBroker = True self.__connectionOptions = None - - self.connectButton.setIcon( - UI.PixmapCache.getIcon("ircDisconnect")) - + + self.connectButton.setIcon(UI.PixmapCache.getIcon("ircDisconnect")) + self.subscribeGroup.setEnabled(True) self.subscribePropertiesButton.setVisible( - self.__client.getProtocol() == MqttProtocols.MQTTv5) + self.__client.getProtocol() == MqttProtocols.MQTTv5 + ) self.unsubscribeGroup.setEnabled(True) self.unsubscribePropertiesButton.setVisible( - self.__client.getProtocol() == MqttProtocols.MQTTv5) + self.__client.getProtocol() == MqttProtocols.MQTTv5 + ) self.publishGroup.setEnabled(True) self.brokerStatusButton.setEnabled(True) self.publishPropertiesButton.setVisible( - self.__client.getProtocol() == MqttProtocols.MQTTv5) - + self.__client.getProtocol() == MqttProtocols.MQTTv5 + ) + self.__statusLoadValues.clear() self.__clearBrokerStatusLabels() self.__setBrokerStatusSubscribed(False) else: self.__client.stopLoop() - + @pyqtSlot() def __connectTimeout(self): """ @@ -363,36 +381,37 @@ """ self.__flashBrokerStatusLabel(self.tr("Connection timed out")) self.__setConnectButtonState() - + @pyqtSlot(int) @pyqtSlot(int, int) def __brokerDisconnected(self, rc, packetType=None): """ Private slot to handle a disconnection from the broker. - + @param rc MQTT error result code @type int @param packetType packet type as reported by the client @type int """ self.__connectedToBroker = False - + # ensure, the client loop is stopped self.__client.stopLoop() - + msg = ( # MQTT v5 mqttReasonCode(rc, packetType) - if packetType is not None else + if packetType is not None + else # MQTT v3 ( mqttErrorMessage(rc) - if rc > 0 else - self.tr("Connection to Broker shut down cleanly.") + if rc > 0 + else self.tr("Connection to Broker shut down cleanly.") ) ) self.__flashBrokerStatusLabel(msg) - + self.connectButton.setIcon(UI.PixmapCache.getIcon("ircConnect")) self.__setConnectButtonState() @@ -400,7 +419,7 @@ self.__topicQueue = {} self.__updateUnsubscribeTopicComboBox() self.__updatePublishTopicComboBox() - + self.subscribeGroup.setEnabled(False) self.subscribePropertiesButton.setVisible(False) self.unsubscribeGroup.setEnabled(False) @@ -408,14 +427,14 @@ self.publishGroup.setEnabled(False) self.publishPropertiesButton.setVisible(False) self.brokerStatusButton.setEnabled(False) - + 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 @@ -423,42 +442,45 @@ """ with contextlib.suppress(KeyError): if MqttClient.LogLevelMap[level] < self.logLevelComboBox.itemData( - self.logLevelComboBox.currentIndex()): + self.logLevelComboBox.currentIndex() + ): return - + scrollbarValue = self.logEdit.verticalScrollBar().value() - + textCursor = self.logEdit.textCursor() if not self.logEdit.document().isEmpty(): textCursor.movePosition(QTextCursor.MoveOperation.End) self.logEdit.setTextCursor(textCursor) self.logEdit.insertPlainText("\n") - + textBlockFormat = textCursor.blockFormat() try: textBlockFormat.setBackground( - self.__logMessagesBackgrounds[MqttClient.LogLevelMap[level]]) + self.__logMessagesBackgrounds[MqttClient.LogLevelMap[level]] + ) except KeyError: textBlockFormat.setBackground( - self.__logMessagesBackgrounds[MqttClient.LogDisabled]) + self.__logMessagesBackgrounds[MqttClient.LogDisabled] + ) textCursor.setBlockFormat(textBlockFormat) textCursor.movePosition(QTextCursor.MoveOperation.End) self.logEdit.setTextCursor(textCursor) - + txt = self.tr("{0}: {1}").format(mqttLogLevelString(level), message) self.logEdit.insertPlainText(Utilities.filterAnsiSequences(txt)) - + if self.followLogMessagesCheckBox.isChecked(): self.logEdit.ensureCursorVisible() else: self.logEdit.verticalScrollBar().setValue(scrollbarValue) - + @pyqtSlot(str, bytes, int, bool) @pyqtSlot(str, bytes, int, bool, dict) def __messageReceived(self, topic, payload, qos, retain, properties=None): """ 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 @@ -474,26 +496,25 @@ # handle broker status messages self.__handleBrokerStatusMessage(topic, payload) else: - self.__appendMessage(topic, payload, qos, retain, - properties=properties) - + self.__appendMessage(topic, payload, qos, retain, properties=properties) + @pyqtSlot(int) def __messagePublished(self, mid): """ Private slot to handle a message being published. - + @param mid ID of the published message @type int """ # nothing to show for this pass - + @pyqtSlot(int) def __topicSubscribed(self, mid): """ Private slot to handle being subscribed to topics (MQTT v3.1, MQTT v3.1.1). - + @param mid ID of the subscribe request @type int """ @@ -501,15 +522,15 @@ topic = self.__topicQueue.pop(mid) self.__subscribedTopics.append(topic) self.__addTopicToRecent(topic) - + self.__updateUnsubscribeTopicComboBox() self.__updatePublishTopicComboBox() - + @pyqtSlot(int, list, dict) def __topicSubscribedV5(self, mid, reasonCodes, properties): """ Private slot to handle being subscribed to topics (MQTT v5). - + @param mid ID of the subscribe request @type int @param reasonCodes list of reason codes, one for each topic @@ -520,18 +541,18 @@ """ msg = mqttReasonCode(reasonCodes[0].value, reasonCodes[0].packetType) self.__flashBrokerStatusLabel(msg) - + if properties: self.__showProperties("Subscribe", properties) - + self.__topicSubscribed(mid) - + @pyqtSlot(int) def __topicUnsubscribed(self, mid): """ Private slot to handle being unsubcribed from a topic (MQTT v3.1, MQTT v3.1.1). - + @param mid ID of the unsubscribe request @type int """ @@ -541,12 +562,12 @@ self.__subscribedTopics.remove(topic) self.__updateUnsubscribeTopicComboBox() self.__updatePublishTopicComboBox() - + @pyqtSlot(int, int, int, dict) def __topicUnsubscribedV5(self, mid, rc, packetType, properties): """ Private slot to handle being unsubscribed to topics (MQTT v5). - + @param mid ID of the subscribe request @type int @param rc MQTTv5 reason code @@ -559,29 +580,29 @@ """ msg = mqttReasonCode(rc, packetType) self.__flashBrokerStatusLabel(msg) - + if properties: self.__showProperties("Unsubscribe", properties) - + self.__topicUnsubscribed(mid) - + ####################################################################### ## 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() def on_modeButton_clicked(self): """ @@ -589,27 +610,27 @@ connection mode. """ 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. - + @param host host name of the broker @type str """ self.__setConnectButtonState() - + @pyqtSlot() def on_brokerConnectionOptionsButton_clicked(self): """ @@ -617,23 +638,22 @@ dialog to edit connection profiles. """ if self.__connectionModeProfile: - from .MqttConnectionProfilesDialog import ( - MqttConnectionProfilesDialog - ) + from .MqttConnectionProfilesDialog import MqttConnectionProfilesDialog + profileName = self.profileComboBox.currentText() dlg = MqttConnectionProfilesDialog( self.__plugin.getPreferences("BrokerProfiles"), - currentProfile=profileName, parent=self) + currentProfile=profileName, + parent=self, + ) if dlg.exec() == QDialog.DialogCode.Accepted: profilesDict = dlg.getProfiles() self.__plugin.setPreferences("BrokerProfiles", profilesDict) self.__populateProfileComboBox() else: - from .MqttConnectionOptionsDialog import ( - MqttConnectionOptionsDialog - ) - dlg = MqttConnectionOptionsDialog( - self.__connectionOptions, parent=self) + from .MqttConnectionOptionsDialog import MqttConnectionOptionsDialog + + dlg = MqttConnectionOptionsDialog(self.__connectionOptions, parent=self) if dlg.exec() == QDialog.DialogCode.Accepted: self.__connectionOptions = dlg.getConnectionOptions() if self.__connectionOptions["TlsEnable"]: @@ -646,7 +666,7 @@ if port == "8883": # it is default TLS port => set to non-encrypted port self.brokerPortComboBox.setEditText("1883") - + @pyqtSlot() def on_connectButton_clicked(self): """ @@ -659,7 +679,7 @@ self.__profileConnectToBroker() else: self.__directConnectToBroker() - + @pyqtSlot() def on_subscribePropertiesButton_clicked(self): """ @@ -669,20 +689,20 @@ self.__editProperties( "subscribe", self.tr("SUBSCRIBE: User Properties for '{0}'").format(topic), - topic + topic, ) - + @pyqtSlot(str) def on_subscribeTopicComboBox_editTextChanged(self, topic): """ Private slot to handle a change of the entered topic. - + @param topic entered topic text @type str """ self.subscribeButton.setEnabled(bool(topic)) self.subscribePropertiesButton.setEnabled(bool(topic)) - + @pyqtSlot() def on_subscribeButton_clicked(self): """ @@ -695,19 +715,22 @@ EricMessageBox.warning( self, self.tr("Subscribe to Topic"), - self.tr("Subscriptions to the Status topic '$SYS' shall" - " be done on the 'Status' tab.")) + self.tr( + "Subscriptions to the Status topic '$SYS' shall" + " be done on the 'Status' tab." + ), + ) else: properties = ( - self.__plugin.getPreferences("SubscribeProperties") - .get(topic, []) - if self.__client.getProtocol() == MqttProtocols.MQTTv5 else - None + self.__plugin.getPreferences("SubscribeProperties").get(topic, []) + if self.__client.getProtocol() == MqttProtocols.MQTTv5 + else None ) result, mid = self.__client.subscribe( - topic, qos=qos, properties=properties) + topic, qos=qos, properties=properties + ) self.__topicQueue[mid] = topic - + @pyqtSlot() def on_unsubscribePropertiesButton_clicked(self): """ @@ -717,20 +740,20 @@ self.__editProperties( "unsubscribe", self.tr("UNSUBSCRIBE: User Properties for '{0}'").format(topic), - topic + topic, ) - + @pyqtSlot(str) def on_unsubscribeTopicComboBox_currentIndexChanged(self, topic): """ Private slot to handle the selection of a topic to unsubscribe from. - + @param topic topic text @type str """ self.unsubscribeButton.setEnabled(bool(topic)) self.unsubscribePropertiesButton.setEnabled(bool(topic)) - + @pyqtSlot() def on_unsubscribeButton_clicked(self): """ @@ -739,15 +762,13 @@ topic = self.unsubscribeTopicComboBox.currentText() if topic: properties = ( - self.__plugin.getPreferences("SubscribeProperties") - .get(topic, []) - if self.__client.getProtocol() == MqttProtocols.MQTTv5 else - None + self.__plugin.getPreferences("SubscribeProperties").get(topic, []) + if self.__client.getProtocol() == MqttProtocols.MQTTv5 + else None ) - result, mid = self.__client.unsubscribe( - topic, properties=properties) + result, mid = self.__client.unsubscribe(topic, properties=properties) self.__topicQueue[mid] = topic - + @pyqtSlot() def on_publishPropertiesButton_clicked(self): """ @@ -757,20 +778,20 @@ self.__editProperties( "publish", self.tr("PUBLISH: User Properties for '{0}'").format(topic), - topic + topic, ) - + @pyqtSlot(str) def on_publishTopicComboBox_editTextChanged(self, topic): """ Private slot to handle changes of the publish topic name. - + @param topic topic text @type str """ self.publishButton.setEnabled(bool(topic)) self.publishPropertiesButton.setEnabled(bool(topic)) - + @pyqtSlot() def on_publishButton_clicked(self): """ @@ -781,9 +802,9 @@ retain = self.publishRetainCheckBox.isChecked() payloadFile = self.publishPayloadFilePicker.text() if ( - bool(payloadFile) and - os.path.exists(payloadFile) and - os.path.getsize(payloadFile) <= 268435455 + bool(payloadFile) + and os.path.exists(payloadFile) + and os.path.getsize(payloadFile) <= 268435455 ): # payload size limit is 268,435,455 bytes try: @@ -793,9 +814,11 @@ EricMessageBox.critical( self, self.tr("Read Payload from File"), - self.tr("""<p>The file <b>{0}</b> could not be read.""" - """ Aborting...</p><p>Reason: {1}</p>""").format( - payloadFile, str(err))) + self.tr( + """<p>The file <b>{0}</b> could not be read.""" + """ Aborting...</p><p>Reason: {1}</p>""" + ).format(payloadFile, str(err)), + ) return else: payloadStr = self.publishPayloadEdit.toPlainText() @@ -805,20 +828,20 @@ payloadStr = None properties = ( self.__plugin.getPreferences("PublishProperties").get(topic, []) - if self.__client.getProtocol() == MqttProtocols.MQTTv5 else - None + if self.__client.getProtocol() == MqttProtocols.MQTTv5 + else None ) - + msgInfo = self.__client.publish( - topic, payload=payloadStr, qos=qos, retain=retain, - properties=properties) + topic, payload=payloadStr, qos=qos, retain=retain, properties=properties + ) if msgInfo.rc == 0: if topic not in self.__publishedTopics: self.__publishedTopics.append(topic) self.__updatePublishTopicComboBox(resetTopic=False) if self.clearPublishCheckBox.isChecked(): self.on_publishClearButton_clicked() - + @pyqtSlot() def on_publishClearRetainedButton_clicked(self): """ @@ -827,17 +850,18 @@ topic = self.publishTopicComboBox.currentText() properties = ( self.__plugin.getPreferences("PublishProperties").get(topic, []) - if self.__client.getProtocol() == MqttProtocols.MQTTv5 else - None + if self.__client.getProtocol() == MqttProtocols.MQTTv5 + else None ) - + msgInfo = self.__client.publish( - topic, payload=None, retain=True, properties=properties) + topic, payload=None, retain=True, properties=properties + ) if msgInfo.rc == 0: if topic not in self.__publishedTopics: self.__publishedTopics.append(topic) self.__updatePublishTopicComboBox(resetTopic=False) - + @pyqtSlot() def on_publishClearButton_clicked(self): """ @@ -848,27 +872,27 @@ self.publishQosSpinBox.setValue(0) self.publishRetainCheckBox.setChecked(False) self.publishPayloadFilePicker.clear() - + @pyqtSlot(str) def on_publishPayloadFilePicker_textChanged(self, path): """ Private slot handling a change of path of the payload file. - + @param path path of the payload file @type str """ self.publishPayloadEdit.setEnabled(not bool(path)) - + @pyqtSlot(QPoint) def on_propertiesEdit_customContextMenuRequested(self, pos): """ Private slot to show the context menu for the properties output. - + @param pos the position of the mouse pointer @type QPoint """ self.__propertiesEditMenu.popup(self.propertiesEdit.mapToGlobal(pos)) - + @pyqtSlot() def on_brokerStatusButton_clicked(self): """ @@ -876,31 +900,29 @@ """ if self.__brokerStatusTopicSubscribed: # unsubscribe status topic - rc, _ = self.__client.unsubscribe( - MqttMonitorWidget.BrokerStatusTopic) + rc, _ = self.__client.unsubscribe(MqttMonitorWidget.BrokerStatusTopic) if rc == 0: # successfully sent self.__setBrokerStatusSubscribed(False) else: # subscribe status topic - rc, _ = self.__client.subscribe( - MqttMonitorWidget.BrokerStatusTopic) + rc, _ = self.__client.subscribe(MqttMonitorWidget.BrokerStatusTopic) if rc == 0: # successfully sent self.__setBrokerStatusSubscribed(True) - + @pyqtSlot(int) def on_messagesEdit_blockCountChanged(self, newBlockCount): """ Private slot handling changes of received messages. - + @param newBlockCount (ignored) @type int """ enable = not self.messagesEdit.document().isEmpty() self.saveMessagesButton.setEnabled(enable) self.clearMessagesButton.setEnabled(enable) - + @pyqtSlot() def on_saveMessagesButton_clicked(self): """ @@ -912,12 +934,13 @@ "", self.tr("Messages Files (*.txt);;All Files (*)"), "", - EricFileDialog.DontConfirmOverwrite) - + EricFileDialog.DontConfirmOverwrite, + ) + if fn: if fn.endswith("."): fn = fn[:-1] - + ext = QFileInfo(fn).suffix() if not ext: ex = selectedFilter.split("(*")[1].split(")")[0] @@ -927,12 +950,14 @@ res = EricMessageBox.yesNo( self, self.tr("Save Messages"), - self.tr("<p>The file <b>{0}</b> already exists." - " Overwrite it?</p>").format(fn), - icon=EricMessageBox.Warning) + self.tr( + "<p>The file <b>{0}</b> already exists." " Overwrite it?</p>" + ).format(fn), + icon=EricMessageBox.Warning, + ) if not res: return - + fn = Utilities.toNativeSeparators(fn) try: with open(fn, "w") as f: @@ -941,22 +966,24 @@ EricMessageBox.critical( self, self.tr("Save Messages"), - self.tr("""<p>The file <b>{0}</b> could not be written.""" - """</p><p>Reason: {1}</p>""").format( - fn, str(err))) - + self.tr( + """<p>The file <b>{0}</b> could not be written.""" + """</p><p>Reason: {1}</p>""" + ).format(fn, str(err)), + ) + @pyqtSlot(int) def on_logEdit_blockCountChanged(self, newBlockCount): """ Private slot handling changes of received messages. - + @param newBlockCount (ignored) @type int """ enable = not self.logEdit.document().isEmpty() self.saveLogMessagesButton.setEnabled(enable) self.clearLogMessagesButton.setEnabled(enable) - + @pyqtSlot() def on_saveLogMessagesButton_clicked(self): """ @@ -968,12 +995,13 @@ "", self.tr("Log Files (*.log);;All Files (*)"), "", - EricFileDialog.DontConfirmOverwrite) - + EricFileDialog.DontConfirmOverwrite, + ) + if fn: if fn.endswith("."): fn = fn[:-1] - + ext = QFileInfo(fn).suffix() if not ext: ex = selectedFilter.split("(*")[1].split(")")[0] @@ -983,12 +1011,14 @@ res = EricMessageBox.yesNo( self, self.tr("Save Log Messages"), - self.tr("<p>The file <b>{0}</b> already exists." - " Overwrite it?</p>").format(fn), - icon=EricMessageBox.Warning) + self.tr( + "<p>The file <b>{0}</b> already exists." " Overwrite it?</p>" + ).format(fn), + icon=EricMessageBox.Warning, + ) if not res: return - + fn = Utilities.toNativeSeparators(fn) try: with open(fn, "w") as f: @@ -997,19 +1027,21 @@ EricMessageBox.critical( self, self.tr("Save Log Messages"), - self.tr("""<p>The file <b>{0}</b> could not be written.""" - """</p><p>Reason: {1}</p>""").format( - fn, str(err))) - + self.tr( + """<p>The file <b>{0}</b> could not be written.""" + """</p><p>Reason: {1}</p>""" + ).format(fn, str(err)), + ) + ####################################################################### ## Utility methods ####################################################################### - + def __setBrokerStatusSubscribed(self, subscribed): """ Private method to set the subscription status for the broker status topics. - + @param subscribed subscription status for the broker status topics @type bool """ @@ -1017,17 +1049,19 @@ if subscribed: self.brokerStatusButton.setText(self.tr("Unsubscribe")) self.brokerStatusButton.setToolTip( - self.tr("Press to deactivate the status display")) + self.tr("Press to deactivate the status display") + ) else: self.brokerStatusButton.setText(self.tr("Subscribe")) self.brokerStatusButton.setToolTip( - self.tr("Press to activate the status display")) - + self.tr("Press to activate the status display") + ) + def __addBrokerToRecent(self, host, port): """ Private method to add a host name to the list of recently connected brokers. - + @param host host name of broker @type str @param port port number of the connection @@ -1042,60 +1076,59 @@ maxBrokers = self.__plugin.getPreferences("RecentBrokersNumber") brokerList = brokerList[:maxBrokers] self.__plugin.setPreferences("RecentBrokersWithPort", brokerList) - + self.__populateBrokerComboBoxes() - + def __populateBrokerComboBoxes(self): """ Private method to populate the broker name and port combo boxes. """ brokerPortList = self.__plugin.getPreferences("RecentBrokersWithPort") - + # step 1: clear combo boxes self.brokerComboBox.clear() self.brokerPortComboBox.clear() - + # step 2a: populate the broker name list currentBroker = brokerPortList[0][0] if brokerPortList else "" brokerSet = {b[0].strip() for b in brokerPortList} self.brokerComboBox.addItems(sorted(brokerSet)) index = self.brokerComboBox.findText(currentBroker) self.brokerComboBox.setCurrentIndex(index) - + # step 2b: populate the broker ports list currentPort = brokerPortList[0][1] if brokerPortList else 1883 currentPortStr = "{0:5}".format(currentPort) portsSet = {b[1] for b in brokerPortList} portsSet.update({1883, 8883}) - self.brokerPortComboBox.addItems( - sorted("{0:5}".format(p) for p in portsSet)) + self.brokerPortComboBox.addItems(sorted("{0:5}".format(p) for p in portsSet)) index = self.brokerPortComboBox.findText(currentPortStr) self.brokerPortComboBox.setCurrentIndex(index) - + # step 3: update the connect button state self.__setConnectButtonState() - + def __populateProfileComboBox(self): """ Private method to populate the profiles selection box. """ profilesDict = self.__plugin.getPreferences("BrokerProfiles") mostRecentProfile = self.__plugin.getPreferences("MostRecentProfile") - + self.profileComboBox.clear() self.profileComboBox.addItems(sorted(profilesDict.keys())) if mostRecentProfile: index = self.profileComboBox.findText(mostRecentProfile) if index >= 0: self.profileComboBox.setCurrentIndex(index) - + self.__setConnectButtonState() - + def __addTopicToRecent(self, topic): """ Private method to add a topic to the list of recently subscribed topics. - + @param topic subscribed topic @type str """ @@ -1107,51 +1140,50 @@ maxTopics = self.__plugin.getPreferences("RecentTopicsNumber") topicsList = topicsList[:maxTopics] self.__plugin.setPreferences("RecentTopics", topicsList) - + self.__populateSubscribeTopicComboBox() - + def __populateSubscribeTopicComboBox(self): """ Private method to populate the subscribe topic combo box. """ topicsList = self.__plugin.getPreferences("RecentTopics") - + self.subscribeTopicComboBox.clear() self.subscribeTopicComboBox.addItems(sorted(topicsList)) self.subscribeTopicComboBox.clearEditText() - + def __updateUnsubscribeTopicComboBox(self): """ Private method to update the unsubcribe topic combo box. """ self.unsubscribeTopicComboBox.clear() self.unsubscribeTopicComboBox.addItems(sorted(self.__subscribedTopics)) - self.unsubscribeButton.setEnabled( - bool(self.__subscribedTopics)) - self.unsubscribePropertiesButton.setEnabled( - bool(self.__subscribedTopics)) - + self.unsubscribeButton.setEnabled(bool(self.__subscribedTopics)) + self.unsubscribePropertiesButton.setEnabled(bool(self.__subscribedTopics)) + def __updatePublishTopicComboBox(self, resetTopic=True): """ Private method to update the publish topic combo box. - + @param resetTopic flag indicating to reset the topic @type bool """ currentTopic = self.publishTopicComboBox.currentText() self.publishTopicComboBox.clear() self.publishTopicComboBox.addItems( - list(set(self.__publishedTopics + self.__subscribedTopics))) + list(set(self.__publishedTopics + self.__subscribedTopics)) + ) if resetTopic: self.publishTopicComboBox.clearEditText() else: topicIndex = self.publishTopicComboBox.findText(currentTopic) self.publishTopicComboBox.setCurrentIndex(topicIndex) - + def __appendMessage(self, topic, payload, qos, retain, properties=None): """ Private method to append a received message to the output. - + @param topic topic of the received message @type str @param payload payload of the received message @@ -1164,45 +1196,43 @@ @type dict """ scrollbarValue = self.messagesEdit.verticalScrollBar().value() - + textCursor = self.messagesEdit.textCursor() if not self.messagesEdit.document().isEmpty(): textCursor.movePosition(QTextCursor.MoveOperation.End) self.messagesEdit.setTextCursor(textCursor) self.messagesEdit.insertPlainText("\n") - + textBlockFormat = textCursor.blockFormat() if self.__isMessageAlternate: - textBlockFormat.setBackground( - self.messagesEdit.palette().alternateBase()) + textBlockFormat.setBackground(self.messagesEdit.palette().alternateBase()) else: - textBlockFormat.setBackground( - self.messagesEdit.palette().base()) + textBlockFormat.setBackground(self.messagesEdit.palette().base()) textCursor.setBlockFormat(textBlockFormat) textCursor.movePosition(QTextCursor.MoveOperation.End) self.messagesEdit.setTextCursor(textCursor) - + self.messagesEdit.setCurrentCharFormat(self.__messagesTopicFormat) self.messagesEdit.insertPlainText(topic + "\n") - + self.messagesEdit.setCurrentCharFormat(self.__messagesQosFormat) self.messagesEdit.insertPlainText(self.tr("QoS: {0}\n").format(qos)) - + if retain: self.messagesEdit.setCurrentCharFormat(self.__messagesQosFormat) self.messagesEdit.insertPlainText(self.tr("Retained Message\n")) - + if properties: - self.messagesEdit.setCurrentCharFormat( - self.__messagesSubheaderFormat) + self.messagesEdit.setCurrentCharFormat(self.__messagesSubheaderFormat) self.messagesEdit.insertPlainText(self.tr("Properties:\n")) self.messagesEdit.setCurrentCharFormat(self.__messagesFormat) for name, value in sorted(properties.items()): self.messagesEdit.insertPlainText( - self.tr("{0}: {1}\n", "property name, property value") - .format(name, value) + self.tr("{0}: {1}\n", "property name, property value").format( + name, value + ) ) - + self.messagesEdit.setCurrentCharFormat(self.__messagesSubheaderFormat) self.messagesEdit.insertPlainText(self.tr("Message:\n")) payloadStr = str(payload, encoding="utf-8", errors="replace") @@ -1212,18 +1242,18 @@ self.messagesEdit.insertPlainText(payloadStr) else: self.messagesEdit.insertPlainText(self.tr("<empty>")) - + if self.followMessagesCheckBox.isChecked(): self.messagesEdit.ensureCursorVisible() else: self.messagesEdit.verticalScrollBar().setValue(scrollbarValue) - + self.__isMessageAlternate = not self.__isMessageAlternate - + def __handleBrokerStatusMessage(self, topic, payload): """ Private method to handle a status message of the broker. - + @param topic topic of the received message @type str @param payload payload of the received message @@ -1231,18 +1261,18 @@ """ payloadStr = str(payload, encoding="utf-8", errors="replace").strip() topic = topic.strip() - + if topic.startswith(MqttMonitorWidget.BrokerStatusTopicLoadPrefix): self.__handleBrokerLoadStatusMessage(topic, payloadStr) else: with contextlib.suppress(KeyError): label = self.__statusLabelMapping[topic] label.setText(payloadStr) - + def __handleBrokerLoadStatusMessage(self, topic, payloadStr): """ Private method to append a received message to the output. - + @param topic topic of the received message @type str @param payloadStr string representation of the payload of the @@ -1251,15 +1281,17 @@ """ subtopic, topicElement = topic.rsplit("/", 1) self.__statusLoadValues[subtopic][topicElement] = payloadStr - + with contextlib.suppress(KeyError): label = self.__statusLabelMapping[subtopic] - label.setText("{0} / {1} / {2}".format( - self.__statusLoadValues[subtopic]["1min"], - self.__statusLoadValues[subtopic]["5min"], - self.__statusLoadValues[subtopic]["15min"], - )) - + label.setText( + "{0} / {1} / {2}".format( + self.__statusLoadValues[subtopic]["1min"], + self.__statusLoadValues[subtopic]["5min"], + self.__statusLoadValues[subtopic]["15min"], + ) + ) + def __clearBrokerStatusLabels(self): """ Private method to clear the broker status labels. @@ -1268,15 +1300,16 @@ label = ( "- / - / -" if statusLabelKey.startswith( - MqttMonitorWidget.BrokerStatusTopicLoadPrefix) else - "-" + MqttMonitorWidget.BrokerStatusTopicLoadPrefix + ) + else "-" ) self.__statusLabelMapping[statusLabelKey].setText(label) - + def __loadDefaultDictFactory(self): """ Private method to populate non-existing load items. - + @return default dictionary entry @rtype dict """ @@ -1285,41 +1318,47 @@ "5min": "-", "15min": "-", } - + def __setConnectionMode(self, profileMode): """ Private method to set the connection mode. - + @param profileMode flag indicating the profile connection mode @type bool """ self.__connectionModeProfile = profileMode if profileMode: - self.modeButton.setIcon(UI.PixmapCache.getIcon( - os.path.join("MqttMonitor", "icons", - "profiles-{0}".format(self.__iconSuffix)) - )) + self.modeButton.setIcon( + UI.PixmapCache.getIcon( + os.path.join( + "MqttMonitor", "icons", "profiles-{0}".format(self.__iconSuffix) + ) + ) + ) else: - self.modeButton.setIcon(UI.PixmapCache.getIcon( - os.path.join("MqttMonitor", "icons", - "quickopen-{0}".format(self.__iconSuffix)) - )) - + self.modeButton.setIcon( + UI.PixmapCache.getIcon( + os.path.join( + "MqttMonitor", + "icons", + "quickopen-{0}".format(self.__iconSuffix), + ) + ) + ) + self.profileComboBox.setVisible(profileMode) self.brokerConnectionWidget.setVisible(not profileMode) self.__setConnectButtonState() - + def __setConnectButtonState(self): """ Private method to set the enabled state of the connect button. """ if self.__connectionModeProfile: - self.connectButton.setEnabled( - bool(self.profileComboBox.currentText())) + self.connectButton.setEnabled(bool(self.profileComboBox.currentText())) else: - self.connectButton.setEnabled( - bool(self.brokerComboBox.currentText())) - + self.connectButton.setEnabled(bool(self.brokerComboBox.currentText())) + def __directConnectToBroker(self): """ Private method to connect to the broker with entered data. @@ -1333,33 +1372,35 @@ port = 1883 if host: self.brokerStatusLabel.setText( - self.tr("Connecting to {0}:{1} ...").format( - host, port)) + self.tr("Connecting to {0}:{1} ...").format(host, port) + ) self.brokerStatusLabel.show() - + self.__addBrokerToRecent(host, port) self.connectButton.setEnabled(False) - + if self.clearWillButton.isChecked(): clearWill = True self.clearWillButton.setChecked(False) else: clearWill = False - + if self.__connectionOptions is None: self.__client = self.__createClient() - self.__client.connectToServer( - host, port=port, clearWill=clearWill) + self.__client.connectToServer(host, port=port, clearWill=clearWill) else: self.__client = self.__createClient( clientId=self.__connectionOptions["ClientId"], cleanSession=self.__connectionOptions["CleanSession"], - protocol=self.__connectionOptions["Protocol"] + protocol=self.__connectionOptions["Protocol"], ) self.__client.connectToServerWithOptions( - host, port=port, options=self.__connectionOptions, - clearWill=clearWill) - + host, + port=port, + options=self.__connectionOptions, + clearWill=clearWill, + ) + def __profileConnectToBroker(self): """ Private method to connect to the broker with selected profile. @@ -1367,7 +1408,7 @@ profileName = self.profileComboBox.currentText() if profileName: self.__plugin.setPreferences("MostRecentProfile", profileName) - + profilesDict = self.__plugin.getPreferences("BrokerProfiles") connectionProfile = copy.deepcopy(profilesDict[profileName]) host = connectionProfile["BrokerAddress"] @@ -1376,35 +1417,36 @@ protocol = connectionProfile["Protocol"] except KeyError: protocol = MqttProtocols( - self.__plugin.getPreferences("DefaultProtocol")) - + self.__plugin.getPreferences("DefaultProtocol") + ) + self.brokerStatusLabel.setText( - self.tr("Connecting to {0}:{1} ...").format( - host, port)) + self.tr("Connecting to {0}:{1} ...").format(host, port) + ) self.brokerStatusLabel.show() - + self.connectButton.setEnabled(False) - + if self.clearWillButton.isChecked(): clearWill = True self.clearWillButton.setChecked(False) else: clearWill = False - + self.__client = self.__createClient( clientId=connectionProfile["ClientId"], cleanSession=connectionProfile["CleanSession"], - protocol=protocol + protocol=protocol, ) self.__client.connectToServerWithOptions( - host, port=port, options=connectionProfile, - clearWill=clearWill) - + host, port=port, options=connectionProfile, clearWill=clearWill + ) + def __showProperties(self, typeStr, properties): """ Private method to display the received properties in the properties pane. - + @param typeStr message type @type str @param properties dictionary containing the relevant properties @@ -1414,36 +1456,33 @@ if not self.propertiesEdit.document().isEmpty(): textCursor.movePosition(QTextCursor.MoveOperation.End) self.propertiesEdit.setTextCursor(textCursor) - + textBlockFormat = textCursor.blockFormat() if self.__isPropertiesAlternate: - textBlockFormat.setBackground( - self.propertiesEdit.palette().alternateBase()) + textBlockFormat.setBackground(self.propertiesEdit.palette().alternateBase()) else: - textBlockFormat.setBackground( - self.propertiesEdit.palette().base()) + textBlockFormat.setBackground(self.propertiesEdit.palette().base()) textCursor.setBlockFormat(textBlockFormat) textCursor.movePosition(QTextCursor.MoveOperation.End) self.propertiesEdit.setTextCursor(textCursor) - + self.propertiesEdit.setCurrentCharFormat(self.__propertiesTopicFormat) self.propertiesEdit.insertPlainText(typeStr + "\n") - + for name, value in sorted(properties.items()): - self.propertiesEdit.setCurrentCharFormat( - self.__propertiesNameFormat) + self.propertiesEdit.setCurrentCharFormat(self.__propertiesNameFormat) self.propertiesEdit.insertPlainText("{0}: ".format(name)) self.propertiesEdit.setCurrentCharFormat(self.__propertiesFormat) self.propertiesEdit.insertPlainText("{0}\n".format(str(value))) - + self.propertiesEdit.ensureCursorVisible() - + self.__isPropertiesAlternate = not self.__isPropertiesAlternate - + def __editProperties(self, propertiesType, header, key): """ Private method to edit user properties of a given type. - + @param propertiesType properties type (one of 'subscribe', 'unsubscribe', 'publish') @type str @@ -1453,11 +1492,10 @@ @type str """ from .MqttUserPropertiesEditor import MqttUserPropertiesEditorDialog - + preferencesKey = "{0}Properties".format(propertiesType.capitalize()) properties = self.__plugin.getPreferences(preferencesKey) - dlg = MqttUserPropertiesEditorDialog( - header, properties.get(key, []), self) + dlg = MqttUserPropertiesEditorDialog(header, properties.get(key, []), self) if dlg.exec() == QDialog.DialogCode.Accepted: properties[key] = dlg.getProperties() self.__plugin.setPreferences(preferencesKey, properties)