Search code examples
pythonqmlpyside2

QML gauges not updating from Python


I'm still getting to grips with QT... I've made a python file and a QML file. the Python file updates the gauge's value from data it gets over UDP.

This only works once though... the first UDP packet comes in and updates the gauge, but when it gets the next packet,despite the value updating, the gauge itself does not.

QML

 CircularGauge {
        id: circularGauge
        x: 30
        y: 30
        value: itt1value
        minimumValue: 0
        maximumValue: 1200
        tickmarksVisible: false
        style: CircularGaugeStyle {
            maximumValueAngle: 400
            minimumValueAngle: 90
        }
    }

Python:

def configureApplication():

    # Set up the application window
    app = QGuiApplication(sys.argv)
    view = QQuickView()
    view.setResizeMode(QQuickView.SizeRootObjectToView)
    view.setTitle("my title")

    # Load the QML file
    qml_file = os.path.join(os.path.dirname(__file__), "maingui.qml")
    view.setSource(QUrl.fromLocalFile(os.path.abspath(qml_file)))

    # load the slots into the QML file
    view.rootContext().setContextProperty("itt1value", 0)


    t = threading.Thread(target=receivedata, args=(view,))
    t.start()

    # Show the window
    if view.status() == QQuickView.Error:
        sys.exit(-1)
    view.show()

    # execute and cleanup
    app.exec_()
    del view

In the threaded method receivedata() I get the data from UDP, process it, then send it to the gauge like so:

view.rootContext().setContextProperty("itt1value", itt)

receivedata() has a while loop in it with the above details, but the gauge only actually updates once. If I put a statement in the QML file to display itt1value, it always has the correct value, so do I need to put in a method to detect the change to this value and re-paint the gauge?

Edit: I was asked for the details of receivedata(), so I have attached it here:

def receivedata(view):
    print("Starting UDP server...")
    UDP_IP = "192.168.0.14"
    UDP_PORT = 49000
    sock = socket.socket(socket.AF_INET,  # Internet
                         socket.SOCK_DGRAM)  # UDP
    sock.bind((UDP_IP, UDP_PORT))
    olditt = 0
    loopruns = 0 # for debugging

    while True:
        rawstring = sock.recv(1024)
        hexarray = []

        #lots of irrelevent formatting here, result is int(value)


        itt = float(hextoint(value, olditt))
        olditt = itt

        itt = format(itt, '.3f')

        current = str(loopruns) # for debugging
        view.setTitle(current) # for debugging
        view.rootContext().setContextProperty("itt1value", itt)
        loopruns = loopruns + 1
        print(itt)


Solution

  • You have the following errors:

    • You cannot directly modify the GUI from another thread.

    • A value can be exported again with setContextProperty(), this will not change the previous value unless the QML is reloaded.

    • If you want "itt" to modify any value in QML it must be of compatible types, in this case the value of CircularGauge is "real" and therefore the type of data supported in python is float.

    Considering the above, I have created a QObject since it can notify changes through signals since it is thread-safe, and export the QObject making connections using Connections.

    main.py

    import os
    import random
    import sys
    import threading
    import time
    
    from PySide2.QtCore import QObject, QUrl, Signal
    from PySide2.QtGui import QGuiApplication
    from PySide2.QtQuick import QQuickView
    
    
    class Connections(QObject):
        titleChanged = Signal(str, arguments=["title"])
        valueChanged = Signal(float, arguments=["value"])
    
    
    def receivedata(connector):
        # configurations
    
        loopruns = 0
        while True:
            # other stuff
            time.sleep(0.1)
            itt = random.uniform(0.0, 1200.0)
            connector.valueChanged.emit(itt)
            connector.titleChanged.emit(str(loopruns))
            loopruns += 1
    
    
    def main(args):
        app = QGuiApplication(args)
        view = QQuickView(title="my title", resizeMode=QQuickView.SizeRootObjectToView)
        connector = Connections()
        connector.titleChanged.connect(view.setTitle)
        view.rootContext().setContextProperty("connector", connector)
        # Load the QML file
        qml_file = os.path.join(os.path.dirname(__file__), "maingui.qml")
        view.setSource(QUrl.fromLocalFile(os.path.abspath(qml_file)))
        # start thread
        threading.Thread(target=receivedata, args=(connector,)).start()
        # Show the window
        if view.status() == QQuickView.Error:
            return -1
        view.show()
        # execute and cleanup
        ret = app.exec_()
        del view
        return ret
    
    
    if __name__ == "__main__":
        sys.exit(main(sys.argv)) 
    

    maingui.qml

    import QtQml 2.13
    import QtQuick.Extras 1.4
    import QtQuick.Controls.Styles 1.4
    
    CircularGauge {
        id: circularGauge
        value: 100
        minimumValue: 0
        maximumValue: 1200
        tickmarksVisible: false
        style: CircularGaugeStyle {
            maximumValueAngle: 400
            minimumValueAngle: 90
        }
    
        Connections{
            target: connector
            onValueChanged: circularGauge.value = value
        }
    }