6 """ |
6 """ |
7 Module implementing a dialog to edit the MQTT connection profiles. |
7 Module implementing a dialog to edit the MQTT connection profiles. |
8 """ |
8 """ |
9 |
9 |
10 import collections |
10 import collections |
|
11 import copy |
11 |
12 |
12 from PyQt6.QtCore import pyqtSlot, Qt, QUuid |
13 from PyQt6.QtCore import pyqtSlot, Qt, QUuid |
13 from PyQt6.QtWidgets import ( |
14 from PyQt6.QtWidgets import ( |
14 QDialog, QDialogButtonBox, QAbstractButton, QListWidgetItem, QInputDialog, |
15 QDialog, QDialogButtonBox, QAbstractButton, QListWidgetItem, QInputDialog, |
15 QLineEdit |
16 QLineEdit |
37 @param profiles dictionary containing dictionaries containing the |
38 @param profiles dictionary containing dictionaries containing the |
38 connection parameters. Each entry must have the keys |
39 connection parameters. Each entry must have the keys |
39 "BrokerAddress", "BrokerPort", "ClientId", "Protocol", |
40 "BrokerAddress", "BrokerPort", "ClientId", "Protocol", |
40 "ConnectionTimeout", "Keepalive", "CleanSession", "Username", |
41 "ConnectionTimeout", "Keepalive", "CleanSession", "Username", |
41 "Password", "WillTopic", "WillMessage", "WillQos", "WillRetain", |
42 "Password", "WillTopic", "WillMessage", "WillQos", "WillRetain", |
42 "TlsEnable", "TlsCaCert", "TlsClientCert", "TlsClientKey". |
43 "TlsEnable", "TlsCaCert", "TlsClientCert", "TlsClientKey", |
|
44 "UserProperties". |
43 @type dict |
45 @type dict |
44 @param parent reference to the parent widget |
46 @param parent reference to the parent widget |
45 @type QWidget |
47 @type QWidget |
46 """ |
48 """ |
47 super().__init__(parent) |
49 super().__init__(parent) |
71 EricPathPickerModes.OPEN_FILE_MODE) |
73 EricPathPickerModes.OPEN_FILE_MODE) |
72 self.tlsSelfSignedClientKeyFilePicker.setFilters( |
74 self.tlsSelfSignedClientKeyFilePicker.setFilters( |
73 self.tr("Key Files (*.key *.pem);;All Files (*)")) |
75 self.tr("Key Files (*.key *.pem);;All Files (*)")) |
74 |
76 |
75 self.profileTabWidget.setCurrentIndex(0) |
77 self.profileTabWidget.setCurrentIndex(0) |
|
78 |
|
79 self.connectPropertiesButton.clicked[bool].connect( |
|
80 self.__propertiesTypeSelected) |
|
81 self.disconnectPropertiesButton.clicked[bool].connect( |
|
82 self.__propertiesTypeSelected) |
76 |
83 |
77 if len(self.__profiles) == 0: |
84 if len(self.__profiles) == 0: |
78 self.minusButton.setEnabled(False) |
85 self.minusButton.setEnabled(False) |
79 self.copyButton.setEnabled(False) |
86 self.copyButton.setEnabled(False) |
80 |
87 |
234 def getProfiles(self): |
241 def getProfiles(self): |
235 """ |
242 """ |
236 Public method to return a dictionary of profiles. |
243 Public method to return a dictionary of profiles. |
237 |
244 |
238 @return dictionary containing dictionaries containing the defined |
245 @return dictionary containing dictionaries containing the defined |
239 connection profiles. Each entry have the keys "BrokerAddress", |
246 connection profiles. Each entry has the keys "BrokerAddress", |
240 "BrokerPort", "ClientId", "Protocol", "ConnectionTimeout", |
247 "BrokerPort", "ClientId", "Protocol", "ConnectionTimeout", |
241 "Keepalive", "CleanSession", "Username", "Password", "WillTopic", |
248 "Keepalive", "CleanSession", "Username", "Password", "WillTopic", |
242 "WillMessage", "WillQos", "WillRetain", "TlsEnable", "TlsCaCert", |
249 "WillMessage", "WillQos", "WillRetain", "TlsEnable", "TlsCaCert", |
243 "TlsClientCert", "TlsClientKey". |
250 "TlsClientCert", "TlsClientKey", "UserProperties". |
244 @rtype dict |
251 @rtype dict |
245 """ |
252 """ |
246 profilesDict = {} |
253 profilesDict = {} |
247 profilesDict.update(self.__profiles) |
254 profilesDict.update(self.__profiles) |
248 return profilesDict |
255 return profilesDict |
260 protocol = MqttProtocols.MQTTv311 |
267 protocol = MqttProtocols.MQTTv311 |
261 elif self.mqttv5Button.isChecked(): |
268 elif self.mqttv5Button.isChecked(): |
262 protocol = MqttProtocols.MQTTv5 |
269 protocol = MqttProtocols.MQTTv5 |
263 else: |
270 else: |
264 protocol = MqttProtocols.MQTTv311 |
271 protocol = MqttProtocols.MQTTv311 |
|
272 |
|
273 if protocol == MqttProtocols.MQTTv5: |
|
274 if self.connectPropertiesButton.isChecked(): |
|
275 self.__userProperties["connect"] = ( |
|
276 self.propertiesWidget.getProperties()) |
|
277 else: |
|
278 self.__userProperties["disconnect"] = ( |
|
279 self.propertiesWidget.getProperties()) |
|
280 self.__userProperties["use_connect"] = ( |
|
281 self.samePropertiesCheckBox.isChecked()) |
|
282 else: |
|
283 self.__userProperties = {} |
265 |
284 |
266 profileName = self.profileEdit.text() |
285 profileName = self.profileEdit.text() |
267 connectionProfile = { |
286 connectionProfile = { |
268 "BrokerAddress": self.brokerAddressEdit.text(), |
287 "BrokerAddress": self.brokerAddressEdit.text(), |
269 "BrokerPort": self.brokerPortSpinBox.value(), |
288 "BrokerPort": self.brokerPortSpinBox.value(), |
280 "WillRetain": self.willRetainCheckBox.isChecked(), |
299 "WillRetain": self.willRetainCheckBox.isChecked(), |
281 "TlsEnable": self.tlsGroupBox.isChecked(), |
300 "TlsEnable": self.tlsGroupBox.isChecked(), |
282 "TlsCaCert": "", |
301 "TlsCaCert": "", |
283 "TlsClientCert": "", |
302 "TlsClientCert": "", |
284 "TlsClientKey": "", |
303 "TlsClientKey": "", |
|
304 "UserProperties": copy.deepcopy(self.__userProperties), |
285 } |
305 } |
286 if connectionProfile["TlsEnable"]: |
306 if connectionProfile["TlsEnable"]: |
287 if self.tlsCertsFileButton.isChecked(): |
307 if self.tlsCertsFileButton.isChecked(): |
288 connectionProfile["TlsCaCert"] = self.tlsCertsFilePicker.text() |
308 connectionProfile["TlsCaCert"] = self.tlsCertsFilePicker.text() |
289 elif self.tlsSelfSignedCertsButton.isChecked(): |
309 elif self.tlsSelfSignedCertsButton.isChecked(): |
354 if profileName is not None: |
374 if profileName is not None: |
355 self.profileEdit.setText(profileName) |
375 self.profileEdit.setText(profileName) |
356 self.brokerAddressEdit.setText(connectionProfile["BrokerAddress"]) |
376 self.brokerAddressEdit.setText(connectionProfile["BrokerAddress"]) |
357 self.brokerPortSpinBox.setValue(connectionProfile["BrokerPort"]) |
377 self.brokerPortSpinBox.setValue(connectionProfile["BrokerPort"]) |
358 self.clientIdEdit.setText(connectionProfile["ClientId"]) |
378 self.clientIdEdit.setText(connectionProfile["ClientId"]) |
|
379 |
|
380 # general tab |
359 self.mqttv31Button.setChecked( |
381 self.mqttv31Button.setChecked( |
360 connectionProfile["Protocol"] == MqttProtocols.MQTTv31) |
382 connectionProfile["Protocol"] == MqttProtocols.MQTTv31) |
361 self.mqttv311Button.setChecked( |
383 self.mqttv311Button.setChecked( |
362 connectionProfile["Protocol"] == MqttProtocols.MQTTv311) |
384 connectionProfile["Protocol"] == MqttProtocols.MQTTv311) |
363 self.mqttv5Button.setChecked( |
385 self.mqttv5Button.setChecked( |
364 connectionProfile["Protocol"] == MqttProtocols.MQTTv5) |
386 connectionProfile["Protocol"] == MqttProtocols.MQTTv5) |
|
387 self.on_mqttv5Button_toggled(self.mqttv5Button.isChecked()) |
365 self.connectionTimeoutSpinBox.setValue( |
388 self.connectionTimeoutSpinBox.setValue( |
366 connectionProfile["ConnectionTimeout"]) |
389 connectionProfile["ConnectionTimeout"]) |
367 self.keepaliveSpinBox.setValue(connectionProfile["Keepalive"]) |
390 self.keepaliveSpinBox.setValue(connectionProfile["Keepalive"]) |
368 self.cleanSessionCheckBox.setChecked(connectionProfile["CleanSession"]) |
391 self.cleanSessionCheckBox.setChecked(connectionProfile["CleanSession"]) |
|
392 |
|
393 # user credentials tab |
369 self.usernameEdit.setText(connectionProfile["Username"]) |
394 self.usernameEdit.setText(connectionProfile["Username"]) |
370 self.passwordEdit.setText( |
395 self.passwordEdit.setText( |
371 pwConvert(connectionProfile["Password"], encode=False)) |
396 pwConvert(connectionProfile["Password"], encode=False)) |
|
397 |
|
398 # will tab |
372 self.willTopicEdit.setText(connectionProfile["WillTopic"]) |
399 self.willTopicEdit.setText(connectionProfile["WillTopic"]) |
373 self.willMessageEdit.setPlainText(connectionProfile["WillMessage"]) |
400 self.willMessageEdit.setPlainText(connectionProfile["WillMessage"]) |
374 self.willQosSpinBox.setValue(connectionProfile["WillQos"]) |
401 self.willQosSpinBox.setValue(connectionProfile["WillQos"]) |
375 self.willRetainCheckBox.setChecked(connectionProfile["WillRetain"]) |
402 self.willRetainCheckBox.setChecked(connectionProfile["WillRetain"]) |
|
403 |
|
404 # SSL/TLS tab |
376 self.tlsGroupBox.setChecked(connectionProfile["TlsEnable"]) |
405 self.tlsGroupBox.setChecked(connectionProfile["TlsEnable"]) |
377 if ( |
406 if ( |
378 connectionProfile["TlsCaCert"] and |
407 connectionProfile["TlsCaCert"] and |
379 connectionProfile["TlsClientCert"] |
408 connectionProfile["TlsClientCert"] |
380 ): |
409 ): |
388 elif connectionProfile["TlsCaCert"]: |
417 elif connectionProfile["TlsCaCert"]: |
389 self.tlsCertsFileButton.setChecked(True) |
418 self.tlsCertsFileButton.setChecked(True) |
390 self.tlsCertsFilePicker.setText(connectionProfile["TlsCaCert"]) |
419 self.tlsCertsFilePicker.setText(connectionProfile["TlsCaCert"]) |
391 else: |
420 else: |
392 self.tlsDefaultCertsButton.setChecked(True) |
421 self.tlsDefaultCertsButton.setChecked(True) |
|
422 |
|
423 # user properties tab |
|
424 self.__userProperties = copy.deepcopy( |
|
425 connectionProfile.get("UserProperties", {})) |
|
426 if not self.__userProperties: |
|
427 self.__userProperties = { |
|
428 "connect": [], |
|
429 "disconnect": [], |
|
430 "use_connect": True, |
|
431 } |
|
432 |
|
433 if connectionProfile["Protocol"] == MqttProtocols.MQTTv5: |
|
434 self.connectPropertiesButton.setChecked(True) |
|
435 self.propertiesWidget.setProperties( |
|
436 self.__userProperties["connect"]) |
|
437 self.samePropertiesCheckBox.setChecked( |
|
438 self.__userProperties["use_connect"]) |
|
439 self.disconnectPropertiesButton.setEnabled( |
|
440 not self.__userProperties["use_connect"]) |
|
441 else: |
|
442 self.propertiesWidget.clear() |
|
443 |
393 self.__populatingProfile = False |
444 self.__populatingProfile = False |
394 |
445 |
395 self.showPasswordButton.setChecked(False) |
446 self.showPasswordButton.setChecked(False) |
396 self.profileFrame.setEnabled(True) |
447 self.profileFrame.setEnabled(True) |
397 self.__updateApplyButton() |
448 self.__updateApplyButton() |
|
449 |
|
450 self.profileTabWidget.setCurrentIndex(0) |
398 |
451 |
399 def __clearProfile(self): |
452 def __clearProfile(self): |
400 """ |
453 """ |
401 Private method to clear the profile data entry fields. |
454 Private method to clear the profile data entry fields. |
402 """ |
455 """ |
420 self.tlsCertsFilePicker.setText("") |
473 self.tlsCertsFilePicker.setText("") |
421 self.tlsSelfSignedCertsButton.setChecked(False) |
474 self.tlsSelfSignedCertsButton.setChecked(False) |
422 self.tlsSelfSignedCertsFilePicker.setText("") |
475 self.tlsSelfSignedCertsFilePicker.setText("") |
423 self.tlsSelfSignedClientCertFilePicker.setText("") |
476 self.tlsSelfSignedClientCertFilePicker.setText("") |
424 self.tlsSelfSignedClientKeyFilePicker.setText("") |
477 self.tlsSelfSignedClientKeyFilePicker.setText("") |
|
478 |
|
479 self.__userProperties = { |
|
480 "connect": [], |
|
481 "disconnect": [], |
|
482 "use_connect": True, |
|
483 } |
|
484 self.propertiesWidget.clear() |
|
485 self.samePropertiesCheckBox.setChecked(True) |
|
486 self.connectPropertiesButton.setChecked(True) |
|
487 self.disconnectPropertiesButton.setEnabled(False) |
|
488 |
425 self.__populatingProfile = False |
489 self.__populatingProfile = False |
426 |
490 |
427 self.showPasswordButton.setChecked(False) |
491 self.showPasswordButton.setChecked(False) |
428 self.profileFrame.setEnabled(False) |
492 self.profileFrame.setEnabled(False) |
429 self.__updateApplyButton() |
493 self.__updateApplyButton() |
506 self.tlsSelfSignedClientCertFilePicker.text() != |
570 self.tlsSelfSignedClientCertFilePicker.text() != |
507 connectionProfile["TlsClientCert"] or |
571 connectionProfile["TlsClientCert"] or |
508 self.tlsSelfSignedClientKeyFilePicker.text() != |
572 self.tlsSelfSignedClientKeyFilePicker.text() != |
509 connectionProfile["TlsClientKey"] |
573 connectionProfile["TlsClientKey"] |
510 ) |
574 ) |
|
575 # check user properties only, if not yet changed |
|
576 if not changed and protocol == MqttProtocols.MQTTv5: |
|
577 properties = { |
|
578 "connect": self.propertiesWidget.getProperties(), |
|
579 "disconnect": self.__userProperties["disconnect"], |
|
580 } if self.connectPropertiesButton.isChecked() else { |
|
581 "connect": self.__userProperties["connect"], |
|
582 "disconnect": self.propertiesWidget.getProperties(), |
|
583 } |
|
584 changed |= ( |
|
585 self.samePropertiesCheckBox.isChecked() != |
|
586 connectionProfile["UserProperties"]["use_connect"] or |
|
587 sorted(properties["connect"]) != |
|
588 sorted(connectionProfile["UserProperties"]["connect"]) or |
|
589 sorted(properties["disconnect"]) != |
|
590 sorted(connectionProfile["UserProperties"]["disconnect"]) |
|
591 ) |
|
592 |
511 return changed |
593 return changed |
512 |
594 |
513 else: |
595 else: |
514 return True |
596 return True |
515 |
597 |
586 |
668 |
587 @param checked current state of the clean session selection |
669 @param checked current state of the clean session selection |
588 @type bool |
670 @type bool |
589 """ |
671 """ |
590 self.__updateApplyButton() |
672 self.__updateApplyButton() |
|
673 |
|
674 @pyqtSlot(bool) |
|
675 def on_mqttv5Button_toggled(self, checked): |
|
676 """ |
|
677 Private slot to handle the selection of the MQTT protocol. |
|
678 |
|
679 @param checked state of the button |
|
680 @type bool |
|
681 """ |
|
682 self.profileTabWidget.setTabEnabled( |
|
683 self.profileTabWidget.indexOf(self.propertiesTab), |
|
684 checked |
|
685 ) |
|
686 # TODO: add code to enable the WILL properties button |
591 |
687 |
592 @pyqtSlot(bool) |
688 @pyqtSlot(bool) |
593 def on_showPasswordButton_toggled(self, checked): |
689 def on_showPasswordButton_toggled(self, checked): |
594 """ |
690 """ |
595 Private slot to show or hide the password. |
691 Private slot to show or hide the password. |
713 @param checked state of the selection |
809 @param checked state of the selection |
714 @type bool |
810 @type bool |
715 """ |
811 """ |
716 self.__updateApplyButton() |
812 self.__updateApplyButton() |
717 |
813 |
|
814 @pyqtSlot(bool) |
|
815 def on_samePropertiesCheckBox_toggled(self, checked): |
|
816 """ |
|
817 Private slot to handle a change of the properties usage. |
|
818 |
|
819 @param checked flag indicating to use the same user properties for |
|
820 CONNECT and DISCONNECT |
|
821 @type bool |
|
822 """ |
|
823 if checked and not self.connectPropertiesButton.isChecked(): |
|
824 self.connectPropertiesButton.click() |
|
825 self.disconnectPropertiesButton.setEnabled(not checked) |
|
826 |
|
827 @pyqtSlot(bool) |
|
828 def __propertiesTypeSelected(self, checked): |
|
829 """ |
|
830 Private slot to handle the switching of the user properties type. |
|
831 |
|
832 @param checked state of the buttons |
|
833 @type bool |
|
834 """ |
|
835 if checked: |
|
836 # handle the selection only |
|
837 if self.connectPropertiesButton.isChecked(): |
|
838 self.__userProperties["disconnect"] = ( |
|
839 self.propertiesWidget.getProperties()) |
|
840 self.propertiesWidget.setProperties( |
|
841 self.__userProperties["connect"]) |
|
842 else: |
|
843 self.__userProperties["connect"] = ( |
|
844 self.propertiesWidget.getProperties()) |
|
845 self.propertiesWidget.setProperties( |
|
846 self.__userProperties["disconnect"]) |
|
847 |
718 @pyqtSlot() |
848 @pyqtSlot() |
719 def reject(self): |
849 def reject(self): |
720 """ |
850 """ |
721 Public slot to reject the dialog changes. |
851 Public slot to reject the dialog changes. |
722 """ |
852 """ |