multiserialviewer 24.12.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- multiserialviewer/__init__.py +1 -0
- multiserialviewer/__main__.py +12 -0
- multiserialviewer/__version__.py +13 -0
- multiserialviewer/application/__init__.py +0 -0
- multiserialviewer/application/application.py +225 -0
- multiserialviewer/application/proxyStyle.py +10 -0
- multiserialviewer/application/serialViewerController.py +50 -0
- multiserialviewer/gui/__init__.py +0 -0
- multiserialviewer/gui/mainWindow.py +80 -0
- multiserialviewer/gui/serialViewerCreateDialog.py +96 -0
- multiserialviewer/gui/serialViewerWindow.py +67 -0
- multiserialviewer/gui/textHighlighterSettingsDialog.py +46 -0
- multiserialviewer/serial_data/__init__.py +0 -0
- multiserialviewer/serial_data/serialConnectionSettings.py +11 -0
- multiserialviewer/serial_data/serialDataProcessor.py +51 -0
- multiserialviewer/serial_data/serialDataReceiver.py +63 -0
- multiserialviewer/text_highlighter/__init__.py +0 -0
- multiserialviewer/text_highlighter/colorSelectorItemDelegate.py +41 -0
- multiserialviewer/text_highlighter/textHighlighter.py +32 -0
- multiserialviewer/text_highlighter/textHighlighterConfig.py +19 -0
- multiserialviewer/text_highlighter/textHighlighterTableModel.py +120 -0
- multiserialviewer/ui_files/__init__.py +0 -0
- multiserialviewer/ui_files/createSerialViewerDialog.ui +209 -0
- multiserialviewer/ui_files/mainWindow.ui +82 -0
- multiserialviewer/ui_files/serialViewerWindow.ui +72 -0
- multiserialviewer/ui_files/textHighlighterSettingsDialog.ui +93 -0
- multiserialviewer/ui_files/uiFileHelper.py +17 -0
- multiserialviewer-24.12.0.dist-info/LICENSE +674 -0
- multiserialviewer-24.12.0.dist-info/METADATA +23 -0
- multiserialviewer-24.12.0.dist-info/RECORD +33 -0
- multiserialviewer-24.12.0.dist-info/WHEEL +5 -0
- multiserialviewer-24.12.0.dist-info/entry_points.txt +2 -0
- multiserialviewer-24.12.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .__version__ import __version__
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from multiserialviewer import __version__
|
|
3
|
+
from multiserialviewer.application.application import Application
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def main() -> int | str:
|
|
7
|
+
app = Application(__version__, sys.argv)
|
|
8
|
+
return app.exec()
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
if __name__ == '__main__':
|
|
12
|
+
sys.exit(main())
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Version scheme:
|
|
2
|
+
# YEAR.MONTH.COUNTER
|
|
3
|
+
# YEAR, MONTH are two digits and zero-padded
|
|
4
|
+
# COUNTER resets every month and starts with 0
|
|
5
|
+
# For example:
|
|
6
|
+
# - 23.09.0
|
|
7
|
+
# - 23.09.1
|
|
8
|
+
# - 23.10.0
|
|
9
|
+
#
|
|
10
|
+
# Development versions contain the DEV-COUNTER postfix:
|
|
11
|
+
# - 24.01.0.DEV-1
|
|
12
|
+
|
|
13
|
+
__version__ = '24.12.0'
|
|
File without changes
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
from PySide6.QtWidgets import QApplication
|
|
2
|
+
from PySide6.QtCore import QSettings, QSize, Slot
|
|
3
|
+
from typing import List
|
|
4
|
+
from platformdirs import user_config_dir
|
|
5
|
+
import pathlib
|
|
6
|
+
import copy
|
|
7
|
+
|
|
8
|
+
from multiserialviewer.gui.mainWindow import MainWindow
|
|
9
|
+
from multiserialviewer.serial_data.serialDataReceiver import SerialDataReceiver
|
|
10
|
+
from multiserialviewer.serial_data.serialDataProcessor import SerialDataProcessor
|
|
11
|
+
from multiserialviewer.serial_data.serialConnectionSettings import SerialConnectionSettings
|
|
12
|
+
from multiserialviewer.text_highlighter.textHighlighterConfig import TextHighlighterConfig
|
|
13
|
+
from multiserialviewer.application.serialViewerController import SerialViewerController
|
|
14
|
+
from multiserialviewer.application.proxyStyle import ProxyStyle
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Application(QApplication):
|
|
20
|
+
NAME = 'MultiSerialViewer'
|
|
21
|
+
|
|
22
|
+
def __init__(self, version: str, arguments):
|
|
23
|
+
super().__init__(arguments)
|
|
24
|
+
|
|
25
|
+
self.config_dir = user_config_dir(appname=Application.NAME, roaming=False, ensure_exists=True, appauthor=False)
|
|
26
|
+
self.main_config_file_path = str(pathlib.PurePath(self.config_dir, 'multiserialviewer.ini'))
|
|
27
|
+
self.highlighter_config_file_path = str(pathlib.PurePath(self.config_dir, 'highlighter.ini'))
|
|
28
|
+
|
|
29
|
+
self.controller = {}
|
|
30
|
+
self.highlighterSettings: List[TextHighlighterConfig] = []
|
|
31
|
+
|
|
32
|
+
self.mainWindow = MainWindow(f'{Application.NAME} {version}')
|
|
33
|
+
self.mainWindow.signal_showSerialViewerCreateDialog.connect(self.showSerialViewerCreateDialog)
|
|
34
|
+
self.mainWindow.signal_createSerialViewer.connect(self.createSerialViewer)
|
|
35
|
+
self.mainWindow.signal_clearAll.connect(self.clearAll)
|
|
36
|
+
self.mainWindow.signal_connectionStateChanged.connect(self.changeConnectionState)
|
|
37
|
+
self.mainWindow.signal_aboutToBeClosed.connect(self.saveSettings)
|
|
38
|
+
self.mainWindow.signal_aboutToBeClosed.connect(self.saveSerialViewerSettings)
|
|
39
|
+
self.mainWindow.signal_aboutToBeClosed.connect(self.saveHighlighterSettings)
|
|
40
|
+
self.mainWindow.signal_aboutToBeClosed.connect(self.stopAllSerialViewer)
|
|
41
|
+
self.mainWindow.signal_editHighlighterSettings.connect(self.showHighlighterSettingsDialog)
|
|
42
|
+
self.mainWindow.signal_applyHighlighterSettings.connect(self.setHighlighterSettings)
|
|
43
|
+
|
|
44
|
+
self.loadSettings()
|
|
45
|
+
self.loadHighlighterSettings()
|
|
46
|
+
self.loadSerialViewerSettings()
|
|
47
|
+
|
|
48
|
+
self.setStyle(ProxyStyle())
|
|
49
|
+
self.mainWindow.show()
|
|
50
|
+
|
|
51
|
+
def initDefaultHighlighterSettings(self):
|
|
52
|
+
self.highlighterSettings = []
|
|
53
|
+
|
|
54
|
+
cfg = TextHighlighterConfig()
|
|
55
|
+
cfg.pattern = r'\[MSG: .* :MSG\]'
|
|
56
|
+
cfg.color_foreground = 'darkgreen'
|
|
57
|
+
cfg.color_background = 'transparent'
|
|
58
|
+
cfg.italic = False
|
|
59
|
+
cfg.bold = True
|
|
60
|
+
cfg.font_size = QApplication.font().pointSize()
|
|
61
|
+
self.highlighterSettings.append(cfg)
|
|
62
|
+
|
|
63
|
+
cfg = TextHighlighterConfig()
|
|
64
|
+
cfg.pattern = r'\[ERR: .* :ERR\]'
|
|
65
|
+
cfg.color_foreground = 'darkred'
|
|
66
|
+
cfg.color_background = 'transparent'
|
|
67
|
+
cfg.italic = False
|
|
68
|
+
cfg.bold = True
|
|
69
|
+
cfg.font_size = QApplication.font().pointSize()
|
|
70
|
+
self.highlighterSettings.append(cfg)
|
|
71
|
+
|
|
72
|
+
@Slot(object)
|
|
73
|
+
def setHighlighterSettings(self, settings: List[TextHighlighterConfig]):
|
|
74
|
+
self.highlighterSettings = settings
|
|
75
|
+
for ctrl in self.controller.values():
|
|
76
|
+
ctrl.view.setHighlighterSettings(self.highlighterSettings)
|
|
77
|
+
|
|
78
|
+
@Slot()
|
|
79
|
+
def showSerialViewerCreateDialog(self):
|
|
80
|
+
already_used_ports = list(self.controller.keys())
|
|
81
|
+
self.mainWindow.showSerialViewerCreateDialog(already_used_ports)
|
|
82
|
+
|
|
83
|
+
@Slot(str, SerialConnectionSettings)
|
|
84
|
+
def createSerialViewer(self, window_title: str, settings: SerialConnectionSettings, size: QSize = None):
|
|
85
|
+
if settings.portName in self.controller:
|
|
86
|
+
raise Exception(f"{settings.portName} exists already")
|
|
87
|
+
|
|
88
|
+
receiver = SerialDataReceiver(settings)
|
|
89
|
+
processor = SerialDataProcessor(receiver.rxQueue)
|
|
90
|
+
view = self.mainWindow.createSerialViewerWindow(window_title, size)
|
|
91
|
+
view.setHighlighterSettings(self.highlighterSettings)
|
|
92
|
+
ctrl = SerialViewerController(receiver, processor, view)
|
|
93
|
+
|
|
94
|
+
ctrl.terminated.connect(self.deleteSerialViewer)
|
|
95
|
+
self.controller[settings.portName] = ctrl
|
|
96
|
+
|
|
97
|
+
if self.mainWindow.getConnectionState():
|
|
98
|
+
ctrl.start()
|
|
99
|
+
|
|
100
|
+
@Slot()
|
|
101
|
+
def deleteSerialViewer(self, portName):
|
|
102
|
+
if portName in self.controller:
|
|
103
|
+
del self.controller[portName]
|
|
104
|
+
else:
|
|
105
|
+
raise Exception("Controller to remove does not exist in list")
|
|
106
|
+
|
|
107
|
+
@Slot()
|
|
108
|
+
def clearAll(self):
|
|
109
|
+
for ctrl in self.controller.values():
|
|
110
|
+
ctrl.view.clear()
|
|
111
|
+
|
|
112
|
+
@Slot(bool)
|
|
113
|
+
def changeConnectionState(self, state):
|
|
114
|
+
if len(self.controller.values()) > 0:
|
|
115
|
+
if state:
|
|
116
|
+
failed_to_connect = False
|
|
117
|
+
|
|
118
|
+
# try to connect all ports
|
|
119
|
+
for ctrl in self.controller.values():
|
|
120
|
+
if not ctrl.start():
|
|
121
|
+
failed_to_connect = True
|
|
122
|
+
|
|
123
|
+
if failed_to_connect:
|
|
124
|
+
# cleanup if connect failed
|
|
125
|
+
for ctrl in self.controller.values():
|
|
126
|
+
ctrl.stop()
|
|
127
|
+
self.mainWindow.setConnectionState(False)
|
|
128
|
+
else:
|
|
129
|
+
self.mainWindow.setConnectionState(True)
|
|
130
|
+
else:
|
|
131
|
+
for ctrl in self.controller.values():
|
|
132
|
+
ctrl.stop()
|
|
133
|
+
self.mainWindow.setConnectionState(False)
|
|
134
|
+
|
|
135
|
+
def loadSettings(self):
|
|
136
|
+
settings = QSettings(self.main_config_file_path, QSettings.Format.IniFormat)
|
|
137
|
+
|
|
138
|
+
settings.beginGroup("MainWindow")
|
|
139
|
+
self.mainWindow.resize(settings.value("size", QSize(800, 800)))
|
|
140
|
+
settings.endGroup()
|
|
141
|
+
|
|
142
|
+
def saveSettings(self):
|
|
143
|
+
settings = QSettings(self.main_config_file_path, QSettings.Format.IniFormat)
|
|
144
|
+
|
|
145
|
+
settings.beginGroup("MainWindow")
|
|
146
|
+
settings.setValue("size", self.mainWindow.size())
|
|
147
|
+
settings.endGroup()
|
|
148
|
+
|
|
149
|
+
def loadSerialViewerSettings(self):
|
|
150
|
+
settings = QSettings(self.main_config_file_path, QSettings.Format.IniFormat)
|
|
151
|
+
|
|
152
|
+
number_of_connections = settings.beginReadArray("connections")
|
|
153
|
+
for i in range(number_of_connections):
|
|
154
|
+
settings.setArrayIndex(i)
|
|
155
|
+
|
|
156
|
+
# check if all needed keys exist
|
|
157
|
+
if all(elem in settings.allKeys() for elem in ['serialViewer', 'view/size', 'view/title']):
|
|
158
|
+
self.createSerialViewer(settings.value("view/title"), settings.value("serialViewer"),
|
|
159
|
+
settings.value("view/size"))
|
|
160
|
+
settings.endArray()
|
|
161
|
+
|
|
162
|
+
@Slot()
|
|
163
|
+
def saveSerialViewerSettings(self):
|
|
164
|
+
settings = QSettings(self.main_config_file_path, QSettings.Format.IniFormat)
|
|
165
|
+
|
|
166
|
+
settings.beginWriteArray("connections")
|
|
167
|
+
settings.remove("") # remove all existing connections
|
|
168
|
+
|
|
169
|
+
for i, ctrl in enumerate(self.controller.values()):
|
|
170
|
+
settings.setArrayIndex(i)
|
|
171
|
+
settings.setValue("serialViewer", ctrl.receiver.settings)
|
|
172
|
+
settings.setValue("view/title", ctrl.view.windowTitle())
|
|
173
|
+
settings.setValue("view/size", ctrl.view.size())
|
|
174
|
+
|
|
175
|
+
settings.endArray()
|
|
176
|
+
|
|
177
|
+
def loadHighlighterSettings(self):
|
|
178
|
+
settings = QSettings(self.highlighter_config_file_path, QSettings.Format.IniFormat)
|
|
179
|
+
|
|
180
|
+
number_of_settings = settings.beginReadArray("settings")
|
|
181
|
+
if number_of_settings > 0:
|
|
182
|
+
self.highlighterSettings = []
|
|
183
|
+
|
|
184
|
+
for i in range(number_of_settings):
|
|
185
|
+
settings.setArrayIndex(i)
|
|
186
|
+
|
|
187
|
+
# check if all needed keys exist
|
|
188
|
+
if all(elem in settings.allKeys() for elem in ['pattern', 'color_foreground', 'color_background', 'italic', 'bold']):
|
|
189
|
+
cfg = TextHighlighterConfig()
|
|
190
|
+
cfg.pattern = settings.value("pattern")
|
|
191
|
+
cfg.color_foreground = settings.value("color_foreground")
|
|
192
|
+
cfg.color_background = settings.value("color_background")
|
|
193
|
+
cfg.italic = settings.value("italic", type=bool)
|
|
194
|
+
cfg.bold = settings.value("bold", type=bool)
|
|
195
|
+
cfg.font_size = settings.value("font_size", type=int)
|
|
196
|
+
self.highlighterSettings.append(cfg)
|
|
197
|
+
settings.endArray()
|
|
198
|
+
else:
|
|
199
|
+
self.initDefaultHighlighterSettings()
|
|
200
|
+
|
|
201
|
+
@Slot()
|
|
202
|
+
def saveHighlighterSettings(self):
|
|
203
|
+
settings = QSettings(self.highlighter_config_file_path, QSettings.Format.IniFormat)
|
|
204
|
+
|
|
205
|
+
settings.beginWriteArray("settings")
|
|
206
|
+
settings.remove("") # remove all existing settings
|
|
207
|
+
|
|
208
|
+
for i, cfg in enumerate(self.highlighterSettings):
|
|
209
|
+
settings.setArrayIndex(i)
|
|
210
|
+
settings.setValue("pattern", cfg.pattern)
|
|
211
|
+
settings.setValue("color_foreground", cfg.color_foreground)
|
|
212
|
+
settings.setValue("color_background", cfg.color_background)
|
|
213
|
+
settings.setValue("italic", cfg.italic)
|
|
214
|
+
settings.setValue("bold", cfg.bold)
|
|
215
|
+
settings.setValue("font_size", cfg.font_size)
|
|
216
|
+
settings.endArray()
|
|
217
|
+
|
|
218
|
+
@Slot()
|
|
219
|
+
def stopAllSerialViewer(self):
|
|
220
|
+
for ctrl in self.controller.values():
|
|
221
|
+
ctrl.stop()
|
|
222
|
+
|
|
223
|
+
@Slot()
|
|
224
|
+
def showHighlighterSettingsDialog(self):
|
|
225
|
+
self.mainWindow.showHighlighterSettingsDialog(copy.deepcopy(self.highlighterSettings))
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from PySide6.QtWidgets import QProxyStyle, QStyle
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ProxyStyle(QProxyStyle):
|
|
5
|
+
def subElementRect(self, element, opt, widget=None):
|
|
6
|
+
if element == QStyle.SE_ItemViewItemCheckIndicator and not opt.text:
|
|
7
|
+
rect = super().subElementRect(element, opt, widget)
|
|
8
|
+
rect.moveCenter(opt.rect.center())
|
|
9
|
+
return rect
|
|
10
|
+
return super().subElementRect(element, opt, widget)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from PySide6.QtCore import QObject, Slot, Signal
|
|
2
|
+
|
|
3
|
+
from multiserialviewer.gui.serialViewerWindow import SerialViewerWindow
|
|
4
|
+
from multiserialviewer.serial_data.serialDataReceiver import SerialDataReceiver
|
|
5
|
+
from multiserialviewer.serial_data.serialDataProcessor import SerialDataProcessor
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SerialViewerController(QObject):
|
|
9
|
+
terminated = Signal(str)
|
|
10
|
+
|
|
11
|
+
def __init__(self, receiver: SerialDataReceiver, processor: SerialDataProcessor, view: SerialViewerWindow):
|
|
12
|
+
super().__init__()
|
|
13
|
+
|
|
14
|
+
self.receiver = receiver
|
|
15
|
+
self.processor = processor
|
|
16
|
+
self.view = view
|
|
17
|
+
|
|
18
|
+
self.view.closed.connect(self.terminate)
|
|
19
|
+
self.processor.dataAvailable.connect(self.view.appendData)
|
|
20
|
+
|
|
21
|
+
def start(self) -> bool:
|
|
22
|
+
if self.receiver.open_port():
|
|
23
|
+
self.processor.start()
|
|
24
|
+
self.receiver.start()
|
|
25
|
+
self.show_message(f'Opened {self.receiver.settings.portName}')
|
|
26
|
+
return True
|
|
27
|
+
else:
|
|
28
|
+
self.show_error(f'Failed to open {self.receiver.settings.portName}')
|
|
29
|
+
return False
|
|
30
|
+
|
|
31
|
+
def stop(self):
|
|
32
|
+
if self.receiver.isReceiving():
|
|
33
|
+
self.receiver.stop()
|
|
34
|
+
self.receiver.close_port()
|
|
35
|
+
self.processor.stop()
|
|
36
|
+
self.show_message(f'Closed {self.receiver.settings.portName}')
|
|
37
|
+
|
|
38
|
+
def show_message(self, text):
|
|
39
|
+
self.view.appendData(f'\n[MSG: {text} :MSG]\n', True)
|
|
40
|
+
|
|
41
|
+
def show_error(self, text):
|
|
42
|
+
self.view.appendData(f'\n[ERR: {text} :ERR]\n', True)
|
|
43
|
+
|
|
44
|
+
@Slot()
|
|
45
|
+
def terminate(self):
|
|
46
|
+
# view is already closed
|
|
47
|
+
self.receiver.stop()
|
|
48
|
+
self.receiver.close_port()
|
|
49
|
+
self.processor.stop()
|
|
50
|
+
self.terminated.emit(self.receiver.settings.portName)
|
|
File without changes
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
from PySide6.QtWidgets import QMdiArea, QMainWindow, QPushButton
|
|
2
|
+
from PySide6.QtCore import QSize, Signal
|
|
3
|
+
|
|
4
|
+
from typing import List
|
|
5
|
+
|
|
6
|
+
from multiserialviewer.ui_files.uiFileHelper import createWidgetFromUiFile
|
|
7
|
+
from multiserialviewer.serial_data.serialConnectionSettings import SerialConnectionSettings
|
|
8
|
+
from multiserialviewer.gui.serialViewerWindow import SerialViewerWindow
|
|
9
|
+
from multiserialviewer.gui.serialViewerCreateDialog import SerialViewerCreateDialog
|
|
10
|
+
from multiserialviewer.gui.textHighlighterSettingsDialog import TextHighlighterSettingsDialog
|
|
11
|
+
from multiserialviewer.text_highlighter.textHighlighter import TextHighlighterConfig
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class MainWindow(QMainWindow):
|
|
15
|
+
signal_showSerialViewerCreateDialog = Signal()
|
|
16
|
+
signal_createSerialViewer = Signal(str, SerialConnectionSettings)
|
|
17
|
+
signal_clearAll = Signal()
|
|
18
|
+
signal_connectionStateChanged = Signal(bool)
|
|
19
|
+
signal_aboutToBeClosed = Signal()
|
|
20
|
+
signal_editHighlighterSettings = Signal()
|
|
21
|
+
signal_applyHighlighterSettings = Signal(object)
|
|
22
|
+
|
|
23
|
+
def __init__(self, title: str):
|
|
24
|
+
super(MainWindow, self).__init__()
|
|
25
|
+
self.setWindowTitle(title)
|
|
26
|
+
|
|
27
|
+
widget = createWidgetFromUiFile("mainWindow.ui")
|
|
28
|
+
|
|
29
|
+
self.mdiArea = widget.findChild(QMdiArea, 'mdiArea')
|
|
30
|
+
self.pb_changeConnectionState: QPushButton = widget.findChild(QPushButton, 'pb_changeConnectionState')
|
|
31
|
+
|
|
32
|
+
self.setCentralWidget(widget)
|
|
33
|
+
self.setConnectionState(False)
|
|
34
|
+
|
|
35
|
+
# connections
|
|
36
|
+
widget.pb_create.clicked.connect(self.signal_showSerialViewerCreateDialog)
|
|
37
|
+
widget.pb_clear.clicked.connect(self.signal_clearAll)
|
|
38
|
+
widget.pb_changeConnectionState.clicked.connect(self.signal_connectionStateChanged)
|
|
39
|
+
widget.pb_highlighter.clicked.connect(self.signal_editHighlighterSettings)
|
|
40
|
+
|
|
41
|
+
def showSerialViewerCreateDialog(self, disabled_ports: list):
|
|
42
|
+
dialog = SerialViewerCreateDialog(self)
|
|
43
|
+
dialog.disablePorts(disabled_ports)
|
|
44
|
+
if dialog.exec():
|
|
45
|
+
port_name = dialog.getPortName()
|
|
46
|
+
if len(port_name) > 0:
|
|
47
|
+
settings = SerialConnectionSettings(port_name)
|
|
48
|
+
settings.baudrate = dialog.getBaudrate()
|
|
49
|
+
settings.bytesize = dialog.getDataBits()
|
|
50
|
+
settings.parity = dialog.getParity()
|
|
51
|
+
settings.stopbits = dialog.getStopBits()
|
|
52
|
+
|
|
53
|
+
self.signal_createSerialViewer.emit(dialog.getName(), settings)
|
|
54
|
+
|
|
55
|
+
def createSerialViewerWindow(self, viewTitle: str, size: QSize = None):
|
|
56
|
+
view = SerialViewerWindow(viewTitle)
|
|
57
|
+
if size:
|
|
58
|
+
view.resize(size)
|
|
59
|
+
self.mdiArea.addSubWindow(view)
|
|
60
|
+
view.show()
|
|
61
|
+
return view
|
|
62
|
+
|
|
63
|
+
def showHighlighterSettingsDialog(self, settings: List[TextHighlighterConfig]):
|
|
64
|
+
dialog = TextHighlighterSettingsDialog(self, settings)
|
|
65
|
+
if dialog.exec():
|
|
66
|
+
self.signal_applyHighlighterSettings.emit(dialog.table_model.settings)
|
|
67
|
+
|
|
68
|
+
def getConnectionState(self):
|
|
69
|
+
return self.pb_changeConnectionState.isChecked()
|
|
70
|
+
|
|
71
|
+
def setConnectionState(self, state):
|
|
72
|
+
self.pb_changeConnectionState.setChecked(state)
|
|
73
|
+
if state:
|
|
74
|
+
self.pb_changeConnectionState.setText('Stop')
|
|
75
|
+
else:
|
|
76
|
+
self.pb_changeConnectionState.setText('Start')
|
|
77
|
+
|
|
78
|
+
def closeEvent(self, event):
|
|
79
|
+
self.signal_aboutToBeClosed.emit()
|
|
80
|
+
event.accept()
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
from PySide6.QtWidgets import QDialog, QVBoxLayout, QDialogButtonBox
|
|
2
|
+
from PySide6.QtCore import Slot
|
|
3
|
+
import serial.tools.list_ports
|
|
4
|
+
import serial
|
|
5
|
+
|
|
6
|
+
from multiserialviewer.ui_files.uiFileHelper import createWidgetFromUiFile
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SerialViewerCreateDialog(QDialog):
|
|
10
|
+
def __init__(self, parent):
|
|
11
|
+
super().__init__(parent)
|
|
12
|
+
|
|
13
|
+
self.setWindowTitle("Create SerialViewer")
|
|
14
|
+
self.disabled_port_names = []
|
|
15
|
+
|
|
16
|
+
self.connectWidget = createWidgetFromUiFile("createSerialViewerDialog.ui")
|
|
17
|
+
|
|
18
|
+
self.refreshListOfSerialPorts()
|
|
19
|
+
self.populateBaudRateCombobox()
|
|
20
|
+
self.populateDataBitsCombobox()
|
|
21
|
+
self.populateParityCombobox()
|
|
22
|
+
self.populateStopBitsCombobox()
|
|
23
|
+
|
|
24
|
+
self.connectWidget.pb_refresh.clicked.connect(self.refreshListOfSerialPorts)
|
|
25
|
+
self.connectWidget.buttonBox.accepted.connect(self.accept)
|
|
26
|
+
self.connectWidget.buttonBox.rejected.connect(self.reject)
|
|
27
|
+
|
|
28
|
+
QVBoxLayout(self).addWidget(self.connectWidget)
|
|
29
|
+
|
|
30
|
+
def disablePorts(self, disabled_port_names: list):
|
|
31
|
+
self.disabled_port_names = disabled_port_names
|
|
32
|
+
self.refreshListOfSerialPorts()
|
|
33
|
+
|
|
34
|
+
@Slot()
|
|
35
|
+
def refreshListOfSerialPorts(self):
|
|
36
|
+
port_name_list = [p.device for p in serial.tools.list_ports.comports() if
|
|
37
|
+
p.device not in self.disabled_port_names]
|
|
38
|
+
self.connectWidget.cb_portName.clear()
|
|
39
|
+
self.connectWidget.cb_portName.addItems(port_name_list)
|
|
40
|
+
|
|
41
|
+
ok_button_enabled_state = len(port_name_list) > 0
|
|
42
|
+
self.connectWidget.buttonBox.button(QDialogButtonBox.Ok).setEnabled(ok_button_enabled_state)
|
|
43
|
+
|
|
44
|
+
def populateBaudRateCombobox(self):
|
|
45
|
+
baudrates = ['9600', '38400', '115200', '256000', '1000000']
|
|
46
|
+
self.connectWidget.cb_baudrate.clear()
|
|
47
|
+
self.connectWidget.cb_baudrate.addItems(baudrates)
|
|
48
|
+
self.connectWidget.cb_baudrate.setCurrentIndex(4)
|
|
49
|
+
|
|
50
|
+
def populateDataBitsCombobox(self):
|
|
51
|
+
self.connectWidget.cb_dataSize.clear()
|
|
52
|
+
self.connectWidget.cb_dataSize.addItem(str(serial.FIVEBITS), userData=serial.FIVEBITS)
|
|
53
|
+
self.connectWidget.cb_dataSize.addItem(str(serial.SIXBITS), userData=serial.SIXBITS)
|
|
54
|
+
self.connectWidget.cb_dataSize.addItem(str(serial.SEVENBITS), userData=serial.SEVENBITS)
|
|
55
|
+
self.connectWidget.cb_dataSize.addItem(str(serial.EIGHTBITS), userData=serial.EIGHTBITS)
|
|
56
|
+
self.connectWidget.cb_dataSize.setCurrentIndex(3)
|
|
57
|
+
|
|
58
|
+
def populateParityCombobox(self):
|
|
59
|
+
self.connectWidget.cb_parity.clear()
|
|
60
|
+
self.connectWidget.cb_parity.addItem(serial.PARITY_NAMES[serial.PARITY_NONE], userData=serial.PARITY_NONE)
|
|
61
|
+
self.connectWidget.cb_parity.addItem(serial.PARITY_NAMES[serial.PARITY_EVEN], userData=serial.PARITY_EVEN)
|
|
62
|
+
self.connectWidget.cb_parity.addItem(serial.PARITY_NAMES[serial.PARITY_ODD], userData=serial.PARITY_ODD)
|
|
63
|
+
self.connectWidget.cb_parity.addItem(serial.PARITY_NAMES[serial.PARITY_MARK], userData=serial.PARITY_MARK)
|
|
64
|
+
self.connectWidget.cb_parity.addItem(serial.PARITY_NAMES[serial.PARITY_SPACE], userData=serial.PARITY_SPACE)
|
|
65
|
+
self.connectWidget.cb_parity.setCurrentIndex(0)
|
|
66
|
+
|
|
67
|
+
def populateStopBitsCombobox(self):
|
|
68
|
+
self.connectWidget.cb_stopBits.clear()
|
|
69
|
+
self.connectWidget.cb_stopBits.addItem(str(serial.STOPBITS_ONE), userData=serial.STOPBITS_ONE)
|
|
70
|
+
self.connectWidget.cb_stopBits.addItem(str(serial.STOPBITS_ONE_POINT_FIVE),
|
|
71
|
+
userData=serial.STOPBITS_ONE_POINT_FIVE)
|
|
72
|
+
self.connectWidget.cb_stopBits.addItem(str(serial.STOPBITS_TWO), userData=serial.STOPBITS_TWO)
|
|
73
|
+
self.connectWidget.cb_stopBits.setCurrentIndex(0)
|
|
74
|
+
|
|
75
|
+
def getName(self):
|
|
76
|
+
name = self.connectWidget.ed_name.text()
|
|
77
|
+
if name == '':
|
|
78
|
+
name = self.getPortName()
|
|
79
|
+
elif self.getPortName() not in name:
|
|
80
|
+
name = '{} ({})'.format(name, self.getPortName())
|
|
81
|
+
return name
|
|
82
|
+
|
|
83
|
+
def getPortName(self):
|
|
84
|
+
return self.connectWidget.cb_portName.currentText()
|
|
85
|
+
|
|
86
|
+
def getBaudrate(self):
|
|
87
|
+
return self.connectWidget.cb_baudrate.currentText()
|
|
88
|
+
|
|
89
|
+
def getDataBits(self):
|
|
90
|
+
return self.connectWidget.cb_dataSize.currentData()
|
|
91
|
+
|
|
92
|
+
def getParity(self):
|
|
93
|
+
return self.connectWidget.cb_parity.currentData()
|
|
94
|
+
|
|
95
|
+
def getStopBits(self):
|
|
96
|
+
return self.connectWidget.cb_stopBits.currentData()
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
from PySide6.QtCore import Qt, Slot, Signal
|
|
2
|
+
from PySide6.QtGui import QTextCursor, QClipboard
|
|
3
|
+
from PySide6.QtWidgets import QApplication, QMdiSubWindow, QTextEdit, QPushButton, QCheckBox
|
|
4
|
+
from typing import List
|
|
5
|
+
from multiserialviewer.text_highlighter.textHighlighter import TextHighlighter, TextHighlighterConfig
|
|
6
|
+
from multiserialviewer.ui_files.uiFileHelper import createWidgetFromUiFile
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SerialViewerWindow(QMdiSubWindow):
|
|
10
|
+
closed = Signal()
|
|
11
|
+
|
|
12
|
+
def __init__(self, window_title):
|
|
13
|
+
super().__init__()
|
|
14
|
+
|
|
15
|
+
widget = createWidgetFromUiFile("serialViewerWindow.ui")
|
|
16
|
+
|
|
17
|
+
self.setWidget(widget)
|
|
18
|
+
self.setWindowTitle(window_title)
|
|
19
|
+
self.setAttribute(Qt.WA_DeleteOnClose)
|
|
20
|
+
|
|
21
|
+
self.textEdit: QTextEdit = widget.findChild(QTextEdit, 'textEdit')
|
|
22
|
+
|
|
23
|
+
self.highlighter = TextHighlighter()
|
|
24
|
+
self.highlighter.setDocument(self.textEdit.document())
|
|
25
|
+
|
|
26
|
+
pb_clear: QPushButton = widget.findChild(QPushButton, 'pb_clear')
|
|
27
|
+
pb_clear.pressed.connect(self.clear)
|
|
28
|
+
|
|
29
|
+
pb_copy: QPushButton = widget.findChild(QPushButton, 'pb_copy')
|
|
30
|
+
pb_copy.pressed.connect(self.copy)
|
|
31
|
+
|
|
32
|
+
self.checkBox_enabled: QCheckBox = self.widget().findChild(QCheckBox, 'checkBox_enabled')
|
|
33
|
+
|
|
34
|
+
def closeEvent(self, event):
|
|
35
|
+
# is not called when mainwindow is closed
|
|
36
|
+
event.accept()
|
|
37
|
+
self.closed.emit()
|
|
38
|
+
|
|
39
|
+
@Slot()
|
|
40
|
+
def clear(self):
|
|
41
|
+
self.textEdit.clear()
|
|
42
|
+
|
|
43
|
+
def setHighlighterSettings(self, settings: List[TextHighlighterConfig]):
|
|
44
|
+
self.highlighter.setSettings(settings)
|
|
45
|
+
self.highlighter.rehighlight()
|
|
46
|
+
|
|
47
|
+
@Slot()
|
|
48
|
+
def copy(self):
|
|
49
|
+
clipboard: QClipboard = QApplication.clipboard()
|
|
50
|
+
cursor = self.textEdit.textCursor()
|
|
51
|
+
|
|
52
|
+
if cursor.selection().isEmpty():
|
|
53
|
+
text = self.textEdit.toPlainText()
|
|
54
|
+
else:
|
|
55
|
+
# copy selected text
|
|
56
|
+
text = cursor.selection().toPlainText()
|
|
57
|
+
|
|
58
|
+
if len(text) > 0:
|
|
59
|
+
clipboard.setText(text)
|
|
60
|
+
|
|
61
|
+
@Slot()
|
|
62
|
+
def appendData(self, data, force=False):
|
|
63
|
+
if self.checkBox_enabled.isChecked() or force:
|
|
64
|
+
self.textEdit.moveCursor(QTextCursor.End)
|
|
65
|
+
self.textEdit.insertPlainText(data)
|
|
66
|
+
self.textEdit.ensureCursorVisible()
|
|
67
|
+
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from PySide6.QtWidgets import QDialog, QVBoxLayout, QHeaderView
|
|
2
|
+
from typing import List
|
|
3
|
+
|
|
4
|
+
from multiserialviewer.ui_files.uiFileHelper import createWidgetFromUiFile
|
|
5
|
+
from multiserialviewer.text_highlighter.textHighlighterConfig import TextHighlighterConfig
|
|
6
|
+
from multiserialviewer.text_highlighter.textHighlighterTableModel import TextHighlighterTableModel
|
|
7
|
+
from multiserialviewer.text_highlighter.colorSelectorItemDelegate import ColorSelectorItemDelegate
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TextHighlighterSettingsDialog(QDialog):
|
|
11
|
+
def __init__(self, parent, settings: List[TextHighlighterConfig]):
|
|
12
|
+
super().__init__(parent)
|
|
13
|
+
|
|
14
|
+
self.setWindowTitle("Text Highlighter Settings")
|
|
15
|
+
self.widget = createWidgetFromUiFile("textHighlighterSettingsDialog.ui")
|
|
16
|
+
|
|
17
|
+
self.table_model = TextHighlighterTableModel(settings)
|
|
18
|
+
self.widget.tableView.setModel(self.table_model)
|
|
19
|
+
self.widget.tableView.setItemDelegateForColumn(1, ColorSelectorItemDelegate(self.widget.tableView))
|
|
20
|
+
self.widget.tableView.setItemDelegateForColumn(2, ColorSelectorItemDelegate(self.widget.tableView))
|
|
21
|
+
|
|
22
|
+
# QTableView Headers
|
|
23
|
+
self.horizontal_header = self.widget.tableView.horizontalHeader()
|
|
24
|
+
self.vertical_header = self.widget.tableView.verticalHeader()
|
|
25
|
+
|
|
26
|
+
# size
|
|
27
|
+
self.horizontal_header.setSectionResizeMode(QHeaderView.Stretch)
|
|
28
|
+
self.horizontal_header.setSectionResizeMode(3, QHeaderView.ResizeToContents)
|
|
29
|
+
self.horizontal_header.setSectionResizeMode(4, QHeaderView.ResizeToContents)
|
|
30
|
+
|
|
31
|
+
# buttons
|
|
32
|
+
self.widget.buttonBox.accepted.connect(self.accept)
|
|
33
|
+
self.widget.buttonBox.rejected.connect(self.reject)
|
|
34
|
+
self.widget.pb_add.clicked.connect(self.addSetting)
|
|
35
|
+
self.widget.pb_delete.clicked.connect(self.deleteSetting)
|
|
36
|
+
|
|
37
|
+
QVBoxLayout(self).addWidget(self.widget)
|
|
38
|
+
|
|
39
|
+
def addSetting(self):
|
|
40
|
+
self.table_model.insertRows(self.table_model.rowCount(), 1)
|
|
41
|
+
|
|
42
|
+
def deleteSetting(self):
|
|
43
|
+
selected_model_indices = [modelIndex.row() for modelIndex in self.widget.tableView.selectionModel().selectedRows()]
|
|
44
|
+
|
|
45
|
+
for index in sorted(selected_model_indices, reverse=True):
|
|
46
|
+
self.table_model.removeRows(index, 1)
|
|
File without changes
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import serial
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class SerialConnectionSettings:
|
|
5
|
+
def __init__(self, port_name: str):
|
|
6
|
+
self.portName = port_name
|
|
7
|
+
self.baudrate = 1000000
|
|
8
|
+
self.bytesize = serial.EIGHTBITS
|
|
9
|
+
self.parity = serial.PARITY_NONE
|
|
10
|
+
self.stopbits = serial.STOPBITS_ONE
|
|
11
|
+
self.timeout = 0.5
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from threading import Thread, Event
|
|
3
|
+
from queue import Empty
|
|
4
|
+
from PySide6.QtCore import Signal, QObject
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class SerialDataProcessor(QObject):
|
|
8
|
+
dataAvailable = Signal(str)
|
|
9
|
+
|
|
10
|
+
def __init__(self, raw_data_queue):
|
|
11
|
+
super(SerialDataProcessor, self).__init__()
|
|
12
|
+
self.lastEmitTimestamp = self.getTimestamp()
|
|
13
|
+
self.terminateEvent = Event()
|
|
14
|
+
self.rawDataQueue = raw_data_queue
|
|
15
|
+
self.thread = None
|
|
16
|
+
|
|
17
|
+
def start(self):
|
|
18
|
+
if self.thread is None:
|
|
19
|
+
self.thread = Thread(target=self.processData, args=(self.rawDataQueue, self.terminateEvent))
|
|
20
|
+
self.thread.start()
|
|
21
|
+
|
|
22
|
+
def stop(self):
|
|
23
|
+
if self.thread and self.thread.is_alive():
|
|
24
|
+
self.terminateEvent.set()
|
|
25
|
+
self.thread.join()
|
|
26
|
+
self.thread = None
|
|
27
|
+
self.terminateEvent.clear()
|
|
28
|
+
|
|
29
|
+
def getTimestamp(self):
|
|
30
|
+
return int(round(time.time() * 1000))
|
|
31
|
+
|
|
32
|
+
def timeDiffSinceLastEmit(self):
|
|
33
|
+
return self.getTimestamp() - self.lastEmitTimestamp
|
|
34
|
+
|
|
35
|
+
def processData(self, queue, terminate_event):
|
|
36
|
+
data = bytearray()
|
|
37
|
+
|
|
38
|
+
while not terminate_event.is_set():
|
|
39
|
+
try:
|
|
40
|
+
rx_bytes = queue.get(timeout=0.2)
|
|
41
|
+
data.extend(rx_bytes)
|
|
42
|
+
# queue.task_done()
|
|
43
|
+
except Empty:
|
|
44
|
+
pass
|
|
45
|
+
finally:
|
|
46
|
+
if len(data) > 0:
|
|
47
|
+
try:
|
|
48
|
+
self.dataAvailable.emit(data.decode("ascii"))
|
|
49
|
+
except:
|
|
50
|
+
pass
|
|
51
|
+
data = bytearray()
|