Problem with QT and CAN (QCanBus and friends)
-
I'm writing an application that connects to a CAN bus and I found an issue that exists on the Windows platform and not the Linux platform. I found that the problem also exists in the CAN example program, so I will use that application to explain.
Using the example 'CAN Bus Example'link text on a windows machine, when I click and hold the title bar of the application window, the application immediately stops receiving CAN frames from the hardware. At first I assumed that it was because the GUI thread was blocking the QCanBusDevice::framesReceived() signal, so in my application I created a separate 'worker thread' for receiving frames, but the same behavior exists. I looked into the source code and did some debugging and found that QCanBusDevice::readFrame() stops returning frames from the hardware when the title bar is clicked. I know this because I quit using the framesReceived() signal to read frames, and instead did a busy poll loop to read frames, both in the GUI Thread and in a separate thread. A simple example can be seen by running the example, connecting to some CAN hardware that spits out lots of CAN data continously, like an engine controller, and clicking on the title bar of the application. All of the CAN frames scrolling in the message window immediately stop on the windows platform. The readFrame() function does not block, it just returns empty frames as the docs say it should do when there are no CAN frames available. Meanwhile though, the hardwares receive queue starts filling with unread frames until I let go of the title bar.
I have tested this on windows using Peak CAN hardware, Vector CAN hardware, and Tiny-CAN hardware. On Linux I have also tested this using the socketcan interface, using the same Peak and Tiny-CAN hardware and the problem does not exist. I click on the title bar of the application and drag it all over the desktop and CAN frames are still received with no interruptions.
-
This is not a QCanBus issue. It is Windows issue.
Most of QCanBus classes are used the qwineventnotifier which is stops when the user does click and hold the window title. It is a qwineventnotifier && windows issue/feature.
Same behavior even belongs to all I/O Qt-classes relies on Windows's WriteFileEx/ReadFileEx functions, e.g. QLocalSocket, QProcess and so on.
The TinyCan does not used the qwineventnotifier, it receives Rx callbacks from another thread, running inside of a native tiny-can library. So, I don't sure that on TinyCAN this ussue is reproduced too.
As a workaround, just move your QCanBusDevice's instances to other thread (seems it is possible).
-
I've created a new thread where I create the device and connect:
m_canDevice1 = QCanBus::instance()->createDevice("peakcan", "usb0", &errorString); if (!m_canDevice1) { //emit connect_status(1, errorString); return; } if (!m_canDevice1->connectDevice()) { //emit connect_status(1, m_canDevice1->errorString()); delete m_canDevice1; m_canDevice1 = nullptr; }
(connect_status signal is commented out for now during debug)
After the connectDevice() call I get:
QObject::startTimer: Timers cannot be started from another thread
and of course no CAN functionality. I have taken all QCanBus* code out of the GUI thread so there are no instances of the hardware in the GUI thread anymore.
-
@nostar said in Problem with QT and CAN (QCanBus and friends):
QObject::startTimer: Timers cannot be started from another thread
This means that you use threads wrong. Please read documentation about using a threads.
-
Here is the complete CANThread class. How am I using threads wrong??
The thread is started from MainWindow via a connect button handler:
can = new CANThread(); connect(can, SIGNAL(data_read1()), this, SLOT(process_received1())); connect(can, &CANThread::finished, can, &QObject::deleteLater); can->start();
canthread.h
#ifndef CANTHREAD_H #define CANTHREAD_H #include <QObject> #include <QThread> #include <QQueue> #include <QCanBusDevice> #include <QCanBusFrame> class CANThread : public QThread { Q_OBJECT public: CANThread(); ~CANThread(); void run(); QCanBusFrame rx1; uint8_t connected; QQueue<QCanBusFrame> rxdata; signals: //void connect_status(char s); void data_read1(); private: QCanBusDevice *m_canDevice1 = nullptr; }; #endif // CANTHREAD_H
canthread.cpp
#include "canthread.h" #include <QCanBus> #include <QVariant> CANThread::CANThread() { } CANThread::~CANThread() { } void CANThread::run() { QString errorString; m_canDevice1 = QCanBus::instance()->createDevice("peakcan", "usb0", &errorString); if (!m_canDevice1) { //emit connect_status(1, errorString); return; } m_canDevice1->setConfigurationParameter(QCanBusDevice::CanFdKey, canfd); if (!m_canDevice1->connectDevice()) { //emit connect_status(1, m_canDevice1->errorString()); delete m_canDevice1; m_canDevice1 = nullptr; } else{ QVariant bitRate = m_canDevice1->configurationParameter(QCanBusDevice::BitRateKey); connected = 1; //emit connect_status(0, NULL); } while(connected){ while (m_canDevice1->framesAvailable()) { rx1 = m_canDevice1->readFrame(); if(rx1.frameId()){ rxdata.enqueue(rx1); emit data_read1(); } } } m_canDevice1->disconnectDevice(); delete m_canDevice1; m_canDevice1 = nullptr; }
-
@nostar said in Problem with QT and CAN (QCanBus and friends):
How am I using threads wrong??
Here all is wrong! Please read documentation about (these are tips for you):
- QObject's ownerships.
- QObject's move to thread.
PS: You can found all required info in google or on this forum. Similar issues were talked 100500 times.
-
I hit the same problem. My solution is (as described above) move the Can Bus Device to a separate thread. But this more complicated than just using "moveToThread"
QCanBus is a singleton and is created the first time QCanBus::instance is called. The task, this function is called from, takes owership of the QCanBus and all QCanBusDevice
My solution is: Derive a class from QThread and call all function to QCanBus etc from the run() function ONLY.
void CanPeakWorker::run(){ toStop=false; QCanBus::instance()->moveToThread(this); //this is the first time, instance() is called. qDebug()<<QCanBus::instance()->thread(); //checks if this object really has "arrived" in the correct task ...
And then communicate with the thread via signals and slots. Now the QCanBus is detached from the gui task and keeps running, even if the main window is moved.
-
You should try move only the QCanBusDevice instance into a thread (and maybe set parent as
null
). -
I did that. No improvement.
I found out, that this only works if EVERYTHING CanBus related takes place inside the run function of the thread.
As soon as I use a Slot-mechanism and for example use QCanBusDevice->connectDevice() from a slot in this thread, it is not working anymore. Qt::QueuedConnection for this slot does not help either.Only if I keep EVERYTHING CAN-Related inside the thread:run() function, it works and does not block anymore, if the window is moved.
class CanPeakWorker: public QTread; void CanPeakWorker::run(){ QCanBus::instance()->moveToThread(this); if (!QCanBus::instance()->plugins().contains(QStringLiteral("peakcan"))) { qDebug("peak not available"); return; } QString errorString; toStop=false; const QList<QCanBusDeviceInfo> dev = QCanBus::instance()->availableDevices( QStringLiteral("peakcan"), &errorString); if (errorString.isEmpty()){ for(QCanBusDeviceInfo d :dev){ QCanBusDevice *candev = QCanBus::instance()->createDevice(QStringLiteral("peakcan"), d.name(),&errorString); candev->moveToThread(this); //maybe unnecessary devices.insert(d.description(),candev); //build a map with all found devices and names } } QCanBusDevice *currentDevice=nullptr; while(!toStop){ //thread main loop if(currentDevice==nullptr){ //not running if(!deviceNameToConnectTo.isEmpty()&&(devices[deviceNameToConnectTo]!=nullptr)){ currentDevice=devices[deviceNameToConnectTo]; currentDevice->connectDevice(); } else msleep(10); //nothing open, idle } else{ //running if(deviceNameToConnectTo.isEmpty()){ currentDevice->disconnectDevice(); currentDevice=nullptr; } else{ //sending out received frames if(currentDevice->waitForFramesReceived(-1)){ QVector<QCanBusFrame> frames = currentDevice->readAllFrames(); QDateTime tm=QDateTime::currentDateTime(); for(int a=0;a<frames.size();a++){ CanMessage msg(frames[a]); emit(frameReceived(msg,tm)); } } } } } if(currentDevice!=nullptr) currentDevice->disconnectDevice(); } //slots: don't deal with Thread, just use global variables to communicate with thread. void CanPeakWorker::connect(QString name){ QCanBusDevice *dev = devices[name]; if(dev!=nullptr){ deviceNameToConnectTo=name; return; } } void CanPeakWorker::disconnect(){ deviceNameToConnectTo=""; }
-
@JohannesV oh my goodness,
no its not working, because you're not running an event loop in your QThread of course slots aren't executed.
QCanBusDevice has signals for
- void errorOccurred(QCanBusDevice::CanBusError)
- void framesReceived()
- void framesWritten(qint64 framesCount)
so forcing synchronous behaviour on it to then move it to a thread to get a responsive remaining application is (far from) not ideal.
It is true, that overwriting QThread::run is not necessarily wrong, in this case it is. Please revisit QThreads, do you want/need helpful links ? 😅