Projekt

Obecné

Profil

« Předchozí | Další » 

Revize 64ca87d0

Přidáno uživatelem Martin Forejt před téměř 4 roky(ů)

Develop

Zobrazit rozdíly:

README.md
1 1
# Konfigurovatelný dashboard zobrazování senzorových dat (KIV) - Vochomůrka
2

  
3
# Build
4
```
5
poetry install
6
poetry build
7
```
8
```aswi2021vochomurka-1.0.0-py3-none-any.whl``` deployable file will be generated at dist folder
9

  
10
# Install
11
```
12
pip install aswi2021vochomurka-1.0.0-py3-none-any.whl
13
```
14

  
15
# Run
16
```
17
python -m aswi2021vochomurka.main
18
```
aswi2021vochomurka/app.py
1 1
import logging
2
import os
2 3

  
4
from PyQt5.QtCore import QSettings, QCoreApplication
3 5
from PyQt5.QtWidgets import QApplication
4 6

  
5 7
from aswi2021vochomurka.view.main_view import MainView
......
8 10
class Application(QApplication):
9 11
    def __init__(self, sys_argv):
10 12
        init_logger()
13
        init_settings()
11 14
        super(Application, self).__init__(sys_argv)
12 15
        logging.info('App started')
13 16
        self.main_view = MainView()
......
15 18

  
16 19

  
17 20
def init_logger():
21
    if not os.path.exists('data'):
22
        os.mkdir('data')
23

  
18 24
    logging.basicConfig(
19 25
        level=logging.DEBUG,
20 26
        filename='data/app.log',
......
31 37

  
32 38
    logging.getLogger('apscheduler').setLevel(logging.WARNING)
33 39
    logging.getLogger('matplotlib').setLevel(logging.WARNING)
40

  
41

  
42
def init_settings():
43
    QSettings.setDefaultFormat(QSettings.IniFormat)
44
    QSettings.setPath(QSettings.IniFormat, QSettings.SystemScope, '.')
aswi2021vochomurka/model/Message.py
2 2

  
3 3

  
4 4
class Message(RecordClass):
5
    """
6
    Message wrapper
7
    """
5 8
    topic: str
6 9
    index: int
7 10
    date: str
aswi2021vochomurka/service/file_manager.py
15 15
    ".": "_"})
16 16

  
17 17

  
18
def create_filename(message: Message):
18
def create_filename(message: Message) -> str:
19
    """
20
    Create file name based on message data
21
    :param message: message
22
    :return: filename
23
    """
19 24
    name = "data/" + message.topic.translate(trans) + "/" + message.date + "_" + message.time + ".csv"
20 25
    return name
21 26

  
22 27

  
23 28
class FileManager:
29
    """
30
    Helper class for writing incoming message to files
31
    Each topic has created own instance of this class
32
    """
24 33
    topic: str
25 34
    lastUpdate: float
26 35
    file: TextIO
27 36

  
28 37
    def __init__(self, topic: str, message: Message):
38
        """
39
        Constructing new FileManager will create new file and write first message
40
        :param topic: topic
41
        :param message: message
42
        :except when creating new file fails
43
        """
29 44
        self.topic = topic
30 45
        logging.debug('opening file ' + self.topic)
31 46

  
......
39 54
        self.write(message)
40 55

  
41 56
    def write(self, message: Message):
57
        """
58
        Append message to file
59
        :param message: message
60
        """
42 61
        self.file.write(message.date + ";" + message.time + ";" + str(message.index) + ";" + str(message.value) + "\n")
43 62
        self.lastUpdate = time.time()
44 63

  
45 64
    def close(self):
65
        """
66
        Close file
67
        """
46 68
        logging.debug('closing file ' + self.topic)
47 69
        self.file.flush()
48 70
        self.file.close()
aswi2021vochomurka/service/message_parser.py
6 6

  
7 7

  
8 8
class ParseException(Exception):
9
    """
10
    May be throw when message has incorrect format
11
    """
9 12
    pass
10 13

  
11 14

  
12 15
def parse_mqtt_message(message: MQTTMessage) -> Message:
16
    """
17
    Parse MQTTMessage to Message
18
    :param message: messsage
19
    :return: message
20
    :except: when message has incorrect format
21
    """
