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 QML

    Q_INVOKABLE void connect(const QString &query) make it a public slot instead of using Q_INVOKABLE

    explicit 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 in QML and starting my application using appman 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 using appman?

    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 calling qmlRegisterType?



  • @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 in QML and starting my application using appman 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 using appman?

    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.


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.