Issue
The aim of my code is to create a window with labels, each representing a sensor. The data comes from the USB port in a table of 0s&1s and depending on the value it colours the labels accordingly.
The goal is supposed to look like this:
I am unsure of how to pass the data from the port to the function in real time without recreating the window as a whole each time, as I only want it to change the drawn labels (and their colours). Therefore, I was wondering if anyone could point me in the right direction or give me a suggestion of what I can do to make this work.
The code creating my main window & labels:
class MainWindow (qt.QMainWindow):
def __init__(self):
super().__init__()
self.count = 0
self.j = 0
self.i = 0
self.screen()
self.making()
def screen(self):
self.setWindowTitle("Bee Counter")
self.showMaximized()
def making(self):
for i in values: #Iterates over the list of data which comes from the port.
if (i == 1):
self.label = qt.QLabel(self)
self.label.setStyleSheet("background-color: green; border: 1px solid black;")
self.move_label() #Creates multiple labels with the colour green.
else:
self.label = qt.QLabel(self)
self.label.setStyleSheet("background-color: red; border: 1px solid black;")
self.move_label() #Creates multiple labels with the colour red.
self.count +=1
def move_label(self):
self.label.resize(A, A)
self.label.setAlignment(Qt.AlignmentFlag.AlignCenter)
if self.count%2==0:
k=20
self.label.move(X0+self.j,k)
self.j=self.j+X_STEP
self.label.setText(f"{self.count}")
else:
k=90
self.label.move(X0+self.i,k)
self.label.setText(f"{self.count}")
self.i=self.i+X_STEP
self.label.show()
if __name__ == '__main__':
app = qt.QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec()
The code getting the data from the port:
ser = serial.Serial(
port='COM3',\
baudrate=115200,\
parity=serial.PARITY_NONE,\
stopbits=serial.STOPBITS_ONE,\
bytesize=serial.EIGHTBITS)
print("<Succesfully connected to: " + ser.portstr)
while 1:
if ser.inWaiting()>0:
line = ser.readline()
line = line.decode('utf-8')
line = [char for char in line if char=="1" or char=="0"] #Gets data in a form of a table of 0s & 1s.
print(line)
time.sleep(0.01)
ser.close()
P.S. Excuse my perhaps very obvious question, I simply can not wrap my head around it :)
Solution
The concept is based on the wrong premise: making
should only create the labels (and keep a reference to them), while another function should be responsible for their update.
Since the data rate is quite fast and the display object very simple, it's probably better to use a custom widget instead of continuously set the style sheet.
class DisplayWidget(QWidget):
state = False
def __init__(self, index):
super().__init__()
self.index = str(index)
self.setFixedSize(32, 32)
def setState(self, state):
if self.state != state:
self.state = state
self.update()
def paintEvent(self, event):
qp = QPainter(self)
qp.setBrush(Qt.green if self.state else Qt.red)
qp.drawRect(self.rect().adjusted(0, 0, -1, -1))
qp.drawText(self.rect(), Qt.AlignCenter, self.index)
Now, the "viewer" is a custom widget that is able to create a predefined grid of display widgets and updates them when necessary.
You can provide a default field count, or just ignore that, since the function that updates the data is also capable to update the grid whenever the field count doesn't match.
class SerialViewer(QWidget):
def __init__(self, fieldCount=None):
super().__init__()
layout = QGridLayout(self)
layout.setAlignment(Qt.AlignCenter)
self.widgets = []
if isinstance(fieldCount, int) and fieldCount > 0:
self.createGrid(fieldCount)
def createGrid(self, fieldCount, rows=2):
while self.widgets:
self.widgets.pop(0).deleteLater()
rows = max(1, rows)
count = 0
columns, rest = divmod(fieldCount, rows)
if rest:
columns += 1
for column in range(columns):
for row in range(rows):
widget = DisplayWidget(count)
self.layout().addWidget(widget, row, column)
self.widgets.append(widget)
count += 1
if count == fieldCount:
break
def updateData(self, data):
if len(data) != len(self.widgets):
self.createGrid(len(data))
for widget, state in zip(self.widgets, data):
widget.setState(state)
As you can see, instead of using resize()
or move()
, I'm using a layout manager that is automatically able to place (and eventually resize) the widgets. Remember, fixed geometries are almost always discouraged. Also note that widgets should not be directly added to a QMainWindow, but set for its central widget.
The thread is implemented in a QThread subclass, using a custom signal that is emitted whenever new data is available:
class SerialThread(QThread):
dataReceived = pyqtSignal(object)
def run(self):
ser = serial.Serial(
port='COM3',
baudrate=115200,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
bytesize=serial.EIGHTBITS)
self.keepRunning = True
while self.keepRunning:
if ser.inWaiting() > 0:
line = ser.readline()
line = line.decode('utf-8')
self.dataReceived.emit(
list(int(char) for char in line if char in '01')
)
# note that the indentation level of sleep() is *outside*
# of the "if" otherwise it may temporarily block the loop
# in case there is no data available
time.sleep(0.01)
def stop(self):
self.keepRunning = False
self.wait()
Finally, the main window, from which we can start or stop the serial communication:
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
central = QWidget()
self.setCentralWidget(central)
self.startButton = QPushButton('Start')
self.stopButton = QPushButton('Stop', enabled=False)
self.serialViewer = SerialViewer(32)
layout = QGridLayout(central)
layout.addWidget(self.startButton)
layout.addWidget(self.stopButton, 0, 1)
layout.addWidget(self.serialViewer, 1, 0, 1, 2)
self.serialThread = SerialThread()
self.startButton.clicked.connect(self.start)
self.stopButton.clicked.connect(self.serialThread.stop)
self.serialThread.dataReceived.connect(
self.serialViewer.updateData)
self.serialThread.finished.connect(self.stopped)
def start(self):
self.startButton.setEnabled(False)
self.stopButton.setEnabled(True)
self.serialThread.start()
def stopped(self):
self.startButton.setEnabled(True)
self.stopButton.setEnabled(False)
Notes:
- Qt already provides an asynchronous class for serial communication that already supports signals, QSerialPort;
- the above codes use the old enum syntax, for Qt6 you need the full enum names (
Qt.GlobalColor.red
,Qt.AlignmentFlag.AlignCenter
, etc); - you will probably need a further check for the serial connection before starting the while loop in
run()
;
Answered By - musicamante
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.