13 22
    data = message.payload.decode("utf-8")
14 23
    parts = data.split(";")
15 24
    logging.debug('Parsing message: ' + data + ', parts: ' + str(len(parts)))
aswi2021vochomurka/service/mqtt/mqtt_subscriber.py
7 7

  
8 8

  
9 9
class MQTTSubscriber(Subscriber):
10
    """
11
    MQTT subscriber, implementation of Subscriber over MQTT protocol
12
    """
13
    client: mqtt.Client = None
10 14

  
11
    # The callback for when the client receives a CONNACK response from the server.
12 15
    def on_connect(self, client, userdata, flags, rc, properties=None):
16
        """
17
        The callback for when the client receives a CONNACK response from the server.
18
        See: mqtt.Client.on_connect for info about params
19
        """
13 20
        logging.info('Connected with result code ' + str(rc))
14 21
        self.callback.onConnected()
15 22

  
......
19 26
            logging.info('Subscribed to topic: ' + topic)
20 27
            client.subscribe(topic)
21 28

  
22
    # The callback for when a PUBLISH message is received from the server.
23 29
    def on_message(self, client, userdata, message: mqtt.MQTTMessage):
30
        """
31
        The callback for when a PUBLISH message is received from the server.
32
        See: mqtt.Client.on_message for info about params
33
        """
24 34
        try:
25 35
            m = parse_mqtt_message(message)
26 36
            logging.info('Message: ' + str(m))
......
33 43
            pass
34 44

  
35 45
    def on_disconnect(self, client, userdata, rc):
46
        """
47
        The callback for when the client disconnects from the server.
48
        See: mqtt.Client.on_disconnect for info about params
49
        """
36 50
        logging.info('Disconnected')
37 51
        self.callback.onDisconnected()
38 52
        self.stop()
39 53

  
40 54
    def start(self):
55
        """
56
        Start mqtt client
57
        """
41 58
        super().start()
42 59
        client = mqtt.Client()
60
        self.client = client
43 61
        client.on_connect = self.on_connect
44 62
        client.on_message = self.on_message
45 63
        client.on_disconnect = self.on_disconnect
......
47 65

  
48 66
        if not self.params.anonymous:
49 67
            logging.info('Using credentials, username=' + self.params.username + ', password=' + self.params.password)
50
            client.tls_set()
51 68
            client.username_pw_set(self.params.username, self.params.password)
52 69

  
53 70
        try:
......
63 80
            return
64 81

  
65 82
        client.loop_forever()
83

  
84
    def stop(self):
85
        """
86
        Stop mqtt client
87
        """
88
        super().stop()
89
        if self.client is not None:
90
            logging.info("Disconnecting from broker")
91
            client = self.client
92
            self.client = None
93
            client.disconnect()
aswi2021vochomurka/service/subscriber.py
1 1
import time
2
from typing import Dict
2 3

  
3 4
from apscheduler.schedulers.background import BackgroundScheduler
5
from apscheduler.schedulers.base import STATE_STOPPED
4 6

  
5 7
from aswi2021vochomurka.model.Message import Message
6 8
from aswi2021vochomurka.service.file_manager import FileManager
7 9
from aswi2021vochomurka.service.subscriber_callback import SubscriberCallback
8 10
from aswi2021vochomurka.service.subscriber_params import SubscriberParams
9
from typing import Dict
10 11

  
11 12

  
12 13
class Subscriber:
14
    """
15
    Subscriber is responsible for establishing communication with broker and notifying
16
    about new message via callback
17
    Subscriber must be started via 'start' method and stopped via 'stop' method
18
    """
13 19
    callback: SubscriberCallback
14 20
    params: SubscriberParams
15 21

  
16
    scheduler = BackgroundScheduler()
22
    scheduler: BackgroundScheduler
17 23
    files: Dict[str, FileManager] = {}
18 24

  
19 25
    def __init__(self, callback: SubscriberCallback, params: SubscriberParams):
26
        """
27
        Constructor
28
        :param callback: callback
29
        :param params: params
30
        """
20 31
        self.callback = callback
21 32
        self.params = params
22 33

  
23 34
    def start(self):
35
        """
36
        Start subscriber
37
        """
24 38
        # start scheduler to check closed topics
