1

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

Community
  • 1
  • 1
daGriggs
  • 21
  • 1
  • 8
  • You should set all context properties before you load the QML file, but that is beside the problem at hand. Also, what is PI_THREAD? – rubenvb Jan 06 '17 at 07:29
  • Thanks, will do. I'm building on a Raspberry Pi 3. PI_THREAD is native to wiringPi library, from their site: "a simplified interface to the Linux implementation of Posix threads, as well as a (simplified) mechanisms to access mutex’s (Mutual exclusions)" – daGriggs Jan 06 '17 at 07:36
  • If at all possible, I would suggest to use QThreads instead of anything else when writing a Qt application if at all possible. Of course, whatever you're doing might require the other library's constructs... – rubenvb Jan 06 '17 at 07:39
  • Welp, that got rid of the "not defined" errors!! Now to play more with QML (I'm still very new to it) to get the data refreshing on screen. I'll look into QThreads. Since the function I call in my PI_THREAD does not utilize the RPi3 GPIO, I imagine a generic Qt thread would work just fine. – daGriggs Jan 06 '17 at 07:39

1 Answers1

1

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.

rubenvb
  • 74,642
  • 33
  • 187
  • 332
  • (3) shouldn't be a problem – Kevin Krammer Jan 06 '17 at 10:15
  • 1 and 2 helped solve my problem. I'm still stuck on how to use another thread to call `myData.publishPressure` though. Any help with that is appreciated. I tried using QTimer but it's much too slow, only gives 5-6Hz refresh rate on screen and is terribly inefficient, slowing down GUI performance a lot. So it looks like some kind of thread in C++ will be my solution. – daGriggs Jan 06 '17 at 22:44
  • @daGriggs: would inverting the updates work? As in: periodically refresh the value from QML instead of signaling from C++. – rubenvb Jan 07 '17 at 02:12
  • Sorry, to clarify, I used a QTimer in QML to refresh the value by calling `myData::publishPressure` at regular intervals. I would expect that counts as "periodically refresh the value from QML" or did you have another way of doing that in mind? I can only think of using QTimer in QML or setting a QThread on the task in C++, but I don't know how to span across threads with data from the same class object. Figuring that out is the plan of attack from here on unless someone gives me a better choice haha. – daGriggs Jan 07 '17 at 04:17
  • In my mind I'm committed to somehow regularly calling the `emit pressureChanged` line from either C++ or QML. BUT if there is a better way to go other than Q_PROPERTY have at it, I'm clueless. I tried connecting signals/slots, but couldn't wrap my head around all of it. – daGriggs Jan 07 '17 at 04:20
  • I ended up using `QFuture` & `QConcurrent` object in `main.cpp` dedicated to `emit pressureChanged` and `emit positionChanged` every 150ms (70ms intervals was fastest before GUI started showing performance issues so I gave myself a buffer). – daGriggs Jan 10 '17 at 14:50