Sending C++ signals to QML

2.2k views Asked by At

I have an int available via WiringPiI2C from an ADC at a max rate of 680 times per second. I'd like to sample and forward that value at regular intervals to various GUI objects, at 200-500Hz. I've tried a couple strategies for getting C++ data types into QML, and I always seem to fall just short.

I'm close to success with a custom Handler class and Q_PROPERTY macro, but the value only appears once; it does not update on screen. I can call the data using myData class all day in QML(console.log) or directly from the acquisition function in C++ (qDebug) and it updates flawlessly - I can watch the value as the ADC's analog input voltage varies ever-so-slightly. Every time I run the program, the single frozen value that shows on my screen is different than the last, thus real data must be making it to the screen. But why doesn't it update?

Do I somehow have to run the emit pressureChanged line and point the AppEngine engine.rootContext()->setContextProperty("myData",myData.data()); with the same instance of DataHandler? How would I do this?

UPDATE Indeed, the emit line and AppEngine pointer must be within the same instance of DataHandler. But I'm failing to see how I can do both with one instance. Seems something is always out of scope. I tried running emit via a QTimer in main.qml and it works, but it performs terribly. I need much faster refresh rate than 5-6Hz and it slowed down the rest of the GUI functions a lot. Any help getting myData class's pressureChanged signal sent out to QML at 60+ Hz?

Application output

qrc:/main.qml:31: ReferenceError: myData is not defined

qrc:/main.qml:31: ReferenceError: myData is not defined

I'm sampling, why isn't anyone getting this???

I'm sampling, why isn't anyone getting this???

25771

I'm sampling, why isn't anyone getting this???

25686

I'm sampling, why isn't anyone getting this???

25752

I'm sampling, why isn't anyone getting this???

qml: 25763                    <--- this is a manual button push on screen

I'm sampling, why isn't anyone getting this???

qml: 25702                    <--- this is a manual button push on screen

I'm sampling, why isn't anyone getting this???

25751

Why does QML allow me to use myData to send data to console, but not allow me to use myData as a property to manipulate QML objects?

Is there a much easier way to get simple data types (in my case I will have two integers) into QML constantly updating various text objects on screen?

This post came close to helping me understand what's going on, and I suspect my problem is closely related to what's said there: that is, somehow my binding isn't valid and thus only calls the function DataHandler::getPressure 1 time.

I tried following this tutorial, but it's a different situation (creating a class object in QML from C++, all I want is to move 1 data type to QML), so I wasn't capable enough to apply it to my problem very well...

I've tried for days now... 3 ways of instantiating myData, tried with/without QScopedPointer, tried various ways of accessing myData within QML... I'm going out of my mind, please help! I appeal to the Gods of StackOverflow, for my stack doth truly overflow with ignorance...

datahandler.h

#ifndef DATAHANDLER_H
#define DATAHANDLER_H

#include <QObject>
#include <QPoint>
#include <QDebug>

class DataHandler : public QObject
{
    Q_OBJECT
    Q_PROPERTY(int pressure READ getPressure NOTIFY pressureChanged)

public:
    explicit DataHandler(QObject *parent = 0);

    void setupPressure();
    int getPressureSample();
    int getPressure();
    void publishPressure();

signals:
    void pressureChanged();

};

#endif // DATAHANDLER_H

important bits of datahandler.cpp

#include <wiringPi.h>
#include <wiringPiI2C.h>
#include "datahandler.h"

#define SAMPLES 10

DataHandler::DataHandler(QObject *parent) : QObject(parent)
{

}

int DataHandler::getPressure() {
    int totalSum = 0;
    for (int i = 0; i < SAMPLES; i++){
        totalSum += getPressureSample();
        delay(5);    // Sampling at ~200Hz, the ADC itself maxes at 680Hz so don't sample faster than that.
    }
    qDebug() << "I'm sampling, why isn't anyone getting this update???";
    return totalSum/SAMPLES;
}

void DataHandler::publishPressure() {
    emit pressureChanged();
}

important bits of main.cpp

#include <QCursor>
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <wiringPi.h>
#include <wiringPiI2C.h>
#include "functions.h"
#include "datahandler.h"

PI_THREAD(updatePressure)
{
    DataHandler pressureData(new DataHandler);
    while (true){
        delay(500);
        pressureData.publishPressure();
        qDebug() << pressureData.getPressure();
    }
}

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);

    wiringPiSetup();
    DataHandler().setupPressure();

    app.setOverrideCursor( QCursor( Qt::BlankCursor ) );    //Hide the cursor, no one needs that thing showing!

    QScopedPointer<Functions> myFunctions(new Functions);
    QScopedPointer<DataHandler> myData(new DataHandler);

    QQmlApplicationEngine engine;
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

    engine.rootContext()->setContextProperty("myFunctions",myFunctions.data());
    engine.rootContext()->setContextProperty("myData",myData.data());

    piThreadCreate(updatePressure);

    return app.exec();
}

important bits of main.qml

import QtQuick 2.7
import QtQuick.Window 2.2
import QtQuick.Controls 2.0
import QtQuick.Controls.Styles 1.4
import QtQuick.Extras 1.4

//DECLARATIVE CONTENT
Window {
    id: myWindow
    visible: true
    width: 800
    height: 480
    title: qsTr("Hello World")
    Item {
        focus: true
        Keys.onEscapePressed: myWindow.close()
        Keys.onSpacePressed: console.log("HOW?")
    }

    MainForm {
        id: root
        anchors.fill: parent
        property var shiftArray: 0
        property var tumblerArray: noteSelector.array

        Text {
            id: testText
            z: 9
            anchors.fill: parent
            color: "#FF0000"
            text: myData.pressure
        }

        customPressureClick.onClicked: {
            console.log(myData.pressure)
            toggleCustomPressureState()
            attemptCustomPressure()
        }
    }
}

EXTRA INFO As you can see above, I created a PI_THREAD to constantly call the publishPressure function which is just my way of emitting the signal from outside the class. I suppose I could somehow use QTimer or some other method if that's what's screwing this up.

I used qDebug() to prove that the PI_THREAD is indeed calling publishPressure regularly, slowed it down to 500ms for sanity sake. I know the C++ data acquisition is successful because I've watched it crank out data to console at 500Hz. I know the emit function is reached, but maybe it is somehow not being executed?

I also found it very strange that QML bindings with Keys class worked fine in Window, but not in MainForm. I wonder if that somehow clues to the problem

1

There are 1 answers

6
rubenvb On BEST ANSWER

Some problems I spot right off the bat:

  1. When you call setupPressure, you do so on a temporary object. Even if it works, I doubt that's what you want to do.

  2. You create another two DataHandlers (one in main, which you correctly set (albeit too late) as context property for QML. The other you create in your PI_THREAD... thing, which you then manipulate. This is not the object QML has registered.

  3. You're also binding a string QML property (Text.text) to an int C++ Q_PROPERTY. I'm not sure this works correctly. In any case, I'd suggest trying to match types better.

Fix these problems, and you'll be on your way.