39
        self.scheduler = BackgroundScheduler()
25 40
        self.scheduler.add_job(self.check_closed_topics, 'interval', seconds=self.params.closeLimit)
26 41
        self.scheduler.start()
27 42

  
28 43
    def stop(self):
29
        self.scheduler.shutdown()
44
        """
45
        Stop subscriber
46
        """
47
        if self.scheduler.state != STATE_STOPPED:
48
            self.scheduler.shutdown()
30 49
        self.close_files()
31 50

  
32 51
    def close_files(self):
52
        """
53
        Close all open files
54
        """
33 55
        for topic in self.files:
34 56
            self.files.get(topic).close()
35
        self.files = {}
57
        self.files.clear()
36 58

  
37 59
    def check_closed_topics(self):
60
        """
61
        May be called periodically for checking for expired timeout for closing topic
62
        """
38 63
        t = time.time()
39 64
        for topic in list(self.files):
40 65
            file = self.files.get(topic)
......
44 69
                self.files.pop(topic)
45 70

  
46 71
    def write_to_file(self, message: Message):
72
        """
73
        Write message to file
74
        :param message: message
75
        """
47 76
        if message.topic in self.files:
77
            # file exist, just append message
48 78
            self.files.get(message.topic).write(message)
49 79
        else:
80
            # new message for this topic, create new file
50 81
            fm = FileManager(message.topic, message)
51 82
            self.files[message.topic] = fm
aswi2021vochomurka/service/subscriber_params.py
3 3

  
4 4

  
5 5
class ConnectionParams(RecordClass):
6
    """
7
    Connection params to connect to broker
8
    """
6 9
    host: str
7 10
    port: int
8 11
    timeout: int
9 12

  
10 13

  
11 14
class SubscriberParams(RecordClass):
15
    """
16
    Params for Subscriber
17
    """
12 18
    # list of topics to subscribe
13 19
    topics: List[str]
14 20
    # close limit in seconds
aswi2021vochomurka/settings.ini
1
[General]
2
topics_items=/home/1, /home/2
3
topics_timeout=60
4
connection_host=localhost
5
connection_port=1883
6
connection_keepalive=60
7
connection_anonymous=true
8
connection_username=
9
connection_password=
aswi2021vochomurka/view/logger_view.py
5 5

  
6 6

  
7 7
class LoggerView(logging.Handler, QObject):
8
    """
9
    LoggerView represents console in gui application.
10
    """
8 11
    append = pyqtSignal(str)
9 12

  
10 13
    def __init__(self, parent):
14
        """
15
        Constructor
16
        """
11 17
        super().__init__()
12 18
        super(QObject, self).__init__()
13 19

  
......
19 25
        )
20 26

  
21 27
    def emit(self, record):
28
        """
29
        Emit message from record
30
        :param record: record
31
        """
22 32
        msg = self.format(record)
23 33
        self.append.emit(msg)
24 34

  
25 35
    def appendMessage(self, msg):
36
        """
37
        Append message
38
        :param msg: message
39
        """
26 40
        self.widget.appendPlainText(msg)
27 41
        self.widget.verticalScrollBar().setValue(self.widget.verticalScrollBar().maximum())
28 42

  
aswi2021vochomurka/view/main_view.py
1 1
import logging
2
import math
3
import random
4 2

  
5
from PyQt5.QtCore import QSize, QThread, QObject, pyqtSignal
6
from PyQt5.QtWidgets import QMainWindow, QPlainTextEdit, QDialog, QHBoxLayout
7
from numpy import pi, sin, cos, tan, exp
8
from matplotlib.pyplot import subplot
3
import matplotlib.pyplot as plt
4
from PyQt5 import QtCore
5
from PyQt5 import QtGui
6
from PyQt5.QtCore import QSize, QThread, QObject, pyqtSignal, QSettings
7
from PyQt5.QtWidgets import QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QScrollArea, QGridLayout, QFileDialog
8
from PyQt5.QtWidgets import QMenuBar, QAction, QPushButton
9
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
9 10

  
10 11
from aswi2021vochomurka.model.Message import Message
11 12
from aswi2021vochomurka.service.mqtt.mqtt_subscriber import MQTTSubscriber
12 13
from aswi2021vochomurka.service.subscriber import Subscriber
13 14
from aswi2021vochomurka.service.subscriber_callback import SubscriberCallback
14 15
from aswi2021vochomurka.service.subscriber_params import SubscriberParams, ConnectionParams
15

  
16
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
17
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
18
import matplotlib.pyplot as plt
19

  
20
import sys
21
from PyQt5.QtWidgets import QDialog, QApplication, QPushButton, QVBoxLayout
22
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
23
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
24
import matplotlib.pyplot as plt
25
import random
26

  
27 16
from aswi2021vochomurka.view.logger_view import LoggerView
17
from aswi2021vochomurka.view.settings import SettingsDialog, DEFAULT_HOST, DEFAULT_PORT, DEFAULT_KEEPALIVE, \
18
    DEFAULT_ANONYMOUS, DEFAULT_USERNAME, DEFAULT_TIMEOUT, DEFAULT_TOPICS, get_settings
