Search code examples
pythonpython-3.xpyqtpyqt5pyqtgraph

setting a Qlabel text according to a variable's value on another class


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!


Solution

  • There are several errors in your code:

    1. The variable chargeflag is a string and has no valueChanged method.

    2. 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)

    3. 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.

    4. 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()