I'm creating a GUI for a raspberry pi interacting with several sensors. I need the GUI to display those values. For this, I used QT to create the main window/UI, then converted the result into python using pyuic5 to develop everything else on python, including a plot of the real-time current (might add a couple of other curves on the same plot or aditional plots). That being said, I'm kind of stuck on setting the text for a Qlabel I've been testing with. I think the best approach for changing that text would be with signals and slots (plus I'm sure I'll be needing that knowledge for other events).
Here is my UI after running pyuic5:
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(1075, 622)
self.centralWidget = QtWidgets.QWidget(MainWindow)
self.centralWidget.setObjectName("centralWidget")
self.gridLayout = QtWidgets.QGridLayout(self.centralWidget)
self.gridLayout.setContentsMargins(11, 11, 11, 11)
self.gridLayout.setSpacing(6)
self.gridLayout.setObjectName("gridLayout")
self.lblSolar = QtWidgets.QLabel(self.centralWidget)
self.lblSolar.setObjectName("lblSolar")
self.gridLayout.addWidget(self.lblSolar, 3, 1, 1, 1)
self.progressBar = QtWidgets.QProgressBar(self.centralWidget)
self.progressBar.setProperty("value", 24)
self.progressBar.setObjectName("progressBar")
self.gridLayout.addWidget(self.progressBar, 4, 0, 1, 1)
self.progressBar_2 = QtWidgets.QProgressBar(self.centralWidget)
self.progressBar_2.setProperty("value", 24)
self.progressBar_2.setObjectName("progressBar_2")
self.gridLayout.addWidget(self.progressBar_2, 4, 1, 1, 1)
self.btnSelectSol = QtWidgets.QPushButton(self.centralWidget)
self.btnSelectSol.setStyleSheet("background-color: rgb(114, 159, 207);")
self.btnSelectSol.setObjectName("btnSelectSol")
self.gridLayout.addWidget(self.btnSelectSol, 1, 3, 1, 1)
self.btnSelectBat = QtWidgets.QPushButton(self.centralWidget)
self.btnSelectBat.setStyleSheet("background-color: rgb(114, 159, 207);")
self.btnSelectBat.setObjectName("btnSelectBat")
self.gridLayout.addWidget(self.btnSelectBat, 2, 3, 1, 1)
self.lblBattery = QtWidgets.QLabel(self.centralWidget)
self.lblBattery.setObjectName("lblBattery")
self.gridLayout.addWidget(self.lblBattery, 3, 0, 1, 1)
self.btnON = QtWidgets.QPushButton(self.centralWidget)
self.btnON.setObjectName("btnON")
self.gridLayout.addWidget(self.btnON, 4, 3, 1, 1)
self.verticalLayout = QtWidgets.QVBoxLayout()
self.verticalLayout.setSizeConstraint(QtWidgets.QLayout.SetFixedSize)
self.verticalLayout.setContentsMargins(11, 11, 11, 11)
self.verticalLayout.setSpacing(6)
self.verticalLayout.setObjectName("verticalLayout")
self.lblCSTitle = QtWidgets.QLabel(self.centralWidget)
self.lblCSTitle.setAlignment(QtCore.Qt.AlignCenter)
self.lblCSTitle.setObjectName("lblCSTitle")
self.verticalLayout.addWidget(self.lblCSTitle)
self.lblCSStatus = QtWidgets.QLabel(self.centralWidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.lblCSStatus.sizePolicy().hasHeightForWidth())
self.lblCSStatus.setSizePolicy(sizePolicy)
self.lblCSStatus.setAutoFillBackground(False)
self.lblCSStatus.setAlignment(QtCore.Qt.AlignCenter)
self.lblCSStatus.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByMouse)
self.lblCSStatus.setObjectName("lblCSStatus")
self.verticalLayout.addWidget(self.lblCSStatus)
self.gridLayout.addLayout(self.verticalLayout, 0, 3, 1, 1)
self.widget = CustomPlot(self.centralWidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.widget.sizePolicy().hasHeightForWidth())
self.widget.setSizePolicy(sizePolicy)
self.widget.setObjectName("widget")
self.gridLayout.addWidget(self.widget, 0, 0, 3, 3)
MainWindow.setCentralWidget(self.centralWidget)
self.menuBar = QtWidgets.QMenuBar(MainWindow)
self.menuBar.setGeometry(QtCore.QRect(0, 0, 1075, 26))
self.menuBar.setObjectName("menuBar")
self.menuHelp = QtWidgets.QMenu(self.menuBar)
self.menuHelp.setObjectName("menuHelp")
self.menuData = QtWidgets.QMenu(self.menuBar)
self.menuData.setObjectName("menuData")
MainWindow.setMenuBar(self.menuBar)
self.mainToolBar = QtWidgets.QToolBar(MainWindow)
self.mainToolBar.setObjectName("mainToolBar")
MainWindow.addToolBar(QtCore.Qt.TopToolBarArea, self.mainToolBar)
self.statusBar = QtWidgets.QStatusBar(MainWindow)
self.statusBar.setObjectName("statusBar")
MainWindow.setStatusBar(self.statusBar)
self.toolBar = QtWidgets.QToolBar(MainWindow)
self.toolBar.setObjectName("toolBar")
MainWindow.addToolBar(QtCore.Qt.TopToolBarArea, self.toolBar)
self.actionGuide = QtWidgets.QAction(MainWindow)
self.actionGuide.setObjectName("actionGuide")
self.actionAbout = QtWidgets.QAction(MainWindow)
self.actionAbout.setObjectName("actionAbout")
self.actionExport = QtWidgets.QAction(MainWindow)
self.actionExport.setObjectName("actionExport")
self.menuHelp.addAction(self.actionGuide)
self.menuHelp.addAction(self.actionAbout)
self.menuData.addAction(self.actionExport)
self.menuBar.addAction(self.menuHelp.menuAction())
self.menuBar.addAction(self.menuData.menuAction())
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "Power System Monitor"))
self.lblSolar.setText(_translate("MainWindow", "Solar Panel"))
self.btnSelectSol.setText(_translate("MainWindow", "Power from Solar Panel"))
self.btnSelectBat.setText(_translate("MainWindow", "Power from Battery"))
self.lblBattery.setText(_translate("MainWindow", "Battery"))
self.btnON.setText(_translate("MainWindow", "EXIT"))
self.lblCSTitle.setText(_translate("MainWindow", "<html><head/><body><p><span style=\" font-size:16pt; font-weight:600;\">Pi Charge System</span></p></body></html>"))
self.lblCSStatus.setText(_translate("MainWindow", "TextLabel"))
self.menuHelp.setTitle(_translate("MainWindow", "Help"))
self.menuData.setTitle(_translate("MainWindow", "Data"))
self.toolBar.setWindowTitle(_translate("MainWindow", "toolBar"))
self.actionGuide.setText(_translate("MainWindow", "Guide"))
self.actionAbout.setText(_translate("MainWindow", "About"))
self.actionExport.setText(_translate("MainWindow", "Export"))
from Plotter import CustomPlot
here's my Plotter.py file:
import sys
import os
os.environ['PYQTGRAPH_QT_LIB']='PyQt5' #force pyqtgraph to use pyqt5
import pyqtgraph as pg
import PyQt5
from pyqtgraph.Qt import QtCore, QtGui
#import numpy as np #np is used for testing plot curves
#from ina219 import INA219, DeviceRangeError #sensory library
from time import sleep
from PyQt5.QtCore import Qt, pyqtSignal
from random import randrange
#SHUNT_OHMS = 0.1
#MAX_EXPECTED_AMPS = 1.0 #2.0
#ina = INA219(SHUNT_OHMS, MAX_EXPECTED_AMPS)
#ina.configure(ina.RANGE_16V)
class CustomPlot(pg.GraphicsWindow):
pg.setConfigOption('background', 'w')
pg.setConfigOption('foreground', 'k')
ptr1 = 0
chargeflag = "start"
#Added a signal
changedValue = pyqtSignal(str)
def __init__(self, parent=None, **kargs):
global chargeflag #variable to emit signal on change
pg.GraphicsWindow.__init__(self, **kargs)
#self.setParent(parent)
p1 = self.addPlot(labels = {'left':'Current (mA)', 'bottom':'Time(secs)'}, xRange=[0,5])
p1.showGrid(x=True, y=True)
p1.setYRange(3.4,4.6)
p1.addLegend()
#self.data1 = np.random.normal(size=10)
#print(self.data1)
self.data1 = []
for n in range(10):
#self.data1.append(ina.current()) #get actual data1 from sensor
self.data1.append(randrange(3,5))
sleep(1)
#print(self.data1)
#print(ina.current())
#self.data1 = ina.voltage
#self.data2 = np.random.normal(size=10)
self.curve1 = p1.plot(self.data1, pen='r', width=25, name='Battery')
#self.curve2 = p1.plot(self.data2, pen=(2,3))
timer = pg.QtCore.QTimer(self)
timer.timeout.connect(self.update)
#timer.timeout.connect(ina.current)
timer.start(1000) # number of seconds (every 1000) for next update
#setting a connection between chargeflag change and on_changed_value function
self.chargeflag.valueChanged.connect(self.on_changed_value)
def update(self):
#global chargeflag
self.data1[:-1] = self.data1[1:] # shift data in the array one sample left (see also: np.roll)
#self.data1[-1] = np.random.normal()
#self.data1[-1] = ina.current() #actual value from sensor
self.data1[-1] = randrange(3.5,4.5)
if self.data1[-1] < 4: #if that should send signal to update Qlabel
self.chargeflag = "solar"
else:
self.chargeflag = 'battery'
print(self.chargeflag)
self.ptr1 += 1
self.curve1.setData(self.data1)
self.curve1.setPos(self.ptr1, 0)
#self.data2[:-1] = self.data2[1:] # shift data in the array one sample left
# (see also: np.roll)
#self.data2[-1] = np.random.normal()
#self.curve2.setData(self.data2)
#self.curve2.setPos(self.ptr1,0)
self.changeflag.changedValue.connect(self.on_changed_value)
#function for other objects from other classes to listen to
def on_changed_value(self, value):
self.changedValue.emit(value)
if __name__ == '__main__':
w = CustomPlot()
w.show()
QtGui.QApplication.instance().exec_()
and lastly my main.py file:
import PyQt5
from PyQt5.QtWidgets import *
import sys
import Powdesign
from PyQt5.QtCore import QCoreApplication, pyqtSlot
from Plotter import CustomPlot
from time import sleep
class Powapp(QMainWindow, Powdesign.Ui_MainWindow):
def __init__(self):
super(self.__class__, self).__init__()
self.setupUi(self)
self.btnON.clicked.connect(QCoreApplication.instance().quit)
self.lblCSStatus.setText("starting value")
def get_chargeflag_value(self, value):
self.lblCSStatus.setText(value)
def make_connection(self, plot_object):
plot_object.changedValue.connect(self.get_flag_value)
@pyqtSlot(str)
def get_flag_value(self, val):
self.lblCSStatus.setText(val)
def main():
app = QApplication(sys.argv)
form = Powapp()
form.show()
form.make_connection(w)
sys.exit(app.exec())
if __name__ == '__main__':
main()
for my signal and slot implementation I was basing on the following example: https://blog.manash.me/quick-pyqt5-1-signal-and-slot-example-in-pyqt5-bf502ccaf11d
I created the variable changeflag on Plotter.py which will be changing from "solar" to "battery" according to the values from the sensor which is the text I would like to be displaying on my Qlabel, but I'm pretty sure that's my mistake. I think I'm supposed to be creating some sort of Qobject that would emit the signal, but it's not clear for me if that's allowed or how to do it. Thanks a lot in advance!
There are several errors in your code:
The variable chargeflag is a string and has no valueChanged method.
valueChanged is an attribute of the CustomPlot class and if you are using it inside the class you should do it using the self
instance: self.changedValue.emit(some_string)
This is not an error but I think it is not necessary, you do not need to create a slot to emit a signal, you can do it anywhere in your class.
It is not necessary to create a make_connection method, besides that you are passing w which has never been created, when using Qt Designer this becomes a member of the class: self.widget = CustomPlot(self.centralWidget)
, so you can connect it in the Powapp class: self.widget.changedValue.connect(self.get_flag_value)
.
All of the above is implemented in the next class, I have modified other parts of your code but they are of little importance, besides the randrange()
function requires integer and non-floating values as input, so I changed the randrange()
function by uniform()
from the same random module, so it changes the import: from random import randrange
to from random import uniform
Plotter.py
class CustomPlot(pg.GraphicsWindow):
pg.setConfigOption('background', 'w')
pg.setConfigOption('foreground', 'k')
ptr1 = 0
#Added a signal
changedValue = pyqtSignal(str)
def __init__(self, parent=None, **kargs):
pg.GraphicsWindow.__init__(self, **kargs)
#self.setParent(parent)
p1 = self.addPlot(labels = {'left':'Current (mA)', 'bottom':'Time(secs)'}, xRange=[0,5])
p1.showGrid(x=True, y=True)
p1.setYRange(3.4,4.6)
p1.addLegend()
#self.data1 = np.random.normal(size=10)
#print(self.data1)
self.data1 = [uniform(3,5) for _ in range(10)]
# self.data1 = [ina.current() for _ in range(10)]
#print(self.data1)
#print(ina.current())
#self.data1 = ina.voltage
#self.data2 = np.random.normal(size=10)
self.curve1 = p1.plot(self.data1, pen='r', width=25, name='Battery')
#self.curve2 = p1.plot(self.data2, pen=(2,3))
timer = pg.QtCore.QTimer(self)
timer.timeout.connect(self.update)
#timer.timeout.connect(ina.current)
timer.start(1000) # number of seconds (every 1000) for next update
#setting a connection between chargeflag change and on_changed_value function
def update(self):
#global chargeflag
self.data1[:-1] = self.data1[1:] # shift data in the array one sample left (see also: np.roll)
#self.data1[-1] = np.random.normal()
#self.data1[-1] = ina.current() #actual value from sensor
self.data1[-1] = uniform(3.5,4.5)
chargeflag = "solar" if self.data1[-1] < 4 else "battery"
self.ptr1 += 1
self.curve1.setData(self.data1)
self.curve1.setPos(self.ptr1, 0)
#self.data2[:-1] = self.data2[1:] # shift data in the array one sample left
# (see also: np.roll)
#self.data2[-1] = np.random.normal()
#self.curve2.setData(self.data2)
#self.curve2.setPos(self.ptr1,0)
self.changedValue.emit(chargeflag)
if __name__ == '__main__':
w = CustomPlot()
w.show()
QtGui.QApplication.instance().exec_()
main.py
class Powapp(QMainWindow, Ui_MainWindow):
def __init__(self):
super(self.__class__, self).__init__()
self.setupUi(self)
self.btnON.clicked.connect(QCoreApplication.instance().quit)
self.lblCSStatus.setText("starting value")
self.widget.changedValue.connect(self.get_flag_value)
def get_chargeflag_value(self, value):
self.lblCSStatus.setText(value)
@pyqtSlot(str)
def get_flag_value(self, val):
self.lblCSStatus.setText(val)
def main():
app = QApplication(sys.argv)
form = Powapp()
form.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()