28 19

  
29 20

  
30 21
class Worker(QObject, SubscriberCallback):
22
    """
23
    Worker representing thread
24
    """
31 25
    connected = pyqtSignal()
32 26
    disconnected = pyqtSignal()
33 27
    error = pyqtSignal(Exception)
34
    newMessage = pyqtSignal(str)
28
    newMessage = pyqtSignal(Message)
29
    closeTopic = pyqtSignal(str)
35 30
    subscriber: Subscriber = None
31
    params: SubscriberParams
36 32

  
37
    params = SubscriberParams(
38
        ["/home/1", "/home/2"],
39
        10,
40
        ConnectionParams("localhost", 1883, 60),
41
        True
42
    )
33
    def __init__(self, params: SubscriberParams) -> None:
34
        """
35
        Constructor
36
        """
37
        super().__init__()
38
        self.params = params
43 39

  
44 40
    def start(self):
41
        """
42
        Start worker
43
        """
45 44
        self.subscriber = MQTTSubscriber(self, self.params)
46 45
        self.subscriber.start()
47 46

  
47
    def stop(self):
48
        """
49
        Stop worker
50
        """
51
        self.subscriber.stop()
52

  
48 53
    def onConnected(self):
54
        """
55
        Emit connection signal
56
        """
49 57
        self.connected.emit()
50 58

  
51 59
    def onDisconnected(self):
60
        """
61
        Emit disconnection signal
62
        """
52 63
        self.disconnected.emit()
53 64

  
54 65
    def onError(self):
55
        self.error.emit()
66
        pass
56 67

  
57 68
    def onMessage(self, message: Message):
58
        self.newMessage.emit(message.topic)
59
        self.window.plot(message)
69
        """
70
        Emit message signal
71
        :param message: message
72
        """
73
        self.newMessage.emit(message)
60 74

  
61 75
    def onCloseTopic(self, topic: str):
62
        pass
63

  
64

  
65
class MainView(QDialog):
76
        """
77
        Emit close topic signal
78
        :param topic: topic
79
        """
80
        print("Close topic")
81
        self.closeTopic.emit(topic)
82

  
83

  
84
class MainView(QMainWindow):
85
    """
86
    Main window of application.
87
    Displays all parts of the application.
88
    """
66 89
    worker: Worker = None
67 90
    workerThread: QThread = None
68 91

  
69 92
    def __init__(self):
93
        """
94
        Constructor - displays all parts of the application.
95
        """
70 96
        super(MainView, self).__init__()
71 97

  
72 98
        self.chartsNum = 0
73
        self.arrayData = []
74

  
75
        self.dataIndex = 0
76 99
        self.dataDict = {}
100
        self.dataDict2 = {}
101
        self.canvasDict = {}
102
        self.figureDict = {}
103
        self.widgetDict = {}
104
        self.widgetList = []
77 105

  
78
        self.figure = plt.figure(figsize=([500,500]))
106
        self.setMinimumSize(QSize(1200, 800))
107
        self.setWindowTitle("MQTT client")
79 108

  
80
        self.canvas = FigureCanvas(self.figure)
81
        self.toolbar = NavigationToolbar(self.canvas, self)
109
        logger = self._createLoggerView()
110
        layout = QVBoxLayout()
111
        layout.addWidget(logger.widget)
112

  
113
        widget = QWidget()
