MqttMonitor/MqttMonitorWidget.py

branch
eric7
changeset 92
2fb5c08019fd
parent 86
620022b14cb4
child 97
21f9c010dc42
equal deleted inserted replaced
91:3cb08e5db764 92:2fb5c08019fd
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
213 if rc == 0: 226 if rc == 0:
214 self.__connectedToBroker = True 227 self.__connectedToBroker = True
215 self.__connectionOptions = None 228 self.__connectionOptions = None
216 229
217 self.connectButton.setIcon( 230 self.connectButton.setIcon(
218 UI.PixmapCache.getIcon("ircDisconnect.png")) 231 UI.PixmapCache.getIcon("ircDisconnect"))
219 232
220 self.subscribeGroup.setEnabled(True) 233 self.subscribeGroup.setEnabled(True)
221 self.unsubscribeGroup.setEnabled(True) 234 self.unsubscribeGroup.setEnabled(True)
222 self.publishGroup.setEnabled(True) 235 self.publishGroup.setEnabled(True)
223 self.brokerStatusButton.setEnabled(True) 236 self.brokerStatusButton.setEnabled(True)
254 if rc > 0 else 267 if rc > 0 else
255 self.tr("Connection to Broker shut down cleanly.") 268 self.tr("Connection to Broker shut down cleanly.")
256 ) 269 )
257 self.__flashBrokerStatusLabel(msg) 270 self.__flashBrokerStatusLabel(msg)
258 271
259 self.connectButton.setIcon(UI.PixmapCache.getIcon("ircConnect.png")) 272 self.connectButton.setIcon(UI.PixmapCache.getIcon("ircConnect"))
260 self.__setConnectButtonState() 273 self.__setConnectButtonState()
261 274
262 self.__subscribedTopics = [] 275 self.__subscribedTopics = []
263 self.__topicQueue = {} 276 self.__topicQueue = {}
264 self.__updateUnsubscribeTopicComboBox() 277 self.__updateUnsubscribeTopicComboBox()
288 301
289 scrollbarValue = self.logEdit.verticalScrollBar().value() 302 scrollbarValue = self.logEdit.verticalScrollBar().value()
290 303
291 textCursor = self.logEdit.textCursor() 304 textCursor = self.logEdit.textCursor()
292 if not self.logEdit.document().isEmpty(): 305 if not self.logEdit.document().isEmpty():
293 textCursor.movePosition(QTextCursor.End) 306 textCursor.movePosition(QTextCursor.MoveOperation.End)
294 self.logEdit.setTextCursor(textCursor) 307 self.logEdit.setTextCursor(textCursor)
295 self.logEdit.insertPlainText("\n") 308 self.logEdit.insertPlainText("\n")
296 309
297 textBlockFormat = textCursor.blockFormat() 310 textBlockFormat = textCursor.blockFormat()
298 try: 311 try:
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
339 Private slot to handle a message being published. 352 Private slot to handle a message being published.
340 353
341 @param mid ID of the published message 354 @param mid ID of the published message
342 @type int 355 @type int
343 """ 356 """
357 # TODO: check this 'pass' statement
344 pass 358 pass
345 359
346 @pyqtSlot(int, tuple) 360 @pyqtSlot(int, tuple)
347 def __topicSubscribed(self, mid, grantedQos): 361 def __topicSubscribed(self, mid, grantedQos):
348 """ 362 """
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
495 """ 509 """
496 topic = self.subscribeTopicEdit.text() 510 topic = self.subscribeTopicEdit.text()
497 qos = self.subscribeQosSpinBox.value() 511 qos = self.subscribeQosSpinBox.value()
498 if topic: 512 if topic:
499 if topic.startswith(MqttMonitorWidget.BrokerStatusTopicPrefix): 513 if topic.startswith(MqttMonitorWidget.BrokerStatusTopicPrefix):
500 E5MessageBox.warning( 514 EricMessageBox.warning(
501 self, 515 self,
502 self.tr("Subscribe to Topic"), 516 self.tr("Subscribe to Topic"),
503 self.tr("Subscriptions to the Status topic '$SYS' shall" 517 self.tr("Subscriptions to the Status topic '$SYS' shall"
504 " be done on the 'Status' tab.")) 518 " be done on the 'Status' tab."))
505 else: 519 else:
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)))
633 @pyqtSlot() 647 @pyqtSlot()
634 def on_saveMessagesButton_clicked(self): 648 def on_saveMessagesButton_clicked(self):
635 """ 649 """
636 Private slot to save the received messages. 650 Private slot to save the received messages.
637 """ 651 """
638 fn, selectedFilter = E5FileDialog.getSaveFileNameAndFilter( 652 fn, selectedFilter = EricFileDialog.getSaveFileNameAndFilter(
639 self, 653 self,
640 self.tr("Save Messages"), 654 self.tr("Save Messages"),
641 "", 655 "",
642 self.tr("Messages Files (*.txt);;All Files (*)"), 656 self.tr("Messages Files (*.txt);;All Files (*)"),
643 "", 657 "",
644 E5FileDialog.Options(E5FileDialog.DontConfirmOverwrite)) 658 EricFileDialog.DontConfirmOverwrite)
645 659
646 if fn: 660 if fn:
647 if fn.endswith("."): 661 if fn.endswith("."):
648 fn = fn[:-1] 662 fn = fn[:-1]
649 663
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)))
689 @pyqtSlot() 703 @pyqtSlot()
690 def on_saveLogMessagesButton_clicked(self): 704 def on_saveLogMessagesButton_clicked(self):
691 """ 705 """
692 Private slot to save the log messages. 706 Private slot to save the log messages.
693 """ 707 """
694 fn, selectedFilter = E5FileDialog.getSaveFileNameAndFilter( 708 fn, selectedFilter = EricFileDialog.getSaveFileNameAndFilter(
695 self, 709 self,
696 self.tr("Save Log Messages"), 710 self.tr("Save Log Messages"),
697 "", 711 "",
698 self.tr("Log Files (*.log);;All Files (*)"), 712 self.tr("Log Files (*.log);;All Files (*)"),
699 "", 713 "",
700 E5FileDialog.Options(E5FileDialog.DontConfirmOverwrite)) 714 EricFileDialog.DontConfirmOverwrite)
701 715
702 if fn: 716 if fn:
703 if fn.endswith("."): 717 if fn.endswith("."):
704 fn = fn[:-1] 718 fn = fn[:-1]
705 719
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

eric ide

mercurial