Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. Help me write my first QML wrapper for a C++ class
QtWS25 Last Chance

Help me write my first QML wrapper for a C++ class

Scheduled Pinned Locked Moved Solved General and Desktop
qmlc++canserial
6 Posts 3 Posters 2.9k Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • ArasA Offline
    ArasA Offline
    Aras
    wrote on last edited by Aras
    #1

    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

    jeremy_kJ 1 Reply Last reply
    0
    • ArasA Offline
      ArasA Offline
      Aras
      wrote on last edited by
      #2

      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"                                                                                                         
      
      1 Reply Last reply
      0
      • ArasA Aras

        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

        jeremy_kJ Online
        jeremy_kJ Online
        jeremy_k
        wrote on last edited by
        #3

        @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?

        Asking a question about code? http://eel.is/iso-c++/testcase/

        1 Reply Last reply
        1
        • VRoninV Offline
          VRoninV Offline
          VRonin
          wrote on last edited by VRonin
          #4

          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

          "La mort n'est rien, mais vivre vaincu et sans gloire, c'est mourir tous les jours"
          ~Napoleon Bonaparte

          On a crusade to banish setIndexWidget() from the holy land of Qt

          1 Reply Last reply
          1
          • ArasA Offline
            ArasA Offline
            Aras
            wrote on last edited by
            #5

            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?

            jeremy_kJ 1 Reply Last reply
            0
            • ArasA Aras

              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?

              jeremy_kJ Online
              jeremy_kJ Online
              jeremy_k
              wrote on last edited by
              #6

              @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.

              Asking a question about code? http://eel.is/iso-c++/testcase/

              1 Reply Last reply
              1

              • Login

              • Login or register to search.
              • First post
                Last post
              0
              • Categories
              • Recent
              • Tags
              • Popular
              • Users
              • Groups
              • Search
              • Get Qt Extensions
              • Unsolved