114
        widget.setLayout(layout)
115
        self.setCentralWidget(widget)
116
        self._createMenuBar()
82 117

  
83
        self.setMinimumSize(QSize(440, 240))
84
        self.setWindowTitle("MQTT demo")
118
        scrollArea = QScrollArea(self)
119
        scrollArea.setWidgetResizable(True)
120
        scrollContent = QWidget()
121
        self.grid = QGridLayout(scrollContent)
122
        scrollArea.setWidget(scrollContent)
123
        layout.addWidget(scrollArea)
85 124

  
86
        # Add logger text field
125
        self.init_subscriber()
126

  
127
    def _createLoggerView(self):
128
        """
129
        Create logger view
130
        """
87 131
        logger = LoggerView(self)
88 132
        formatter = logging.Formatter('%(asctime)s %(message)s', '%H:%M')
89 133
        logger.setFormatter(formatter)
90 134
        logger.setLevel(logging.INFO)
91 135
        logging.getLogger('').addHandler(logger)
136
        return logger
137

  
138
    def _createMenuBar(self):
139
        """
140
        Creates menu bar
141
        """
142
        menuBar = QMenuBar(self)
143
        settingsAction = QAction("&Settings", self)
144
        settingsAction.triggered.connect(self.settings)
145
        menuBar.addAction(settingsAction)
146
        self.setMenuBar(menuBar)
92 147

  
93
        layout = QVBoxLayout()
94
        layout.addWidget(logger.widget)
95
        layout.addWidget(self.toolbar)
96
        layout.addWidget(self.canvas)
148
    def plot(self, message: Message):
149
        """
150
        Plots new charts or updates old ones
151
        :param message: message
152
        """
153
        if message.topic in self.dataDict:
154
            # topic already exists
155
            self.dataDict[message.topic].append(message.value)
97 156

  
98
        self.setLayout(layout)
157
            figure = self.figureDict[message.topic]
158
            figure.clear()
99 159

  
100
        self.initSubscriber()
160
            figure = plt.figure(figure.number)
161
            figure.suptitle(message.topic)
162
            plt.plot(self.dataDict[message.topic])
101 163

  
102
    def plot(self, message: Message):
103
        self.figure.clear()
164
            if message.topic in self.dataDict2:
165
                plt.plot(self.dataDict2[message.topic])
104 166

  
105
        if message.topic in self.dataDict:
106
            self.dataDict[message.topic].append(message.value)
167
            self.canvasDict[message.topic].draw()
107 168
        else:
169
            # new topic
108 170
            self.dataDict[message.topic] = [message.value]
109
            self.chartsNum += 1
110 171

  
111
        rows = math.ceil(self.chartsNum / 2)
172
            figure = plt.figure(figsize=[500, 500])
173

  
174
            canvas = FigureCanvas(figure)
175
            self.layout = QHBoxLayout()
176

  
177
            plt.plot(self.dataDict[message.topic])
112 178

  
113
        b = 0
114
        for a in self.dataDict.values():
115
            self.figure.add_subplot(rows, 2, b + 1)
116
            b += 1
117
            plt.plot(a)
179
            figure.suptitle(message.topic)
118 180

  
119
        self.canvas.draw()
181
            self.canvasDict[message.topic] = canvas
182
            self.figureDict[message.topic] = figure
120 183

  
121
    def initSubscriber(self):
184
            widget = QWidget()
185
            self.widgetDict[message.topic] = widget
186
            self.widgetList.append(widget)
187
            widget.setLayout(self.layout)
188
            button = QPushButton('Load')
189
            button.clicked.connect(lambda: self.getFile(message.topic))
190
            button.setFixedSize(QSize(40, 40))
191

  
192
            button2 = QPushButton('Del')
193
            button2.clicked.connect(lambda: self.deleteSecond(message.topic))
194
            button2.setFixedSize(QSize(40, 40))
195

  
196
            self.layout.addWidget(canvas)
197

  
198
            boxLayout = QVBoxLayout()
199
            boxLayout.addWidget(button)
200
            boxLayout.addWidget(button2)
201
            boxLayout.setAlignment(button, QtCore.Qt.AlignTop)
202
            boxLayout.setAlignment(button2, QtCore.Qt.AlignTop)
