Help me write my first QML wrapper for a C++ class
-
I need to access a C++ API to work with CAN bus. Looks like the best solution is to write a QML wrapper to expose all the functionality I need. I have never done this before and I expect lots of errors, so I thought I post my code here and modify with your help until it works. :)
Here is my
canservice.cpp
so far:#include "canservice.h" #include <QCanBus> #include <QDebug> #include <QCanBusFrame> #include <QTimer> #include <QtCore/qbytearray.h> #include <QtCore/qvariant.h> #include <QtCore/qdebug.h> CANService::CANService(QObject *parent) : QObject(parent), m_canDevice(nullptr) { QString status = ""; initializeSettings(); // TODO" disable sending messages until connection is stablished } CANService::~CANService() { delete m_canDevice; } void CANService::receiveError(QCanBusDevice::CanBusError error) const { switch (error) { case QCanBusDevice::ReadError: case QCanBusDevice::WriteError: case QCanBusDevice::ConnectionError: case QCanBusDevice::ConfigurationError: case QCanBusDevice::UnknownError: qWarning() << m_canDevice->errorString(); default: break; } } void CANService::initializeSettings() { foreach (const QByteArray &backend, QCanBus::instance()->plugins()) { qInfo() << "found: " + backend; if (backend == "socketcan") { // found socketcan m_currentSettings.backendName = "socketcan"; break; } } if(m_currentSettings.backendName.length() < 1) { qWarning() << "did not find a backend"; } m_currentSettings.backendName = "socketcan"; m_currentSettings.deviceInterfaceName = QStringLiteral("vcan0"); } void CANService::connectDevice() { m_canDevice = QCanBus::instance()->createDevice(m_currentSettings.backendName.toLocal8Bit(), m_currentSettings.deviceInterfaceName); if (!m_canDevice) { showStatusMessage(tr("Connection error")); return; } connect(m_canDevice, &QCanBusDevice::errorOccurred, this, &MainWindow::receiveError); connect(m_canDevice, &QCanBusDevice::framesReceived, this, &MainWindow::checkMessages); connect(m_canDevice, &QCanBusDevice::framesWritten, this, &MainWindow::framesWritten); if (p.useConfigurationEnabled) { foreach (const ConnectDialog::ConfigurationItem &item, p.configurations) m_canDevice->setConfigurationParameter(item.first, item.second); } if (!m_canDevice->connectDevice()) { delete m_canDevice; m_canDevice = nullptr; qInfo() << "Connection error"; } else { qInfo() << m_currentSettings.backendName << "is connected"; } } void CANService::sendMessage() const { if (!m_canDevice) return; // TODO: replace test message with input QByteArray writings = dataFromHex("1122334455667788"); QCanBusFrame frame; const int maxPayload = 8; // 64 : 8; int size = writings.size(); if (size > maxPayload) size = maxPayload; writings = writings.left(size); frame.setPayload(writings); //TODO: get from UI qint32 id = 100; if (id > 2047) { //11 bits id = 2047; } frame.setFrameId(id); frame.setExtendedFrameFormat(true); // frame.setFrameType(QCanBusFrame::RemoteRequestFrame); // frame.setFrameType(QCanBusFrame::ErrorFrame); frame.setFrameType(QCanBusFrame::DataFrame); m_canDevice->writeFrame(frame); } void CANService::checkMessages() { if (!m_canDevice) return; const QCanBusFrame frame = m_canDevice->readFrame(); const qint8 dataLength = frame.payload().size(); const qint32 id = frame.frameId(); QString view; if (frame.frameType() == QCanBusFrame::ErrorFrame) { interpretError(view, frame); } else { view += QLatin1String("Id: "); view += QString::number(id, 16).toUpper(); view += QLatin1String(" bytes: "); view += QString::number(dataLength, 10); view += QLatin1String(" data: "); view += dataToHex(frame.payload()); } if (frame.frameType() == QCanBusFrame::RemoteRequestFrame) { qInfo() << "got remote request message" << view; } else if (frame.frameType() == QCanBusFrame::ErrorFrame) { qWarning() << "got can error frame: " << view; } else { qInfo() << "got can frame: " << view; } } void CANService::interpretError(QString &view, const QCanBusFrame &frame) { if (!m_canDevice) return; view = m_canDevice->interpretErrorFrame(frame); } static QByteArray dataToHex(const QByteArray &data) { QByteArray result = data.toHex().toUpper(); for (int i = 0; i < result.size(); i += 3) result.insert(i, ' '); return result; } static QByteArray dataFromHex(const QString &hex) { QByteArray line = hex.toLatin1(); line.replace(' ', QByteArray()); return QByteArray::fromHex(line); }
In the
canservice.h
I have:#ifndef CANSERVICE_H #define CANSERVICE_H #include <QObject> #include <QQuickItem> #include <QCanBusDevice> class CANService : public QObject { Q_OBJECT public: explicit CANService(QObject *parent = 0); typedef QPair<QCanBusDevice::ConfigurationKey, QVariant> ConfigurationItem; struct Settings { QString backendName; QString deviceInterfaceName; QList<ConfigurationItem> configurations; bool useConfigurationEnabled; }; void connectDevice(); Q_INVOKABLE void connect(const QString &query) { qDebug() << "invoking connect with " << query; } explicit ConnectDialog(QWidget *parent = nullptr); ~ConnectDialog(); Settings settings() const; private: Settings m_currentSettings; void initializeSettings(); signals: public slots: }; qmlRegisterType<MyObject>("can.myapp", 1, 0, "CANService"); #endif // CANSERVICE_H
In my QML file I first attempt to import the newly defined service:
import can.myapp 1.0
and then I declare an instance of it:CANService { id: "myCanService" }
cc @SGaist
-
Forgot to mention my first error. When I try to run this application and load the QML file that makes the call to
CANService
, it does not load and I get the following error in application console:component not ready: "file:///home/aras/Projects/myapp/apps/com.myapp.diagnostics/Diagnostics.qml:5 module \"can.myapp\" is not installed\n"
-
@Aras said in Help me write my first QML wrapper for a C++ class:
qmlRegisterType<MyObject>("can.myapp", 1, 0, "CANService");
Perhaps it's a formatting error, but this doesn't look like this is a valid function call from within a function body or initializing a global static variable. Have you verified that the registration function is executing?
-
qmlRegisterType<MyObject>("can.myapp", 1, 0, "CANService");
this should go in main. it's not a macro, it's code that needs to be executed before you load your QMLQ_INVOKABLE void connect(const QString &query)
make it a public slot instead of using Q_INVOKABLEexplicit ConnectDialog(QWidget *parent = nullptr); ~ConnectDialog();
I'm not sure what's going on here but having QWidget inside what is intended to be a QML wrapper doesn't sound right
-
Have you verified that the registration function is executing?
@jeremy_k no i have not! I can not get the debugger to work for my project but that is another story.
qmlRegisterType<MyObject>("can.myapp", 1, 0, "CANService"); this should go in main. it's not a macro, it's code that needs to be executed before you load your QML
I think you are right about this and the problem is that the line above is not executed. There is a problem though: I am using application manager, so my application does not have the conventional main function. I have am declaring
ApplicationManagerWindow
inQML
and starting my application usingappman
command. I looked at the application manager docs but I could not find anything there. Do you know how I could make this C++ call if I am usingappman
?I'm not sure what's going on here but having QWidget inside what is intended to be a QML wrapper doesn't sound right
Ok you are right. I am still very new to Qt and figuring out what libraries I can mix is sometimes confusing me. I will strip out the QWidget then.
Thanks for you help! Anything else you could add regarding
appman
and callingqmlRegisterType
? -
@Aras said in Help me write my first QML wrapper for a C++ class:
I think you are right about this and the problem is that the line above is not executed. There is a problem though: I am using application manager, so my application does not have the conventional main function. I have am declaring
ApplicationManagerWindow
inQML
and starting my application usingappman
command. I looked at the application manager docs but I could not find anything there. Do you know how I could make this C++ call if I am usingappman
?In that case, the registration needs to be done in a plugin.
http://doc.qt.io/qt-5/qtqml-modules-cppplugins.html covers the process.