10 import os |
10 import os |
11 import collections |
11 import collections |
12 import copy |
12 import copy |
13 import contextlib |
13 import contextlib |
14 |
14 |
15 from PyQt5.QtCore import pyqtSlot, Qt, QTimer, QFileInfo |
15 from PyQt6.QtCore import pyqtSlot, Qt, QTimer, QFileInfo |
16 from PyQt5.QtGui import QFont, QTextCursor, QBrush |
16 from PyQt6.QtGui import QFont, QTextCursor, QBrush, QColor |
17 from PyQt5.QtWidgets import QWidget, QDialog |
17 from PyQt6.QtWidgets import QWidget, QDialog |
18 |
18 |
19 from E5Gui import E5MessageBox, E5FileDialog |
19 from EricWidgets import EricMessageBox, EricFileDialog |
20 from E5Gui.E5PathPicker import E5PathPickerModes |
20 from EricWidgets.EricPathPicker import EricPathPickerModes |
21 |
21 |
22 from .Ui_MqttMonitorWidget import Ui_MqttMonitorWidget |
22 from .Ui_MqttMonitorWidget import Ui_MqttMonitorWidget |
23 |
23 |
24 from .MqttClient import ( |
24 from .MqttClient import ( |
25 MqttClient, mqttConnackMessage, mqttErrorMessage, mqttLogLevelString |
25 MqttClient, mqttConnackMessage, mqttErrorMessage, mqttLogLevelString |
35 """ |
35 """ |
36 BrokerStatusTopicPrefix = "$SYS/broker/" |
36 BrokerStatusTopicPrefix = "$SYS/broker/" |
37 BrokerStatusTopic = "$SYS/broker/#" |
37 BrokerStatusTopic = "$SYS/broker/#" |
38 BrokerStatusTopicLoadPrefix = "$SYS/broker/load/" |
38 BrokerStatusTopicLoadPrefix = "$SYS/broker/load/" |
39 |
39 |
40 def __init__(self, plugin, iconSuffix, parent=None): |
40 def __init__(self, plugin, usesDarkPalette, parent=None): |
41 """ |
41 """ |
42 Constructor |
42 Constructor |
43 |
43 |
44 @param plugin reference to the plug-in object |
44 @param plugin reference to the plug-in object |
45 @type MqttMonitorPlugin |
45 @type MqttMonitorPlugin |
46 @param iconSuffix suffix for the icons |
46 @param usesDarkPalette flag indicating the use of a dark application |
47 @type str |
47 palette |
|
48 @type bool |
48 @param parent reference to the parent widget |
49 @param parent reference to the parent widget |
49 @type QWidget |
50 @type QWidget |
50 """ |
51 """ |
51 super().__init__(parent) |
52 super().__init__(parent) |
52 self.setupUi(self) |
53 self.setupUi(self) |
53 |
54 |
54 self.__plugin = plugin |
55 self.__plugin = plugin |
55 self.__iconSuffix = iconSuffix |
56 self.__iconSuffix = "dark" if usesDarkPalette else "light" |
56 |
57 |
57 self.__connectedToBroker = False |
58 self.__connectedToBroker = False |
58 self.__brokerStatusTopicSubscribed = False |
59 self.__brokerStatusTopicSubscribed = False |
59 |
60 |
60 self.pixmapLabel.setPixmap(UI.PixmapCache.getPixmap( |
61 self.pixmapLabel.setPixmap(UI.PixmapCache.getPixmap( |
61 os.path.join("MqttMonitor", "icons", |
62 os.path.join("MqttMonitor", "icons", |
62 "mqtt48-{0}".format(self.__iconSuffix)) |
63 "mqtt48-{0}".format(self.__iconSuffix)) |
63 )) |
64 )) |
64 |
65 |
65 self.publishPayloadFilePicker.setMode(E5PathPickerModes.OpenFileMode) |
66 self.publishPayloadFilePicker.setMode( |
|
67 EricPathPickerModes.OPEN_FILE_MODE) |
66 self.publishPayloadFilePicker.setFilters(self.tr("All Files (*)")) |
68 self.publishPayloadFilePicker.setFilters(self.tr("All Files (*)")) |
67 |
69 |
68 self.__messagesFormat = self.messagesEdit.currentCharFormat() |
70 self.__messagesFormat = self.messagesEdit.currentCharFormat() |
69 self.__messagesTopicFormat = self.messagesEdit.currentCharFormat() |
71 self.__messagesTopicFormat = self.messagesEdit.currentCharFormat() |
70 self.__messagesTopicFormat.setFontWeight(QFont.Bold) |
72 self.__messagesTopicFormat.setFontWeight(QFont.Weight.Bold) |
71 self.__messagesQosFormat = self.messagesEdit.currentCharFormat() |
73 self.__messagesQosFormat = self.messagesEdit.currentCharFormat() |
72 self.__messagesQosFormat.setFontItalic(True) |
74 self.__messagesQosFormat.setFontItalic(True) |
73 |
75 |
74 self.messagesSearchWidget.attachTextEdit(self.messagesEdit) |
76 self.messagesSearchWidget.attachTextEdit(self.messagesEdit) |
75 self.messagesSearchWidget.setWidthForHeight(False) |
77 self.messagesSearchWidget.setWidthForHeight(False) |
85 self.logLevelComboBox.addItem(mqttLogLevelString( |
87 self.logLevelComboBox.addItem(mqttLogLevelString( |
86 logLevel, isMqttLogLevel=False), logLevel) |
88 logLevel, isMqttLogLevel=False), logLevel) |
87 self.logLevelComboBox.setCurrentIndex( |
89 self.logLevelComboBox.setCurrentIndex( |
88 self.logLevelComboBox.count() - 1) |
90 self.logLevelComboBox.count() - 1) |
89 |
91 |
90 self.__logMessagesBackgrounds = { |
92 if usesDarkPalette: |
91 MqttClient.LogDebug: QBrush(Qt.white), |
93 self.__logMessagesBackgrounds = { |
92 MqttClient.LogInfo: QBrush(Qt.lightGray), |
94 MqttClient.LogDebug: QBrush(QColor("#2f2f2f")), |
93 MqttClient.LogNotice: QBrush(Qt.green), |
95 MqttClient.LogInfo: QBrush(QColor("#868686")), |
94 MqttClient.LogWarning: QBrush(Qt.yellow), |
96 MqttClient.LogNotice: QBrush(QColor("#009900")), |
95 MqttClient.LogError: QBrush(Qt.red), |
97 MqttClient.LogWarning: QBrush(QColor("#999900")), |
96 MqttClient.LogDisabled: QBrush(Qt.magenta) |
98 MqttClient.LogError: QBrush(QColor("#990000")), |
97 # reuse LogDisabled for unknown log levels |
99 MqttClient.LogDisabled: QBrush(QColor("#990099")), |
98 } |
100 # reuse LogDisabled for unknown log levels |
|
101 } |
|
102 else: |
|
103 self.__logMessagesBackgrounds = { |
|
104 MqttClient.LogDebug: QBrush(Qt.GlobalColor.white), |
|
105 MqttClient.LogInfo: QBrush(Qt.GlobalColor.lightGray), |
|
106 MqttClient.LogNotice: QBrush(Qt.GlobalColor.green), |
|
107 MqttClient.LogWarning: QBrush(Qt.GlobalColor.yellow), |
|
108 MqttClient.LogError: QBrush(Qt.GlobalColor.red), |
|
109 MqttClient.LogDisabled: QBrush(Qt.GlobalColor.magenta) |
|
110 # reuse LogDisabled for unknown log levels |
|
111 } |
99 |
112 |
100 self.logSearchWidget.attachTextEdit(self.logEdit) |
113 self.logSearchWidget.attachTextEdit(self.logEdit) |
101 self.logSearchWidget.setWidthForHeight(False) |
114 self.logSearchWidget.setWidthForHeight(False) |
102 |
115 |
103 self.brokerWidget.setCurrentIndex(0) |
116 self.brokerWidget.setCurrentIndex(0) |
104 |
117 |
105 self.__connectionModeProfile = True |
118 self.__connectionModeProfile = True |
106 self.__setConnectionMode(True) # initial mode is 'profile connection' |
119 self.__setConnectionMode(True) # initial mode is 'profile connection' |
107 self.__populateProfileComboBox() |
120 self.__populateProfileComboBox() |
108 |
121 |
109 self.connectButton.setIcon(UI.PixmapCache.getIcon("ircConnect.png")) |
122 self.connectButton.setIcon(UI.PixmapCache.getIcon("ircConnect")) |
110 self.brokerConnectionOptionsButton.setIcon(UI.PixmapCache.getIcon( |
123 self.brokerConnectionOptionsButton.setIcon(UI.PixmapCache.getIcon( |
111 os.path.join("MqttMonitor", "icons", |
124 os.path.join("MqttMonitor", "icons", |
112 "connectionOptions-{0}".format(self.__iconSuffix)) |
125 "connectionOptions-{0}".format(self.__iconSuffix)) |
113 )) |
126 )) |
114 self.__populateBrokerComboBoxes() |
127 self.__populateBrokerComboBoxes() |
115 self.brokerStatusLabel.hide() |
128 self.brokerStatusLabel.hide() |
116 |
129 |
117 self.subscribeButton.setIcon(UI.PixmapCache.getIcon("plus.png")) |
130 self.subscribeButton.setIcon(UI.PixmapCache.getIcon("plus")) |
118 self.subscribeButton.setEnabled(False) |
131 self.subscribeButton.setEnabled(False) |
119 self.unsubscribeButton.setIcon(UI.PixmapCache.getIcon("minus.png")) |
132 self.unsubscribeButton.setIcon(UI.PixmapCache.getIcon("minus")) |
120 |
133 |
121 self.__subscribedTopics = [] |
134 self.__subscribedTopics = [] |
122 self.__topicQueue = {} |
135 self.__topicQueue = {} |
123 self.__updateUnsubscribeTopicComboBox() |
136 self.__updateUnsubscribeTopicComboBox() |
124 |
137 |
300 self.__logMessagesBackgrounds[MqttClient.LogLevelMap[level]]) |
313 self.__logMessagesBackgrounds[MqttClient.LogLevelMap[level]]) |
301 except KeyError: |
314 except KeyError: |
302 textBlockFormat.setBackground( |
315 textBlockFormat.setBackground( |
303 self.__logMessagesBackgrounds[MqttClient.LogDisabled]) |
316 self.__logMessagesBackgrounds[MqttClient.LogDisabled]) |
304 textCursor.setBlockFormat(textBlockFormat) |
317 textCursor.setBlockFormat(textBlockFormat) |
305 textCursor.movePosition(QTextCursor.End) |
318 textCursor.movePosition(QTextCursor.MoveOperation.End) |
306 self.logEdit.setTextCursor(textCursor) |
319 self.logEdit.setTextCursor(textCursor) |
307 |
320 |
308 txt = self.tr("{0}: {1}").format(mqttLogLevelString(level), message) |
321 txt = self.tr("{0}: {1}").format(mqttLogLevelString(level), message) |
309 self.logEdit.insertPlainText(Utilities.filterAnsiSequences(txt)) |
322 self.logEdit.insertPlainText(Utilities.filterAnsiSequences(txt)) |
310 |
323 |
432 MqttConnectionProfilesDialog |
446 MqttConnectionProfilesDialog |
433 ) |
447 ) |
434 dlg = MqttConnectionProfilesDialog( |
448 dlg = MqttConnectionProfilesDialog( |
435 self.__client, self.__plugin.getPreferences("BrokerProfiles"), |
449 self.__client, self.__plugin.getPreferences("BrokerProfiles"), |
436 parent=self) |
450 parent=self) |
437 if dlg.exec() == QDialog.Accepted: |
451 if dlg.exec() == QDialog.DialogCode.Accepted: |
438 profilesDict = dlg.getProfiles() |
452 profilesDict = dlg.getProfiles() |
439 self.__plugin.setPreferences("BrokerProfiles", profilesDict) |
453 self.__plugin.setPreferences("BrokerProfiles", profilesDict) |
440 self.__populateProfileComboBox() |
454 self.__populateProfileComboBox() |
441 else: |
455 else: |
442 from .MqttConnectionOptionsDialog import ( |
456 from .MqttConnectionOptionsDialog import ( |
443 MqttConnectionOptionsDialog |
457 MqttConnectionOptionsDialog |
444 ) |
458 ) |
445 dlg = MqttConnectionOptionsDialog( |
459 dlg = MqttConnectionOptionsDialog( |
446 self.__client, self.__connectionOptions, parent=self) |
460 self.__client, self.__connectionOptions, parent=self) |
447 if dlg.exec() == QDialog.Accepted: |
461 if dlg.exec() == QDialog.DialogCode.Accepted: |
448 self.__connectionOptions = dlg.getConnectionOptions() |
462 self.__connectionOptions = dlg.getConnectionOptions() |
449 if self.__connectionOptions["TlsEnable"]: |
463 if self.__connectionOptions["TlsEnable"]: |
450 port = self.brokerPortComboBox.currentText().strip() |
464 port = self.brokerPortComboBox.currentText().strip() |
451 if port == "1883": |
465 if port == "1883": |
452 # it is default non-encrypted port => set to TLS port |
466 # it is default non-encrypted port => set to TLS port |
553 # payload size limit is 268,435,455 bytes |
567 # payload size limit is 268,435,455 bytes |
554 try: |
568 try: |
555 with open(payloadFile, "rb") as f: |
569 with open(payloadFile, "rb") as f: |
556 payloadStr = f.read() |
570 payloadStr = f.read() |
557 except EnvironmentError as err: |
571 except EnvironmentError as err: |
558 E5MessageBox.critical( |
572 EricMessageBox.critical( |
559 self, |
573 self, |
560 self.tr("Read Payload from File"), |
574 self.tr("Read Payload from File"), |
561 self.tr("""<p>The file <b>{0}</b> could not be read.""" |
575 self.tr("""<p>The file <b>{0}</b> could not be read.""" |
562 """ Aborting...</p><p>Reason: {1}</p>""").format( |
576 """ Aborting...</p><p>Reason: {1}</p>""").format( |
563 payloadFile, str(err))) |
577 payloadFile, str(err))) |
651 if not ext: |
665 if not ext: |
652 ex = selectedFilter.split("(*")[1].split(")")[0] |
666 ex = selectedFilter.split("(*")[1].split(")")[0] |
653 if ex: |
667 if ex: |
654 fn += ex |
668 fn += ex |
655 if QFileInfo(fn).exists(): |
669 if QFileInfo(fn).exists(): |
656 res = E5MessageBox.yesNo( |
670 res = EricMessageBox.yesNo( |
657 self, |
671 self, |
658 self.tr("Save Messages"), |
672 self.tr("Save Messages"), |
659 self.tr("<p>The file <b>{0}</b> already exists." |
673 self.tr("<p>The file <b>{0}</b> already exists." |
660 " Overwrite it?</p>").format(fn), |
674 " Overwrite it?</p>").format(fn), |
661 icon=E5MessageBox.Warning) |
675 icon=EricMessageBox.Warning) |
662 if not res: |
676 if not res: |
663 return |
677 return |
664 |
678 |
665 fn = Utilities.toNativeSeparators(fn) |
679 fn = Utilities.toNativeSeparators(fn) |
666 try: |
680 try: |
667 with open(fn, "w") as f: |
681 with open(fn, "w") as f: |
668 f.write(self.messagesEdit.toPlainText()) |
682 f.write(self.messagesEdit.toPlainText()) |
669 except EnvironmentError as err: |
683 except EnvironmentError as err: |
670 E5MessageBox.critical( |
684 EricMessageBox.critical( |
671 self, |
685 self, |
672 self.tr("Save Messages"), |
686 self.tr("Save Messages"), |
673 self.tr("""<p>The file <b>{0}</b> could not be written.""" |
687 self.tr("""<p>The file <b>{0}</b> could not be written.""" |
674 """</p><p>Reason: {1}</p>""").format( |
688 """</p><p>Reason: {1}</p>""").format( |
675 fn, str(err))) |
689 fn, str(err))) |
707 if not ext: |
721 if not ext: |
708 ex = selectedFilter.split("(*")[1].split(")")[0] |
722 ex = selectedFilter.split("(*")[1].split(")")[0] |
709 if ex: |
723 if ex: |
710 fn += ex |
724 fn += ex |
711 if QFileInfo(fn).exists(): |
725 if QFileInfo(fn).exists(): |
712 res = E5MessageBox.yesNo( |
726 res = EricMessageBox.yesNo( |
713 self, |
727 self, |
714 self.tr("Save Log Messages"), |
728 self.tr("Save Log Messages"), |
715 self.tr("<p>The file <b>{0}</b> already exists." |
729 self.tr("<p>The file <b>{0}</b> already exists." |
716 " Overwrite it?</p>").format(fn), |
730 " Overwrite it?</p>").format(fn), |
717 icon=E5MessageBox.Warning) |
731 icon=EricMessageBox.Warning) |
718 if not res: |
732 if not res: |
719 return |
733 return |
720 |
734 |
721 fn = Utilities.toNativeSeparators(fn) |
735 fn = Utilities.toNativeSeparators(fn) |
722 try: |
736 try: |
723 with open(fn, "w") as f: |
737 with open(fn, "w") as f: |
724 f.write(self.logEdit.toPlainText()) |
738 f.write(self.logEdit.toPlainText()) |
725 except EnvironmentError as err: |
739 except EnvironmentError as err: |
726 E5MessageBox.critical( |
740 EricMessageBox.critical( |
727 self, |
741 self, |
728 self.tr("Save Log Messages"), |
742 self.tr("Save Log Messages"), |
729 self.tr("""<p>The file <b>{0}</b> could not be written.""" |
743 self.tr("""<p>The file <b>{0}</b> could not be written.""" |
730 """</p><p>Reason: {1}</p>""").format( |
744 """</p><p>Reason: {1}</p>""").format( |
731 fn, str(err))) |
745 fn, str(err))) |
857 """ |
871 """ |
858 scrollbarValue = self.messagesEdit.verticalScrollBar().value() |
872 scrollbarValue = self.messagesEdit.verticalScrollBar().value() |
859 |
873 |
860 textCursor = self.messagesEdit.textCursor() |
874 textCursor = self.messagesEdit.textCursor() |
861 if not self.messagesEdit.document().isEmpty(): |
875 if not self.messagesEdit.document().isEmpty(): |
862 textCursor.movePosition(QTextCursor.End) |
876 textCursor.movePosition(QTextCursor.MoveOperation.End) |
863 self.messagesEdit.setTextCursor(textCursor) |
877 self.messagesEdit.setTextCursor(textCursor) |
864 self.messagesEdit.insertPlainText("\n") |
878 self.messagesEdit.insertPlainText("\n") |
865 |
879 |
866 textBlockFormat = textCursor.blockFormat() |
880 textBlockFormat = textCursor.blockFormat() |
867 if self.__isAlternate: |
881 if self.__isAlternate: |
869 self.messagesEdit.palette().alternateBase()) |
883 self.messagesEdit.palette().alternateBase()) |
870 else: |
884 else: |
871 textBlockFormat.setBackground( |
885 textBlockFormat.setBackground( |
872 self.messagesEdit.palette().base()) |
886 self.messagesEdit.palette().base()) |
873 textCursor.setBlockFormat(textBlockFormat) |
887 textCursor.setBlockFormat(textBlockFormat) |
874 textCursor.movePosition(QTextCursor.End) |
888 textCursor.movePosition(QTextCursor.MoveOperation.End) |
875 self.messagesEdit.setTextCursor(textCursor) |
889 self.messagesEdit.setTextCursor(textCursor) |
876 |
890 |
877 self.messagesEdit.setCurrentCharFormat(self.__messagesTopicFormat) |
891 self.messagesEdit.setCurrentCharFormat(self.__messagesTopicFormat) |
878 self.messagesEdit.insertPlainText(topic + "\n") |
892 self.messagesEdit.insertPlainText(topic + "\n") |
879 |
893 |