--- a/MqttMonitor/MqttConnectionProfilesDialog.py Wed Jul 21 20:10:36 2021 +0200 +++ b/MqttMonitor/MqttConnectionProfilesDialog.py Thu Jul 22 19:02:32 2021 +0200 @@ -8,6 +8,7 @@ """ import collections +import copy from PyQt6.QtCore import pyqtSlot, Qt, QUuid from PyQt6.QtWidgets import ( @@ -39,7 +40,8 @@ "BrokerAddress", "BrokerPort", "ClientId", "Protocol", "ConnectionTimeout", "Keepalive", "CleanSession", "Username", "Password", "WillTopic", "WillMessage", "WillQos", "WillRetain", - "TlsEnable", "TlsCaCert", "TlsClientCert", "TlsClientKey". + "TlsEnable", "TlsCaCert", "TlsClientCert", "TlsClientKey", + "UserProperties". @type dict @param parent reference to the parent widget @type QWidget @@ -74,6 +76,11 @@ self.profileTabWidget.setCurrentIndex(0) + self.connectPropertiesButton.clicked[bool].connect( + self.__propertiesTypeSelected) + self.disconnectPropertiesButton.clicked[bool].connect( + self.__propertiesTypeSelected) + if len(self.__profiles) == 0: self.minusButton.setEnabled(False) self.copyButton.setEnabled(False) @@ -236,11 +243,11 @@ Public method to return a dictionary of profiles. @return dictionary containing dictionaries containing the defined - connection profiles. Each entry have the keys "BrokerAddress", + connection profiles. Each entry has the keys "BrokerAddress", "BrokerPort", "ClientId", "Protocol", "ConnectionTimeout", "Keepalive", "CleanSession", "Username", "Password", "WillTopic", "WillMessage", "WillQos", "WillRetain", "TlsEnable", "TlsCaCert", - "TlsClientCert", "TlsClientKey". + "TlsClientCert", "TlsClientKey", "UserProperties". @rtype dict """ profilesDict = {} @@ -263,6 +270,18 @@ else: protocol = MqttProtocols.MQTTv311 + if protocol == MqttProtocols.MQTTv5: + if self.connectPropertiesButton.isChecked(): + self.__userProperties["connect"] = ( + self.propertiesWidget.getProperties()) + else: + self.__userProperties["disconnect"] = ( + self.propertiesWidget.getProperties()) + self.__userProperties["use_connect"] = ( + self.samePropertiesCheckBox.isChecked()) + else: + self.__userProperties = {} + profileName = self.profileEdit.text() connectionProfile = { "BrokerAddress": self.brokerAddressEdit.text(), @@ -282,6 +301,7 @@ "TlsCaCert": "", "TlsClientCert": "", "TlsClientKey": "", + "UserProperties": copy.deepcopy(self.__userProperties), } if connectionProfile["TlsEnable"]: if self.tlsCertsFileButton.isChecked(): @@ -356,23 +376,32 @@ self.brokerAddressEdit.setText(connectionProfile["BrokerAddress"]) self.brokerPortSpinBox.setValue(connectionProfile["BrokerPort"]) self.clientIdEdit.setText(connectionProfile["ClientId"]) + + # general tab self.mqttv31Button.setChecked( connectionProfile["Protocol"] == MqttProtocols.MQTTv31) self.mqttv311Button.setChecked( connectionProfile["Protocol"] == MqttProtocols.MQTTv311) self.mqttv5Button.setChecked( connectionProfile["Protocol"] == MqttProtocols.MQTTv5) + self.on_mqttv5Button_toggled(self.mqttv5Button.isChecked()) self.connectionTimeoutSpinBox.setValue( connectionProfile["ConnectionTimeout"]) self.keepaliveSpinBox.setValue(connectionProfile["Keepalive"]) self.cleanSessionCheckBox.setChecked(connectionProfile["CleanSession"]) + + # user credentials tab self.usernameEdit.setText(connectionProfile["Username"]) self.passwordEdit.setText( pwConvert(connectionProfile["Password"], encode=False)) + + # will tab self.willTopicEdit.setText(connectionProfile["WillTopic"]) self.willMessageEdit.setPlainText(connectionProfile["WillMessage"]) self.willQosSpinBox.setValue(connectionProfile["WillQos"]) self.willRetainCheckBox.setChecked(connectionProfile["WillRetain"]) + + # SSL/TLS tab self.tlsGroupBox.setChecked(connectionProfile["TlsEnable"]) if ( connectionProfile["TlsCaCert"] and @@ -390,11 +419,35 @@ self.tlsCertsFilePicker.setText(connectionProfile["TlsCaCert"]) else: self.tlsDefaultCertsButton.setChecked(True) + + # user properties tab + self.__userProperties = copy.deepcopy( + connectionProfile.get("UserProperties", {})) + if not self.__userProperties: + self.__userProperties = { + "connect": [], + "disconnect": [], + "use_connect": True, + } + + if connectionProfile["Protocol"] == MqttProtocols.MQTTv5: + self.connectPropertiesButton.setChecked(True) + self.propertiesWidget.setProperties( + self.__userProperties["connect"]) + self.samePropertiesCheckBox.setChecked( + self.__userProperties["use_connect"]) + self.disconnectPropertiesButton.setEnabled( + not self.__userProperties["use_connect"]) + else: + self.propertiesWidget.clear() + self.__populatingProfile = False self.showPasswordButton.setChecked(False) self.profileFrame.setEnabled(True) self.__updateApplyButton() + + self.profileTabWidget.setCurrentIndex(0) def __clearProfile(self): """ @@ -422,6 +475,17 @@ self.tlsSelfSignedCertsFilePicker.setText("") self.tlsSelfSignedClientCertFilePicker.setText("") self.tlsSelfSignedClientKeyFilePicker.setText("") + + self.__userProperties = { + "connect": [], + "disconnect": [], + "use_connect": True, + } + self.propertiesWidget.clear() + self.samePropertiesCheckBox.setChecked(True) + self.connectPropertiesButton.setChecked(True) + self.disconnectPropertiesButton.setEnabled(False) + self.__populatingProfile = False self.showPasswordButton.setChecked(False) @@ -508,6 +572,24 @@ self.tlsSelfSignedClientKeyFilePicker.text() != connectionProfile["TlsClientKey"] ) + # check user properties only, if not yet changed + if not changed and protocol == MqttProtocols.MQTTv5: + properties = { + "connect": self.propertiesWidget.getProperties(), + "disconnect": self.__userProperties["disconnect"], + } if self.connectPropertiesButton.isChecked() else { + "connect": self.__userProperties["connect"], + "disconnect": self.propertiesWidget.getProperties(), + } + changed |= ( + self.samePropertiesCheckBox.isChecked() != + connectionProfile["UserProperties"]["use_connect"] or + sorted(properties["connect"]) != + sorted(connectionProfile["UserProperties"]["connect"]) or + sorted(properties["disconnect"]) != + sorted(connectionProfile["UserProperties"]["disconnect"]) + ) + return changed else: @@ -590,6 +672,20 @@ self.__updateApplyButton() @pyqtSlot(bool) + def on_mqttv5Button_toggled(self, checked): + """ + Private slot to handle the selection of the MQTT protocol. + + @param checked state of the button + @type bool + """ + self.profileTabWidget.setTabEnabled( + self.profileTabWidget.indexOf(self.propertiesTab), + checked + ) + # TODO: add code to enable the WILL properties button + + @pyqtSlot(bool) def on_showPasswordButton_toggled(self, checked): """ Private slot to show or hide the password. @@ -715,6 +811,40 @@ """ self.__updateApplyButton() + @pyqtSlot(bool) + def on_samePropertiesCheckBox_toggled(self, checked): + """ + Private slot to handle a change of the properties usage. + + @param checked flag indicating to use the same user properties for + CONNECT and DISCONNECT + @type bool + """ + if checked and not self.connectPropertiesButton.isChecked(): + self.connectPropertiesButton.click() + self.disconnectPropertiesButton.setEnabled(not checked) + + @pyqtSlot(bool) + def __propertiesTypeSelected(self, checked): + """ + Private slot to handle the switching of the user properties type. + + @param checked state of the buttons + @type bool + """ + if checked: + # handle the selection only + if self.connectPropertiesButton.isChecked(): + self.__userProperties["disconnect"] = ( + self.propertiesWidget.getProperties()) + self.propertiesWidget.setProperties( + self.__userProperties["connect"]) + else: + self.__userProperties["connect"] = ( + self.propertiesWidget.getProperties()) + self.propertiesWidget.setProperties( + self.__userProperties["disconnect"]) + @pyqtSlot() def reject(self): """