203
            self.layout.addLayout(boxLayout)
204
            widget.setMinimumSize(QSize(500, 500))
205

  
206
            self.grid.addWidget(widget, int(self.chartsNum / 2), self.chartsNum % 2)
207

  
208
            self.chartsNum += 1
209

  
210
    def getFile(self, topic: str):
211
        fname = QFileDialog.getOpenFileName(self, 'Open file',
212
                                            'c:\\', "CSV files (*.csv)")
213
        try:
214
            figure = self.figureDict[topic]
215
            figure.clear()
216

  
217
            self.dataDict2[topic] = []
218

  
219
            file1 = open(fname[0], 'r')
220
            lines = file1.readlines()
221

  
222
            count = 0
223
            for line in lines:
224
                count += 1
225
                parts = line.split(';')
226
                value = float(parts[3])
227
                self.dataDict2[topic].append(value)
228

  
229
            figure = plt.figure(figure.number)
230
            figure.suptitle(topic)
231
            plt.plot(self.dataDict[topic])
232
            plt.plot(self.dataDict2[topic])
233

  
234
            self.canvasDict[topic].draw()
235
        except:
236
            logging.error("Error while loading and displaying data file")
237

  
238
    def deleteSecond(self, topic: str):
239
        if topic in self.dataDict2:
240
            del self.dataDict2[topic]
241

  
242
            figure = self.figureDict[topic]
243
            figure.clear()
244

  
245
            figure = plt.figure(figure.number)
246
            figure.suptitle(topic)
247
            plt.plot(self.dataDict[topic])
248

  
249
            self.canvasDict[topic].draw()
250

  
251
    def deletePlot(self, topic: str):
252
        """
253
        Deletes plot
254
        :param topic: topic
255
        """
256
        widget = self.widgetDict[topic]
257
        self.widgetList.remove(widget)
258
        widget.setParent(None)
259

  
260
        del self.widgetDict[topic]
261
        del self.canvasDict[topic]
262
        del self.figureDict[topic]
263
        del self.dataDict[topic]
264
        if topic in self.dataDict2:
265
            del self.dataDict2[topic]
266

  
267
        self.reorganizePlots()
268

  
269
    def reorganizePlots(self):
270
        """
271
        Reorganize plots
272
        """
273
        count = 0
274
        for widget in self.widgetList:
275
            self.grid.addWidget(widget, int(count / 2), count % 2)
276
            count += 1
277

  
278
        self.chartsNum -= 1
279

  
280
    def closeEvent(self, a0: QtGui.QCloseEvent) -> None:
281
        self.worker.stop()
282

  
283
    def settings(self):
284
        """
285
        Opens settings dialog
286
        """
287
        dialog = SettingsDialog()
288
        if dialog.exec_():
289
            self.reconnect()
290

  
291
    def disconnect(self):
292
        """
293
        Disconnect
294
        """
295
        self.worker.stop()
296
        self.workerThread.quit()
297
        self.workerThread.wait()
298

  
299
    def reconnect(self):
300
        """
301
        Reconnect
302
        """
303
        for widget in self.widgetDict.values():
304
            widget.setParent(None)
305

  
306
        self.widgetDict.clear()
307
        self.widgetList.clear()
308
        self.canvasDict.clear()
309
        self.figureDict.clear()
310
        self.dataDict.clear()
311
        self.dataDict2.clear()
312

  
313
        self.disconnect()
314
        self.worker.params = self.getConfigParams()
315
        self.workerThread.start()
316

  
317
    def init_subscriber(self):
318
        """
319
        Initialization of subscriber
320
        """
122 321
        self.workerThread = QThread()
123
        self.worker = Worker()
322
        self.worker = Worker(self.getConfigParams())
124 323
        self.worker.moveToThread(self.workerThread)
125 324
        self.workerThread.started.connect(self.worker.start)
126
        # self.worker.newMessage.connect(
127
        #    lambda message: self.b.insertPlainText(message + "\n")
128
        # )
325
        self.worker.newMessage.connect(
326
            lambda message: self.plot(message)
327
        )
328
        self.worker.closeTopic.connect(
329
            lambda topic: self.deletePlot(topic)
330
        )
129 331
        self.worker.window = self
130 332
        self.workerThread.start()
