Property of object is not a function



  • I've been working on a MQTT Dashboard, the idea is to cross compile it to run on a Raspberry Pi (topic for another time perhaps) but I've been running into some problems. I am familiar with C++ but this is the first time I try my hand at Qt and qml for a project, I've also been reading documentation and going through some tutorials but I think this case is too specific.

    First, a bit of context: My code is heavily based on both the QT MQTT quick subscription example: https://doc.qt.io/QtMQTT/qtmqtt-quicksubscription-example.html and the Oscilloscope example: https://doc.qt.io/qt-5/qtcharts-qmloscilloscope-example.html. My application is split in pages in a swipe view.

    My main idea was to add the missing update function from the Oscilloscope example's datasource.h and datasource.cpp to my qtmqttclient.h and qtmqttclient.cpp (see below) with some modifications because I am not generating my data, I get it from the mqtt message payload. The code compiles and I am able to run the application and receive messages from my MQTT broker (a Raspberry Pi) but I get this error message every time the update function should be called to replace the series in my Spline Chart:

    Message Handling
    Received 22.000000
    Message Handling
    Received 23.000000
    Message Handling
    Received 24.000000
    qrc:/main.qml:82: TypeError: Property 'update' of object [object Object] is not a function
    

    For reference, here are my source code files (please point out any noob mistakes as it helps speed up my learning process):

    main.cpp:

    #include "qmlmqttclient.h"
    //#include <QGuiApplication>
    #include <QtWidgets/QApplication>
    #include <QQmlApplicationEngine>
    #include <QLoggingCategory>
    
    int main(int argc, char *argv[])
    {
        qputenv("QT_IM_MODULE", QByteArray("qtvirtualkeyboard"));
    
        QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    
        //QGuiApplication app(argc, argv);
        QApplication app(argc, argv);
    
        qmlRegisterType<QmlMqttClient>("MqttClient", 1, 0, "MqttClient");
        qmlRegisterUncreatableType<QmlMqttSubscription>("MqttClient", 1, 0, "MqttSubscription", QLatin1String("Subscriptions are read-only"));
    
        QQmlApplicationEngine engine;
        const QUrl url(QStringLiteral("qrc:/main.qml"));
    //    QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
    //                     &app, [url](QObject *obj, const QUrl &objUrl) {
    //        if (!obj && url == objUrl)
    //            QCoreApplication::exit(-1);
    //    }, Qt::QueuedConnection);
        engine.load(url);
        if (engine.rootObjects().isEmpty())
                return -1;
    
        return app.exec();
    }
    

    I commented out the QGuiApplication, I read that QtCharts needs QApplication.
    Here are my qtmqttclient.h and qtmqttclient.cpp files:

    /****************************************************************************
    **
    ** Copyright (C) 2017 The Qt Company Ltd.
    ** Contact: https://www.qt.io/licensing/
    **
    ** This file is part of the examples of the Qt Toolkit.
    **
    ** $QT_BEGIN_LICENSE:BSD$
    ** Commercial License Usage
    ** Licensees holding valid commercial Qt licenses may use this file in
    ** accordance with the commercial license agreement provided with the
    ** Software or, alternatively, in accordance with the terms contained in
    ** a written agreement between you and The Qt Company. For licensing terms
    ** and conditions see https://www.qt.io/terms-conditions. For further
    ** information use the contact form at https://www.qt.io/contact-us.
    **
    ** BSD License Usage
    ** Alternatively, you may use this file under the terms of the BSD license
    ** as follows:
    **
    ** "Redistribution and use in source and binary forms, with or without
    ** modification, are permitted provided that the following conditions are
    ** met:
    **   * Redistributions of source code must retain the above copyright
    **     notice, this list of conditions and the following disclaimer.
    **   * Redistributions in binary form must reproduce the above copyright
    **     notice, this list of conditions and the following disclaimer in
    **     the documentation and/or other materials provided with the
    **     distribution.
    **   * Neither the name of The Qt Company Ltd nor the names of its
    **     contributors may be used to endorse or promote products derived
    **     from this software without specific prior written permission.
    **
    **
    ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
    ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
    ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
    ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
    ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
    ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
    ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
    **
    ** $QT_END_LICENSE$
    **
    ****************************************************************************/
    
    #ifndef QMLMQTTCLIENT_H
    #define QMLMQTTCLIENT_H
    
    #include <QtCore/QMap>
    #include <QtCore/QObject>
    #include <QtMqtt/QMqttClient>
    #include <QtMqtt/QMqttSubscription>
    #include <QtCharts/QAbstractSeries>
    #include <QDateTime>
    
    class QmlMqttClient;
    
    QT_CHARTS_USE_NAMESPACE
    
    class QmlMqttSubscription : public QObject
    {
        Q_OBJECT
        Q_PROPERTY(QMqttTopicFilter topic MEMBER m_topic NOTIFY topicChanged)
    public:
        QmlMqttSubscription(QMqttSubscription *s, QmlMqttClient *c, QObject *parent = 0);
        ~QmlMqttSubscription();
    
    Q_SIGNALS:
        void topicChanged(QString);
        void messageReceived(const QString &msg);
    
    public slots:
        void handleMessage(const QMqttMessage &qmsg);
        Q_INVOKABLE void update(QAbstractSeries *series);
    
    private:
        Q_DISABLE_COPY(QmlMqttSubscription)
        QMqttSubscription *sub;
        QmlMqttClient *client;
        QMqttTopicFilter m_topic;
        //QDateTime current = QDateTime::currentDateTime();
        //qint64 timeRef = current.toSecsSinceEpoch();
        //qreal x = timeRef;
        qreal x = 0.0;
        QVector<QPointF> points;
        uint8_t samples = 0;
    };
    
    class QmlMqttClient : public QMqttClient
    {
        Q_OBJECT
    public:
        QmlMqttClient(QObject *parent = nullptr);
    
        Q_INVOKABLE QmlMqttSubscription *subscribe(const QString &topic);
        Q_INVOKABLE int publish(const QString &topic, const QString &message, int qos = 0, bool retain = false);
    private:
        Q_DISABLE_COPY(QmlMqttClient)
    };
    
    #endif // QMLMQTTCLIENT_H
    

    In this file I even made the update function invokable but it seems that it didn't do the trick...

    /****************************************************************************
    **
    ** Copyright (C) 2017 The Qt Company Ltd.
    ** Contact: https://www.qt.io/licensing/
    **
    ** This file is part of the examples of the Qt Toolkit.
    **
    ** $QT_BEGIN_LICENSE:BSD$
    ** Commercial License Usage
    ** Licensees holding valid commercial Qt licenses may use this file in
    ** accordance with the commercial license agreement provided with the
    ** Software or, alternatively, in accordance with the terms contained in
    ** a written agreement between you and The Qt Company. For licensing terms
    ** and conditions see https://www.qt.io/terms-conditions. For further
    ** information use the contact form at https://www.qt.io/contact-us.
    **
    ** BSD License Usage
    ** Alternatively, you may use this file under the terms of the BSD license
    ** as follows:
    **
    ** "Redistribution and use in source and binary forms, with or without
    ** modification, are permitted provided that the following conditions are
    ** met:
    **   * Redistributions of source code must retain the above copyright
    **     notice, this list of conditions and the following disclaimer.
    **   * Redistributions in binary form must reproduce the above copyright
    **     notice, this list of conditions and the following disclaimer in
    **     the documentation and/or other materials provided with the
    **     distribution.
    **   * Neither the name of The Qt Company Ltd nor the names of its
    **     contributors may be used to endorse or promote products derived
    **     from this software without specific prior written permission.
    **
    **
    ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
    ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
    ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
    ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
    ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
    ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
    ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
    **
    ** $QT_END_LICENSE$
    **
    ****************************************************************************/
    
    #include "qmlmqttclient.h"
    #include <QDebug>
    #include <QtCharts/QXYSeries>
    #include <QtCore/QtMath>
    
    QT_CHARTS_USE_NAMESPACE
    
    QmlMqttClient::QmlMqttClient(QObject *parent)
        : QMqttClient(parent)
    {
    }
    
    QmlMqttSubscription* QmlMqttClient::subscribe(const QString &topic)
    {
        auto sub = QMqttClient::subscribe(topic, 0);
        auto result = new QmlMqttSubscription(sub, this);
        return result;
    }
    
    QmlMqttSubscription::QmlMqttSubscription(QMqttSubscription *s, QmlMqttClient *c, QObject *parent)
        : QObject(parent),
          sub(s),
          client(c)
    {
        connect(sub, &QMqttSubscription::messageReceived, this, &QmlMqttSubscription::handleMessage);
        m_topic = sub->topic();
    }
    
    QmlMqttSubscription::~QmlMqttSubscription()
    {
    }
    
    void QmlMqttSubscription::update(QAbstractSeries *series)
    {
        qDebug("Updating");
        if (series) {
            QXYSeries *xySeries = static_cast<QXYSeries *>(series);
            qDebug("Series: %s",xySeries->name().toLocal8Bit().data());
            xySeries->replace(points);
            points.clear();
        }
    }
    
    void QmlMqttSubscription::handleMessage(const QMqttMessage &qmsg)
    {
        qDebug()<<"Message Handling";
        emit messageReceived(qmsg.payload());
        x = x + 60;
        QString y_temp = qmsg.payload();
        qreal y = y_temp.toDouble();
        points.append(QPointF(x,y));
        qDebug("Received %f",y);
    }
    
    int QmlMqttClient::publish(const QString &topic, const QString &message, int qos, bool retain)
    {
        auto result = QMqttClient::publish(QMqttTopicName(topic), message.toUtf8(), qos, retain);
        return result;
    }
    

    and here are my qml files and ui :
    main.qml:

    import QtQuick 2.12
    import QtQuick.Controls 2.5
    import QtQuick.VirtualKeyboard 2.4
    import MqttClient 1.0
    import QtQuick.Window 2.2
    import QtQuick.Layouts 1.1
    import QtCharts 2.3
    
    ApplicationWindow {
        id: window
        visible: true
        width: 480
        height: 320
        title: qsTr("MQTT Dashboard")
    
        property var tempSubscription: 0
        property var humtySubscription: 0
        property var heatIdxSubscription: 0
        property real time: 0.0
    
        MqttClient {
            id: client
            clientId: "Qt_MQTT_Dashboard"
            username: "pi"
            password: "D3vi@ntart"
            cleanSession: true
        }
    
        SwipeView {
            id: swipeView
            anchors.fill: parent
            currentIndex: tabBar.currentIndex
    
            Page1Form {
                connectButton.onClicked: {
                    client.hostname = hostnameField.text
                    client.port = portField.text
                    if (client.state === MqttClient.Connected) {
                        client.disconnectFromHost()
                        tempSubscription.destroy()
                        tempSubscription = 0
                    } else {
                        client.connectToHost()
                    }
                }
    
                function stateToString(value) {
                    if (value === 0)
                        return "Disconnected"
                    else if (value === 1)
                        return "Connecting"
                    else if (value === 2)
                        return "Connected"
                    else
                        return "Unknown"
                }
    
                status.text: "Status:" + stateToString(client.state)
    
            }
    
            Page2Form {
                function tempMessage(payload)
                {
                    messageModel.insert(0, {"payload" : payload})
    
                    if (messageModel.count >= 5){
                        messageModel.remove(4)
                    }
                }
                subscribebutton.onClicked: {
                    tempSubscription = client.subscribe(qsTr("/mqtt/temperature"))
                    tempSubscription.messageReceived.connect(tempMessage)
                }
    
                Timer {
                    id: refreshTimer
                    interval: 180000 // every 3 mins
                    running: true
                    repeat: true
                    onTriggered: {
                        MqttSubscription.update(Page2Form.splineseries);
                        //dataSource.update(chartView.series(0));
                        //dataSource.update(chartView.series(1));
                    }
                }
            }
    
            Page3Form{
    
            }
        }
    
        footer: TabBar {
            id: tabBar
            currentIndex: swipeView.currentIndex
    
            TabButton {
                text: qsTr("Broker setup")
            }
            TabButton {
                text: qsTr("View Data")
            }
            TabButton {
                text: qsTr("Page 3")
            }
        }
    
        InputPanel {
            id: inputPanel
            z: 99
            x: 0
            y: window.height
            width: window.width
    
            states: State {
                name: "visible"
                when: inputPanel.active
                PropertyChanges {
                    target: inputPanel
                    y: window.height - inputPanel.height
                }
            }
            transitions: Transition {
                from: ""
                to: "visible"
                reversible: true
                ParallelAnimation {
                    NumberAnimation {
                        properties: "y"
                        duration: 250
                        easing.type: Easing.InOutQuad
                    }
                }
            }
        }
    }
    

    The Page2Form part is the one with problems, MqttSubscription.update(Page2Form.splineseries); is the referenced line, as you can see I left the similar function call from the oscilloscope example there. I also tried to call the function with argument splineseries since that is the name of the exported alias property I used in the ui file below:

    Page1Form.ui.qml:

    import QtQuick 2.12
    import QtQuick.Controls 2.5
    import QtQuick.Layouts 1.1
    import MqttClient 1.0
    import QtCharts 2.3
    
    Page {
        width: 480
        height: 320
        property alias messageView: messageView
        property alias messageModel: messageModel
        property alias subscribebutton: subscribebutton
        property alias splineseries: splineseries
        property alias spline: spline
    
        ChartView {
            enabled: client.state === MqttClient.Connected
            id: spline
            antialiasing: true
            theme: ChartView.ChartThemeDark
            title: "Temperature"
            x: 12
            y: 12
            width: 332
            height: 235
            SplineSeries {
                name: "tempSeries"
                id: splineseries
                useOpenGL: true
            }
        }
    
        ListModel {
            id: messageModel
        }
    
        ListView {
            id: messageView
            x: 365
            y: 66
            model: messageModel
            height: 181
            width: 99
            Layout.columnSpan: 2
            Layout.fillHeight: true
            Layout.fillWidth: true
            clip: true
            delegate: Rectangle {
                width: messageView.width
                height: 30
                color: index % 2 ? "#DDDDDD" : "#888888"
                radius: 5
                Text {
                    text: payload
                    anchors.centerIn: parent
                }
            }
        }
    
        Button {
            id: subscribebutton
            x: 382
            y: 12
            text: qsTr("Start")
            enabled: client.state === MqttClient.Connected
        }
    }
    
    

    Now, to the best of my knowledge the only difference between my Subscription class and the Oscilloscope example's Datasource class is that it was registered with qmlRegisterUncreatableType in main.cpp and somehow that may prevent me from calling the update function...

    Any pointers or references will be greatly appreciated!



  • @lopeztel Hi,

    I don't think update is the issue.
    What MqttSubscription refer to ? I don't see any place in your code where an instance is created whith this name.
    Isn't that the problem ?



  • @Gojir4 Hi, thanks for having a look. As far as I know the class was created to just call QmlMqttSubscription* QmlMqttClient::subscribe somehow I think tempSubscription = client.subscribe(qsTr("/mqtt/temperature")) creates a subscription without creating an object of the class.

    QmlMqttSubscription* QmlMqttClient::subscribe(const QString &topic)
    {
        auto sub = QMqttClient::subscribe(topic, 0);
        auto result = new QmlMqttSubscription(sub, this);
        return result;
    }
    
    QmlMqttSubscription::QmlMqttSubscription(QMqttSubscription *s, QmlMqttClient *c, QObject *parent)
        : QObject(parent),
          sub(s),
          client(c)
    {
    

    and it is definitely working, adding to my overall confusion as to why update can't be called ....
    Also, wouldn't defining the type as uncreatable prevent me from creating an object of the class in the traditional way?



  • @lopeztel said in Property of object is not a function:

    tempSubscription = client.subscribe(qsTr("/mqtt/temperature"))

    Ok, I didn't see it sorry. So should not it be :

    tempSubscription.update(Page2Form.splineseries); 
    


  • @Gojir4 shoot, you're right, the subscribe function returns the actual subscription object! All along I had what I needed! Noob mistake, thanks a lot!
    This is what happens when you spend hours staring at the screen ...



  • @lopeztel said in Property of object is not a function:

    This is what happens when you spend hours staring at the screen ...

    Sometimes when you have had too much to think you need a break and coffee.