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.
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"
}
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"
EDIT:
Problem is most likely with where I am calling qmlRegisterType
. I am using appman
so my application has no main
function. Where is the right place to run qmlRegisterType
from?
I'm responding because you tagged an older post as similar.
Here's a couple questions: 1) Where are you calling
qmlRegisterType<MyObject>("can.myapp", 1, 0, "CANService");
My understanding is that this needs to be called in themain
method before you instantiate theQtQuickApplicationViewer
(if you're loading it that way.)2) Does this work with a simple example (passing a simple
QString
value to a function)?I'm not actively coding in Qt right at the moment, but hopefully this can point you in the right direction:
Here's the "main" method from one of my applications that uses custom components (for simple Java connectivity over the command line).
Here's a simplified class (these classes are ~600 lines)
Theoretically you can invoke this in QML as such now:
I would suggest doing something really simple like this to make sure there isn't a problem in the plumbing and then, if that works, go method by method to see if there's another call that's blocking something. My best guess without full source is that it is probably where you're registering the components.