333

  
334
    def getConfigParams(self) -> SubscriberParams:
335
        """
336
        Returns config parameters
337
        :return: config parameters
338
        """
339
        settings = get_settings()
340

  
341
        connection = ConnectionParams(
342
            settings.value("connection_host", DEFAULT_HOST, str),
343
            settings.value("connection_port", DEFAULT_PORT, int),
344
            settings.value("connection_keepalive", DEFAULT_KEEPALIVE, int)
345
        )
346

  
347
        params = SubscriberParams(
348
            settings.value("topics_items", DEFAULT_TOPICS),
349
            settings.value("topics_timeout", DEFAULT_TIMEOUT, int),
350
            connection,
351
            settings.value("connection_anonymous", DEFAULT_ANONYMOUS, bool),
352
            settings.value("connection_username", DEFAULT_USERNAME, str),
353
            settings.value("connection_password", DEFAULT_USERNAME, str),
354
        )
355

  
356
        return params
aswi2021vochomurka/view/settings.py
1
from PyQt5 import QtCore
2
from PyQt5.QtCore import QSettings, QSize
3
from PyQt5.QtWidgets import QDialog, QVBoxLayout, QDialogButtonBox, QGroupBox, QFormLayout, QLabel, QLineEdit, QSpinBox, \
4
    QCheckBox, QPushButton, QListWidget, QListWidgetItem
5

  
6
DEFAULT_HOST = "localhost"
7
DEFAULT_PORT = 1883
8
DEFAULT_KEEPALIVE = 60
9
DEFAULT_ANONYMOUS = True
10
DEFAULT_USERNAME = ""
11
DEFAULT_PASSWORD = ""
12
DEFAULT_TOPICS = ["/home/1", "/home/2"]
13
DEFAULT_TIMEOUT = 60
14

  
15

  
16
def get_settings():
17
    settings = QSettings('settings.ini', QSettings.IniFormat)
18
    return settings
19

  
20

  
21
class SettingsDialog(QDialog):
22
    """
23
    Settings dialog.
24
    In settings dialog is possible to change settings of application.
25
    """
26
    topics = DEFAULT_TOPICS
27

  
28
    def __init__(self):
29
        """
30
        Constructor
31
        """
32
        super(SettingsDialog, self).__init__(None,
33
                                             QtCore.Qt.WindowCloseButtonHint | QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint)
34
        self.settings = get_settings()
35
        self.setWindowTitle("Settings")
36
        self.setMinimumSize(QSize(600, 500))
37

  
38
        connectionGroupBox = QGroupBox("Connection")
39
        connectionLayout = QFormLayout()
40
        self.hostInput = QLineEdit(self.settings.value("connection_host", DEFAULT_HOST, str))
41
        connectionLayout.addRow(QLabel("Host:"), self.hostInput)
42
        self.portInput = QSpinBox()
43
        self.portInput.setMaximum(65535)
44
        self.portInput.setValue(self.settings.value("connection_port", DEFAULT_PORT, int))
45
        connectionLayout.addRow(QLabel("Port:"), self.portInput)
46
        self.keepaliveInput = QSpinBox()
47
        self.keepaliveInput.setMaximum(1000)
48
        self.keepaliveInput.setValue(self.settings.value("connection_keepalive", DEFAULT_KEEPALIVE, int))
49
        connectionLayout.addRow(QLabel("Keepalive(s):"), self.keepaliveInput)
50
        self.anonymousInput = QCheckBox()
51
        self.anonymousInput.setChecked(self.settings.value("connection_anonymous", DEFAULT_ANONYMOUS, bool))
52
        self.anonymousInput.stateChanged.connect(self.anonymousChanged)
53
        connectionLayout.addRow(QLabel("Anonymous:"), self.anonymousInput)
54
        self.usernameInput = QLineEdit(self.settings.value("connection_username", DEFAULT_USERNAME, str))
55
        connectionLayout.addRow(QLabel("Username:"), self.usernameInput)
56
        self.passwordInput = QLineEdit(self.settings.value("connection_password", DEFAULT_PASSWORD, str))
57
        connectionLayout.addRow(QLabel("Password:"), self.passwordInput)
58
        self.anonymousChanged()
59
        connectionGroupBox.setLayout(connectionLayout)
