I'm creating a gauge cluster using the Qt tutorial. I've gone through all those steps and have the visual side complete, so now I'm on the coding side and completely green.
The setup is an Arduino sending data to a Raspberry Pi. I'm trying to take that data and send it to my QML to update the 'rpmFrame' in the QML. As an experiment, I have the Arduino just sending '800' and I can't get the gauge to show it in any capacity (the data comes in perfect no problem and I have assigned it to the double 'rpmDouble'.) I have with several attempts gotten incoming:: valueToQML to show 800 in the debug, but it never translates to the QML. I think my I just have a fundamental poor understanding of how all the variables work across the header etc.
main.qml
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Timeline 1.0
import incoming 1.0
Window {
width: 1280
height: 480
visible: true
color: "#FFFFFF"
SerialIncoming{
id:incoming
}
Rectangle {
id: rectangle
x: 0
y: -7
width: 1280
height: 487
color: "#ffffff"
}
Rectangle {
id: oil
x: 331
y: 52
width: 69
height: 47
color: "#ff0000"
}
Rectangle {
id: battery
x: 405
y: 52
width: 74
height: 47
color: "#ff0000"
}
Rectangle {
id: leftturn
x: 360
y: 105
width: 69
height: 47
color: "#15ff0d"
}
Rectangle {
id: hibeam
x: 807
y: 65
width: 69
height: 47
color: "#00caff"
}
Rectangle {
id: brake
x: 881
y: 64
width: 74
height: 47
color: "#ff0000"
}
Rectangle {
id: rightturn
x: 852
y: 117
width: 69
height: 47
color: "#15ff0d"
}
Rectangle {
id: oil1
x: 369
y: 386
width: 69
height: 47
color: "#ff0000"
}
Tachometer {
id: tachometer
x: 423
y: 27
rpmFrame: {incoming.valueToQML} // <-- this is where serial data goes
displaySpeed: "500"
scale: 0.9
}
}
incoming.cpp
incoming::incoming(QObject *parent) : QObject(parent) {
arduino = new QSerialPort(this);
serialBuffer="";
arduino->setPortName("ttyACM0");
arduino->setBaudRate (QSerialPort::Baud9600);
arduino->setDataBits (QSerialPort::Data8);
arduino->setParity(QSerialPort::NoParity);
arduino->setStopBits(QSerialPort::OneStop);
arduino->setFlowControl (QSerialPort::NoFlowControl);
arduino->open(QSerialPort::ReadOnly);
QObject::connect(arduino, SIGNAL(readyRead()), this, SLOT(readSerial()));
qDebug() << "KenDash v0.0";
}
void incoming::readSerial()
{
//Buffer to Filter Data
if(arduino->bytesAvailable()>0||arduino->waitForReadyRead(10))
{
QStringList bufferSplit = serialBuffer.split(",");
if(bufferSplit.length() < 5)
{
QByteArray serialData = arduino->readAll();
serialBuffer += QString::fromStdString(serialData.toStdString());
serialData.clear();
}else{
//Clean Data Output
serialBuffer="";
QString serialOutput = bufferSplit[1];
//Split the Clean Data To Sections
QStringList dataSplit = bufferSplit[1].split("/");
QString rpmOutput = dataSplit[0];
QString speedOutput = dataSplit[1];
//qDebug() << "RPM:" << rpmOutput << "Speed:" << speedOutput;
rpmDouble = rpmOutput.toDouble();
incoming *myinstance = new incoming;
QObject *object = myinstance;
object->setProperty("valueToQML", rpmDouble);
//Read Data in Debug Panel
qDebug() << "RPM(onheader2):" << incoming::valueToQML();
}
}
}
main.cpp
#include "incoming.h"
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QDebug>
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
qmlRegisterType<incoming>("incoming", 1, 0, "SerialIncoming");
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);
void incomingserial();
return app.exec();
}
incoming.h
#ifndef INCOMING_H
#define INCOMING_H
#pragma once
#include <QString>
#include <QSerialPort>
#include <QDebug>
#include <QObject>
#include <iostream>
void incomingserial();
class incoming : public QObject
{
Q_OBJECT
Q_PROPERTY(double valueToQML READ valueToQML WRITE setValuetoQML NOTIFY valueToQMLChanged)
public:
explicit incoming(QObject *parent = 0);
//~incoming();
double valueToQML() const
{ return rpmDouble;}
void setValuetoQML(double valueToQML)
{
_valueString = valueToQML;
emit valueToQMLChanged(valueToQML);
}
signals:
void valueToQMLChanged(double);
private slots:
void readSerial();
private:
double _valueString;
QSerialPort *arduino;
static const quint16 arduino_uno_vendor_id = 9025;
static const quint16 arduino_uno_product_id = 67;
QByteArray serialData;
QString serialBuffer;
QString parsed_data;
double rpmTachOutput;
double rpmDouble;
QString rpmOutput;
double rpmToDisplay();
};
#endif // INCOMING_H
I can't understand why you set your new incoming value to _valueString but you read the value from rpmDouble. Your QProperty read and write function should refer to the same variable so when something come from serial you should call:
this->setValuetoQML(rpmOutput.toDouble());
And inside setValuetoQML function assign new value like this:
rpmDouble = valueToQML;
Because you're returning rpmDouble when valueToQML() is called.
Related
i want to send the dynamic image to qml and display on Qt gui i read online image provider is way to solve this issue but i want any sample and simple application which does this.
You should create an image provider class where you load your image, this is a simplified example.
qmlimageprovider.h
#ifndef QMLIMAGEPROVIDER_H
#define QMLIMAGEPROVIDER_H
#include <QQuickImageProvider>
class QmlImageProvider : public QQuickImageProvider
{
public:
QmlImageProvider();
QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize) override;
void updateImage(QImage new_image);
private:
QImage image;
};
#endif // QMLIMAGEPROVIDER_H
qmlimageprovider.cpp
#include "qmlimageprovider.h"
QmlImageProvider::QmlImageProvider()
: QQuickImageProvider(QQuickImageProvider::Image)
{
}
QImage QmlImageProvider::requestImage(const QString &id, QSize *size, const QSize &requestedSize)
{
image.load("/home/ubuntu/music.jpg");
return image;
}
void QmlImageProvider::updateImage(QImage new_image)
{
image = new_image;
}
Then you should register this class to your QML engine.
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include "qmlimageprovider.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.addImageProvider(QLatin1String("imageprovider"),
new QmlImageProvider());
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);
return app.exec();
}
Finally you can use imageprovider in your qml page and don't forget to write image reload function because images will be cache and you need to reload in order to change them.
main.qml
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.3
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Connections {
target: imageprovider
onNewFrameReceived: image.reload();
}
Item {
id: name
width: parent.width
height: parent.height
Rectangle
{
id: progressBackground
height: parent.height
width: parent.width
Text {
id: text
text: qsTr("click to show")
color: "#c400c4"
}
MouseArea {
id: progressArea
anchors.fill: progressBackground
onClicked:
{
image.source = "image://imageprovider/cover"
}
}
}
Image {
id: image
anchors.left: text.right
source: ""
cache: false
function reload() {
var oldSource = source;
source = "";
source = oldSource;
console.log("reload")
}
}
}
}
You can call imageprovider update function to change the image.
I want to display a stream of images from a capture device in Qt3D (via a texture).
So I subclassed QPaintedTextureImage:
#include <Qt3DRender/QPaintedTextureImage>
#include <QPainter>
#include <QImage>
#include <QPaintDevice>
#include <opencv2/opencv.hpp>
class OpenCVCaptureImage : public Qt3DRender::QPaintedTextureImage
{
public:
explicit OpenCVCaptureImage(Qt3DCore::QNode *parent = nullptr);
void paint(QPainter *painter) override;
private:
cv::VideoCapture cap_;
};
OpenCVCaptureImage::OpenCVCaptureImage(Qt3DCore::QNode *parent)
: Qt3DRender::QPaintedTextureImage(parent),
cap_(0)
{
}
void OpenCVCaptureImage::paint(QPainter *painter)
{
cv::Mat frame;
cap_ >> frame;
QImage im(static_cast<uchar *>(frame.data), frame.cols, frame.rows, int(frame.step), QImage::Format_RGB888);
if(im.isNull()) return;
setSize(im.size());
//int w = painter->device()->width();
//int h = painter->device()->height();
painter->drawImage(0, 0, im);
}
According to documentation:
A QPaintedTextureImage must be subclassed and the virtual paint() function implemented. Each time update() is called on the QPaintedTextureImage, the paint() function is invoked and the resulting image is uploaded.
So I add a Timer to my scene tree, and in the onTriggered slot I call capImage.update(), but something goes wrong at runtime:
2019-01-31 22:48:54.865282+0100 Qt3DOpenCV[50001:4569220] qrc:/MyScene.qml:57: TypeError: Property 'update' of object Qt3DRender::QPaintedTextureImage(0x106ef6bf0) is not a function
main.cpp:
#include <QGuiApplication>
#include <Qt3DQuickExtras/qt3dquickwindow.h>
#include "opencvcaptureimage.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
qmlRegisterType<OpenCVCaptureImage>("OpenCVCaptureImage", 1, 0, "OpenCVCaptureImage");
QGuiApplication app(argc, argv);
Qt3DExtras::Quick::Qt3DQuickWindow view;
view.setSource(QUrl("qrc:/MyScene.qml"));
view.show();
return app.exec();
}
MyScene.qml:
import Qt3D.Core 2.0
import Qt3D.Render 2.0
import Qt3D.Input 2.0
import Qt3D.Extras 2.0
import OpenCVCaptureImage 1.0
import QtQml 2.12
Entity {
id: sceneRoot
RenderSettings {
id: renderSettings
activeFrameGraph: ForwardRenderer {
clearColor: Qt.rgba(0, 0.5, 1, 1)
camera: Camera {
id: objectsCamera
projectionType: CameraLens.PerspectiveProjection
fieldOfView: 45
aspectRatio: 16/9
nearPlane : 0.1
farPlane : 1000.0
position: Qt.vector3d( 0.0, 0.0, -40.0 )
upVector: Qt.vector3d( 0.0, 1.0, 0.0 )
viewCenter: Qt.vector3d( 0.0, 0.0, 0.0 )
}
}
}
InputSettings {
id: inputSettings
}
components: [renderSettings, inputSettings]
PlaneMesh {
id: planeMesh
width: 2
height: 2
}
TextureMaterial {
id: planeMaterial
texture: Texture2D {
id: planeTexture
textureImages: [
OpenCVCaptureImage {
id: capImage
}
]
}
}
Timer {
interval: 500
running: true
repeat: true
onTriggered: capImage.update()
}
Transform {
id: planeTransform
rotationX: 90
}
Entity {
id: planeEntity
components: [ planeMesh, planeMaterial, planeTransform ]
}
}
EDIT: I also tried putting the timer in the C++ constructor:
OpenCVCaptureImage::OpenCVCaptureImage(Qt3DCore::QNode *parent)
: Qt3DRender::QPaintedTextureImage(parent),
cap_(0)
{
QTimer::singleShot(2500, [=] {
auto t = new QTimer(this);
connect(t, &QTimer::timeout, [=] {
this->update();
});
t->setInterval(200);
t->start();
});
}
in that case the program crashes in QPainter::drawImage, and this error is printed to console:
2019-02-01 11:15:45.754907+0100 Qt3DOpenCV[55788:4839633] QPaintDevice: Cannot destroy paint device that is being painted
I'm not entirely sure what you are doing wrong, but I used QPaintedTextureImage in this example successfully and added a timer that updates the texture periodically. Qt3D doesn't call it directly but you can like you suggested in your question. Maybe try cleaning the project, then run qmake and build it afterwards.
I am trying to draw rectangles into QML. Data, which contains info about those rectangles, looks like this:
X
Y
Width
Height
Data
Data are stored in array and each item in array represents one rectangle. I am looking for best (or at least a good) way to draw those rectangles.
Which component of QML should I use?
class.h
class MyClass : public QObject
{
Q_OBJECT
Q_PROPERTY(QList<structure> list READ list NOTIFY listChanged)
public:
QList<structure> list() const { return list_; }
signals:
listChanged();
private:
QList<structure> list_;
}
repeater.qml
Repeater {
model: 2
delegate: Rectangle{
width: model.list.width
height: model.list.height
x: model.list.x
y: model.list.y
color: "red"
}
}
It is not necessary to create a QObject, just a QVariantList that stores the QRect is enough. On the other hand the model that you have to pass is just the list of QRect, to access each QRect in the delegate you must use modelData.
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QRect>
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QVariantList rectlist;
rectlist<< QRect{50, 30, 100, 100}
<< QRect{200, 20, 30, 30}
<<QRect{300, 300, 200, 33}
<<QRect{400, 23, 44, 55};
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("rectlist", rectlist);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
main.qml
import QtQuick 2.9
import QtQuick.Window 2.2
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Repeater {
model: rectlist
delegate: Rectangle{
x: modelData.x
y: modelData.y
width: modelData.width
height: modelData.height
color: "red"
}
}
}
Update:
main.cpp
#include <QColor>
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QRect>
struct Data
{
Q_GADGET
Q_PROPERTY(QRect rect MEMBER rect)
Q_PROPERTY(QString text MEMBER text)
Q_PROPERTY(QColor color MEMBER color)
public:
QRect rect;
QString text;
QColor color;
Data(const QRect& rect= QRect(), const QString& text="", const QColor& color = QColor(Qt::transparent)){
this->rect = rect;
this->text = text;
this->color = color;
}
};
Q_DECLARE_METATYPE(Data)
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QVariantList rectlist;
rectlist <<QVariant::fromValue( Data{ QRect{50, 30, 100, 100}, "text1", Qt::red});
rectlist <<QVariant::fromValue( Data{ QRect{200, 20, 30, 30 }, "text2", QColor("blue")});
rectlist <<QVariant::fromValue( Data{ QRect{300, 300, 200,33}, "text3", QColor(0, 200, 0)});
rectlist <<QVariant::fromValue( Data{ QRect{400, 23, 44, 55 }, "text4"});
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("rectlist", rectlist);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
#include "main.moc"
main.qml
import QtQuick 2.9
import QtQuick.Window 2.2
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Repeater {
model: rectlist
delegate: Rectangle{
x: modelData.rect.x
y: modelData.rect.y
width: modelData.rect.width
height: modelData.rect.height
color: modelData.color
Text{
anchors.centerIn: parent
text: modelData.text
}
}
}
}
I try
ApplicationWindow {
onActiveFocusControlChanged: {
console.log(activeFocusControl)
console.log(activeFocusControl.objectName)
}
}
ouput:
qml: QQuickTextField(0xa6ec00) //the 'activeFocusControl'
qml: //the 'activeFocusControl.objectName'
qml: QQuickButton(0xd7ccb0)
qml:
I want to
onActiveFocusControlChanged: {
if (activeFocusControl.className == "QQuickTextField") {
//do something
}
else if (activeFocusControl.className == "QQuickButton") {
//do something
}
but the "className" method does not exist
so how i can do it?
sorry, my english is pool, and thank you
There is no method to access the className from qml, so a possible solution is to create a helper from c ++ as shown below:
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QObject>
class Helper : public QObject
{
Q_OBJECT
public:
Q_INVOKABLE QString getClassName(QObject *obj) const{
return obj? obj->metaObject()->className(): "";
}
};
#include "main.moc"
int main(int argc, char *argv[])
{
#if defined(Q_OS_WIN)
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif
QGuiApplication app(argc, argv);
Helper helper;
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("helper", &helper);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
then it is used on the QML side:
import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Controls 2.3
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Button {
id: button
x: 270
y: 47
text: qsTr("Button")
}
TextField {
id: textField
x: 220
y: 169
text: qsTr("Text Field")
}
onActiveFocusControlChanged: {
var className = helper.getClassName(activeFocusControl)
switch(className){
case "QQuickTextField":
console.log("I am QQuickTextField")
break
case "QQuickButton":
console.log("I am QQuickButton")
break
default:
console.log("empty")
}
}
}
The complete example can be found at the following link.
I'm struggling to get my serial input "analogread2" converted to a double, to display in QML.
Ive added the Q_property, the root context in the main.cpp, and I can add the property in the qml, but I cant get it to display in text format.
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
SerialPort serialport;
//engine.rootContext()->setContextProperty("serialport", &serialport);
qmlRegisterType<SerialPort>("SerialPortlib", 1, 0, "SerialPort");
engine.load(QUrl(QStringLiteral("qrc:/main.qml")))
header.
Q_PROPERTY(double newValue MEMBER m_oil_pressure_volt WRITE set_oil_pressure_volt NOTIFY oil_pressure_volt_Changed )
qml
Window {
visible: true
width: 640
height: 480
id: gauge
SerialPort{
// what would I put in here to display the text value (double)
}
}
any help is much appreciated.
To do the test I am assuming that you are sending the data from the arduino in one of the following ways:
Serial.println(data);
Serial.print(data);
Assuming the above has been implemented the serialport class, this has a function called openDefault that scans the devices that are connected through serial and searches for the word "Arduino" within description or manufacturer and if one finds it connects to it.
The complete code can be found at: https://github.com/eyllanesc/stackoverflow/tree/master/Arduino-QML
serialport.h
#ifndef SERIALPORT_H
#define SERIALPORT_H
#include <QObject>
#include <QSerialPort>
#include <QSerialPortInfo>
class SerialPort : public QObject
{
Q_OBJECT
Q_PROPERTY(double oil_pressure_volt READ get_oil_pressure_volt WRITE set_oil_pressure_volt NOTIFY oil_pressure_volt_Changed )
public:
SerialPort(QObject *parent = 0);
~SerialPort();
double get_oil_pressure_volt() const;
void set_oil_pressure_volt(double newValue);
public slots:
void onReadData();
signals:
void oil_pressure_volt_Changed(double newValue);
private:
QSerialPort *arduino;
double mOil_pressure_volt;
QSerialPortInfo portInfo;
void openDefault();
};
serialport.cpp
#include "serialport.h"
#include <QDebug>
SerialPort::SerialPort(QObject *parent):QObject(parent)
{
arduino = new QSerialPort(this);
connect(arduino, &QSerialPort::readyRead, this, &SerialPort::onReadData);
openDefault();
}
SerialPort::~SerialPort()
{
delete arduino;
}
void SerialPort::set_oil_pressure_volt(double newValue)
{
if (mOil_pressure_volt == newValue)
return;
mOil_pressure_volt = newValue;
emit oil_pressure_volt_Changed(mOil_pressure_volt);
}
void SerialPort::onReadData()
{
if(arduino->bytesAvailable()>0){
QByteArray data = arduino->readAll();
QString value = QString(data).trimmed();
qDebug()<< value;
bool ok;
double val = value.toDouble(&ok);
if(ok)
set_oil_pressure_volt(val);
}
}
void SerialPort::openDefault()
{
for(auto info: QSerialPortInfo::availablePorts()){
qDebug()<<info.portName()<<info.description()<<info.manufacturer();
if(!info.isBusy() && (info.description().contains("Arduino") || info.manufacturer().contains("Arduino"))){
portInfo = info;
break;
}
}
if(portInfo.isNull()){
return;
}
arduino->setPortName(portInfo.portName());
arduino->setBaudRate(QSerialPort::Baud115200);
arduino->setDataBits(QSerialPort::Data8);
arduino->setParity(QSerialPort::NoParity);
arduino->setStopBits(QSerialPort::OneStop);
arduino->setFlowControl(QSerialPort::NoFlowControl);
if(arduino->open(QSerialPort::ReadWrite))
qDebug()<<"Connected to "<< portInfo.manufacturer()<< " on " << portInfo.portName();
else
qCritical()<<"Serial Port error: " << arduino->errorString();
}
double SerialPort::get_oil_pressure_volt() const
{
return mOil_pressure_volt;
}
Example:
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "serialport.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
qmlRegisterType<SerialPort>("SerialPortLib", 1, 0, "SerialPort");
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
main.qml
import QtQuick 2.6
import QtQuick.Window 2.2
import SerialPortLib 1.0
Window {
visible: true
width: 640
height: 480
title: qsTr("Test")
SerialPort{
onOil_pressure_voltChanged: {
tx.text = "%1".arg(oil_pressure_volt);
}
}
Text {
id: tx
anchors.fill: parent
font.family: "Helvetica"
font.pointSize: 20
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
}
}