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 argumentsplineseries
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!
-
@Gojir4 Hi, thanks for having a look. As far as I know the class was created to just call
QmlMqttSubscription* QmlMqttClient::subscribe
somehow I thinktempSubscription = 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?