60

  
61
        topicsGroupBox = QGroupBox("Topics")
62
        topicsLayout = QFormLayout()
63

  
64
        self.topics = self.settings.value("topics_items", DEFAULT_TOPICS, list)
65
        self.topicsListWidget = QListWidget()
66
        for topic in self.topics:
67
            item = QListWidgetItem()
68
            item.setText(topic)
69
            item.setFlags(item.flags() | QtCore.Qt.ItemIsEditable)
70
            self.topicsListWidget.addItem(item)
71

  
72
        topicsLayout.addRow(self.topicsListWidget)
73
        add = QPushButton("Add")
74
        add.setFixedWidth(60)
75
        add.clicked.connect(self.addTopic)
76
        remove = QPushButton("Remove")
77
        remove.setFixedWidth(60)
78
        remove.clicked.connect(self.removeTopic)
79
        topicsLayout.addRow(add, remove)
80

  
81
        self.timeoutInput = QSpinBox()
82
        self.timeoutInput.setMaximum(1000)
83
        self.timeoutInput.setToolTip("Unsubscribe topic and close file when there is not new message after this "
84
                                     "timeout (in seconds) expires")
85
        timeoutLabel = QLabel("Topic timeout(s):")
86
        timeoutLabel.setToolTip("Unsubscribe topic and close file when there is not new message after this "
87
                                "timeout (in seconds) expires")
88
        self.timeoutInput.setValue(self.settings.value("topics_timeout", DEFAULT_TIMEOUT, int))
89
        topicsLayout.addRow(timeoutLabel, self.timeoutInput)
90

  
91
        topicsGroupBox.setLayout(topicsLayout)
92

  
93
        buttonBox = QDialogButtonBox()
94
        buttonBox.addButton("Save and Reconnect", QDialogButtonBox.AcceptRole)
95
        buttonBox.addButton("Cancel", QDialogButtonBox.RejectRole)
96
        buttonBox.accepted.connect(self.accept)
97
        buttonBox.rejected.connect(self.reject)
98

  
99
        mainLayout = QVBoxLayout()
100
        mainLayout.addWidget(connectionGroupBox)
101
        mainLayout.addWidget(topicsGroupBox)
102
        mainLayout.addWidget(buttonBox)
103
        self.setLayout(mainLayout)
104

  
105
    def addTopic(self):
106
        """
107
        Add topic
108
        """
109
        item = QListWidgetItem()
110
        item.setText("/topic")
111
        item.setFlags(item.flags() | QtCore.Qt.ItemIsEditable)
112
        self.topicsListWidget.addItem(item)
113

  
114
    def removeTopic(self):
115
        """
116
        Remove topic
117
        """
118
        for item in self.topicsListWidget.selectedItems():
119
            self.topicsListWidget.takeItem(self.topicsListWidget.row(item))
120

  
121
    def anonymousChanged(self):
122
        """
123
        Changing anonymous/user status
124
        """
125
        self.usernameInput.setEnabled(not self.anonymousInput.isChecked())
126
        self.passwordInput.setEnabled(not self.anonymousInput.isChecked())
127

  
128
    def accept(self) -> None:
129
        """
130
        Accept changes
131
        """
132
        super().accept()
133
        self.topics = []
134
        for index in range(self.topicsListWidget.count()):
135
            self.topics.append(self.topicsListWidget.item(index).text())
136

  
137
        self.settings.setValue("topics_items", self.topics)
138
        self.settings.setValue("topics_timeout", self.timeoutInput.value())
139
        self.settings.setValue("connection_host", self.hostInput.text())
140
        self.settings.setValue("connection_port", self.portInput.value())
141
        self.settings.setValue("connection_keepalive", self.keepaliveInput.value())
142
        self.settings.setValue("connection_anonymous", self.anonymousInput.isChecked())
143
        self.settings.setValue("connection_username", self.usernameInput.text())
144
        self.settings.setValue("connection_password", self.passwordInput.text())
pyproject.toml
1 1
[tool.poetry]
2 2
name = "aswi2021vochomurka"
3
version = "0.1.0"
3
version = "1.0.0"
4 4
description = ""
5 5
authors = ["Tym Vochomurka"]
6 6

  

Také k dispozici: Unified diff