Solved Signal not emitted or not received
-
How can I tell if a signal is not being emitted, or not being received?
I've been troubleshooting this for about a week and I'm stuck! As a learning project I've been extending the Qt CANBus example in Qt 5.12.3 "example sends and receives CAN bus frames". I've added a message parsing class which emits a signal with information and a QML window to receive the signal and display the data. My problem is the code reaches the emitting signal, but the QML window doesn't seem to receive it. I think I've got everything set up correctly, I've used this method in other projects, but I can't figure out why it's not working.
My code:
MainWindow.cpp
#include "mainwindow.h" #include "ui_mainwindow.h" #include "connectdialog.h" #include <QTimer> #include <QCanBus> #include <QDebug> #include <QQuickView> #include <QCloseEvent> #include <QCanBusFrame> #include <QDesktopServices> MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), m_ui(new Ui::MainWindow) { m_ui->setupUi(this); m_connectDialog = new ConnectDialog; m_status = new QLabel; m_ui->statusBar->addPermanentWidget(m_status); m_written = new QLabel; m_ui->statusBar->addWidget(m_written); initActionsConnections(); QTimer::singleShot(50, m_connectDialog, &ConnectDialog::show); engine.rootContext()->setContextProperty("pMessages", &pMessageProcessor); engine.rootContext()->setContextProperty("modFinder", &moduleFinder); engine.rootContext()->setContextProperty("mainWindow", this); } ... void MainWindow::processReceivedFrames() { if (!m_canDevice) return; while (m_canDevice->framesAvailable()) { const QCanBusFrame frame = m_canDevice->readFrame(); QString view; if (frame.frameType() == QCanBusFrame::ErrorFrame) view = m_canDevice->interpretErrorFrame(frame); else view = frame.toString(); const QString time = QString::fromLatin1("%1.%2 ") .arg(frame.timeStamp().seconds(), 10, 10, QLatin1Char(' ')) .arg(frame.timeStamp().microSeconds() / 100, 4, 10, QLatin1Char('0')); const QString flags = frameFlags(frame); QString canMessage; canMessage = view.simplified(); m_ui->receivedMessagesEdit->append(time + flags + view); /***********************************************************************************/ emit canMessageOut(canMessage) // <- this sends message to my parser /***********************************************************************************/ } } // THIS OPENS MY QML WINDOW, WHICH I HAVE OPEN AND RUNNING DURING TESTING void MainWindow::on_actionP_Bus_triggered() { if( component->status() != component->Ready ) { if( component->status() == component->Error ) qDebug()<< "Error: " + component->errorString(); return; } component->create(); }
mainWindow.h:
#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QCanBusDevice> // for CanBusError #include <QMainWindow> #include <QQmlContext> #include <QQmlComponent> #include <QQmlApplicationEngine> #include "pmessageprocessor.h" #include "modulefinder.h" class ConnectDialog; QT_BEGIN_NAMESPACE class QCanBusFrame; class QLabel; namespace Ui { class MainWindow; } QT_END_NAMESPACE class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = nullptr); ~MainWindow() override; QQmlApplicationEngine engine; QQmlComponent *component = new QQmlComponent(&engine, (QUrl(QStringLiteral("qrc:/qml/mainAppWindow.qml")))); void processReceivedFrames(); pMessageProcessor pMessageProcessor; // have to declare these classes in .h not in moduleFinder moduleFinder; // the .cpp or it causes issues QString statusMessage; static QDebug dStream(QString &m_outgoingMessage); signals: void canMessageOut(QString outGoingMessage); // send to CAN message parser void statusMsgChanged(const QString outGoingMessage); // send to QML receiver private slots: void sendFrame(const QCanBusFrame &frame) const; void processErrors(QCanBusDevice::CanBusError) const; void connectDevice(); void disconnectDevice(); void processFramesWritten(qint64); void on_actionP_Bus_triggered(); protected: void closeEvent(QCloseEvent *event) override; private: void initActionsConnections(); qint64 m_numberFramesWritten = 0; Ui::MainWindow *m_ui = nullptr; QLabel *m_status = nullptr; QLabel *m_written = nullptr; ConnectDialog *m_connectDialog = nullptr; QCanBusDevice *m_canDevice = nullptr; }; #endif // MAINWINDOW_H
My parser:
#include "pmessageprocessor.h" #include "mainwindow.h" #include <QHash> #include <QCache> pMessageProcessor::pMessageProcessor() { pHash.insert("FF04", 1); // 1 = Analog values pHash.insert("FF02", 2); // 2 = Output events - outputs on or off } /* *************** RECEIVE MESSAGES AND SEND TO PARSERS *********** */ void pMessageProcessor::pIncomingMessage(QString message){ //qDebug() << message; message.remove(0,2); // remove priority digits parts = message.split("[8]"); // split message at natural separator leftPart = parts.at(0).simplified(); // PGN + Source Address (remove leading and trailing whitespace) pgn = leftPart.left(4); // PGN only srcAddress = leftPart.right(2); // SA only data = parts.at(1).simplified(); // 8 bytes of data (remove leading and trailing whitespace) pgnTest = pHash[pgn]; switch (pgnTest) { case (1): analogInputs(srcAddress, data); break; case (2): outputConditions(srcAddress, data); break; } } /************* PARSERS ***************/ // Case 1 void pMessageProcessor::analogInputs(QString _srcAddress, QString _data){ QString _d0 = _data.left(2); QString _d1 = _data.mid(3,2); QString _d2 = _data.mid(6,2); QString _d3 = _data.mid(9,2); QString _d4 = _data.mid(12,2); QString _d5 = _data.mid(15,2); QString _d6 = _data.mid(18,2); QString _d7 = _data.right(2); QString _analogInputOne = _d1 + _d0; // (high byte + low byte) QString _analogInputTwo = _d3 + _d2; QString _analogInputThree = _d5 + _d4; QString _analogInputFour = _d7 + _d6; //qDebug()<< "data: " << _data <<_d0 << _d1 << _d2 << _d3 << _d4 <<_d5 << _d6 << _d7; // TODO - Conversion checking / logging qint16 analogInputOne = static_cast<qint16>(_analogInputOne.toInt(&ok, 16)*.001); // analog input 1 qint16 analogInputTwo = static_cast<qint16>(_analogInputTwo.toInt(&ok, 16)*.001); // analog input 2 qint16 analogInputThree = static_cast<qint16>(_analogInputThree.toInt(&ok, 16)*.001); // analog input 3 qint16 analogInputFour = static_cast<qint16>(_analogInputFour.toInt(&ok, 16)*.001); // analog input 4 analogInputsMessage(_srcAddress, analogInputOne, analogInputTwo, analogInputThree, analogInputFour); //qDebug() << "source address:" << _srcAddress << "A1:" << analogInputOne << "A2:" << analogInputTwo << "A3:" << analogInputThree << "A4:" << analogInputFour; } ... /********************** SIGNALING ********************/ void pMessageProcessor::analogInputsMessage( QString _srcAddress, qint16 _analogInputOne, qint16 _analogInputTwo, qint16 _analogInputThree, qint16 _analogInputFour) { emit analogInputsValue(_srcAddress, _analogInputOne, _analogInputTwo, _analogInputThree, _analogInputFour); /********************************************************************/ qDebug() << "signal reached"; // <- CODE GETS TO HERE /********************************************************************/ }
pMessageProcessor.h:
#ifndef PMESSAGEPROCESSOR_H #define PMESSAGEPROCESSOR_H #include <QHash> #include <QObject> #include <QDebug> class pMessageProcessor : public QObject { Q_OBJECT public: pMessageProcessor(); void analogInputs(QString, QString); void outputConditions(QString, QString); void analogInputsMessage(QString, qint16, qint16, qint16, qint16); QString statusMessage; static QDebug dStream(QString &m_outgoingMessage); public slots: void pIncomingMessage(QString message); signals: void analogInputsValue( QString srcAddressOut, qint16 analogInputOne, qint16 analogInputTwo, qint16 analogInputThree, qint16 analogInputFour); private: bool ok; int pgnTest; QString message1; QString message2; QString leftPart; QString pgn; QString data; QString srcAddress; QStringList parts; QHash<QString, int> pHash; }; #endif // PMESSAGEPROCESSOR_H
The QML connector:
Connections { target: pMessages onAnalogInputsValue: {srcAddress = srcAddressOut; aInputOne = analogInputOne; aInputTwo = analogInputTwo; aInputThree = analogInputThree; aInputFour = analogInputFour; console.log("srcAddress" + srcAddress)} //<- THIS NEVER GETS PRINTED }
My QML windows are arranged as an ApplicationWindow, which holds two sub windows. One is hidden behind the other until needed and I control the z value to arrange them. The Connections statement is in the subwindow that will receive and display the data, though I've tried putting it in the main window with no effect.
I don't know how to see if the signal is actually being emitted, but I know the code is at least getting to the emit statement. I read about QSignalSpy, but getting that up and running seems a little out of my league right now. I'm hoping someone can spot some stupid error that is keeping this from working.
-
@MScottM You're most welcome! Always happy to help a willing learner.
With respect to QQmlEngine, I was wondering, and asked in one of my posts above, whether it was an issue to create a second one, and if that might be part of my problem.
Yes, that is definitely part of your problem. The diagram below summarizes your code:
(Red arrows show signal-slot connections; Blue lines show QML contexts)
You have created 2 separate message processors and 2 separate QML engines.
- Your first
pMessageProcessor
(in main.cpp) receives theMainWindow::canMessageOut()
signal. However, you have not connected anything to thispMessageProcessor
's signals. - Your second
pMessageProcessor
(in mainwindow.h) has its signals connected to the signal handlers ModulePage. However, it does not receiveMainWindow::canMessageOut()
so it never emits its signals.
From the diagram, can you see what needs to happen for your code to work as you want? Try to implement the change in your code. If you can't figure it out, describe what you want to do and I'll give you some pointers.
The simple answer is 'all of the above'. A part of my job includes working with equipment that uses CAN bus to communicate, and I thought it would be interesting to learn some more about Qt while making a tool that could be useful for me when troubleshooting.
When learning multiple new concepts, I recommend the "divide and conquer" approach. Focus on one thing at a time -- you will be able to learn much faster this way. Mixing multiple concepts into the same study makes it much harder to troubleshoot things.
While I've had one or two formal classes in programming (enough to make me dangerous), I'm in no way a computer science major, and I apologize for the amateur mistakes I must be making!
You don't need a computer science major to learn Qt, but you do need to be systematic and patient.
Integrating QML GUIs with C++ seems like the best way to make interesting useful programs.
You can absolutely make interesting useful programs using Qt Widgets too.
Since the CAN example already has a solid GUI based on Qt Widgets, I highly recommend you first learn by adding new functionality to that example by implementing new widgets. Leave QML out of it for now.
Add QML after you have mastered widgets, or implement QML in a separate, smaller, and simpler project which doesn't have such a complex web of connections.
- Your first
-
Hi,
I am not 100% sure, but shouldn't you set all the context properties before creating your component since you are connecting stuff in there from the context ?
-
Hi @SGaist ,
thanks for taking a look. Forgive my ignorance, but could you expand on that a little? Do you mean in the MainWindow.cpp?
These?
engine.rootContext()->setContextProperty("pMessages", &pMessageProcessor); engine.rootContext()->setContextProperty("modFinder", &moduleFinder); engine.rootContext()->setContextProperty("mainWindow", this);
-
Yes, you are constructing your component before these are assigned.
Move it after these three lines.
-
Again - sorry for not quite getting it! Do you mean when the QML window gets created here?
void MainWindow::on_actionP_Bus_triggered() { if( component->status() != component->Ready ) { if( component->status() == component->Error ) qDebug()<< "Error: " + component->errorString(); return; } component->create(); }
this does happen after mainWindow has been created.
-
I was thinking about the construction part:
QQmlComponent *component = new QQmlComponent(&engine, (QUrl(QStringLiteral("qrc:/qml/mainAppWindow.qml"))));
-
Hi @SGaist
I moved the constructor into the function and it didn't make a difference.
-
Then I would first take these two into the main function and initialise everything there just to be sure it's working correctly.
-
I tried it. The QML component initialized immediately, but there was no change in the behavior.
I was reading this article:
debugging-signals-and-slots-in-qt
and tried this line:
connect(this, SIGNAL(&analogInputsValue()), qApp, SLOT(aboutQt));
I assume this is supposed to pop up the Qt About box when the signal triggers, but I get a console message:
"qObject::connect: no such signal pMessageProcessor::&analogInputsValue()
-
-
Hi @JKSH,
I removed the '&' and got the same result - "no such signal".
-
@mscottm said in Signal not emitted or not received:
I removed the '&' and got the same result - "no such signal".
Looking at your
mainWindow.h
, you don't have aanalogInputsValue()
signal. Instead, you have aanalogInputsValue(QString, qint16, qint16, qint16, qint16)
signal.So, your connection code should be
connect(this, SIGNAL(analogInputsValue(QString, qint16, qint16, qint16, qint16)), qApp, SLOT(aboutQt()));
NOTE: Remember the
()
in the slot too! WriteSLOT(aboutQt())
, notSLOT(aboutQt)
-
@jksh said in Signal not emitted or not received:
connect(this, SIGNAL(analogInputsValue(QString, qint16, qint16, qint16, qint16)), qApp, SLOT(aboutQt()));
OHHH! That did it, thanks!
So the signal is obviously firing - the 'About' message box popped up. Now I know where to focus my troubleshooting. I know that at least the IDE is recognizing the class contextProperty, as it 'colorizes' the target class name in the connect statement.
What else can I look at or try?
-
Well, to ensure that the basics are working, try starting from the Embedding C++ Objects into QML with Context Properties example and then move things around until they are where you would like to have them in your application.
-
@SGaist - on that page it says:
"If the QML item needs to receive signals from the context property, it can connect to them using the Connections type. For example, if ApplicationData has a signal named dataChanged(), this signal can be connected to using an onDataChanged handler within a Connections object:
Text { text: applicationData.getCurrentDateTime() Connections { target: applicationData onDataChanged: console.log("The application data changed!") } }
That is what I'm doing, and the only difference from my QML and their example is I have my Connections statement higher in the hierarchy, but I've tried it in multiple places.
-
What I wanted you to check was that your QML + context properties are working correctly when following the same setup as the example provided in the documentation. Once that has been confirmed, we can then move these stuff in your main window class.
-
The code you posted is very large; it will take us a lot of time to read through and identify your problem.
Please post a minimal example that demonstrates your problem.
-
Hi @SGaist,
I think you put me on to something. When I created the project to test the code in the example you pointed to, I noticed in the 'boilerplate' code that was created, a QObject::connect statement that I don't have:
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, &app, [url](QObject *obj, const QUrl &objUrl) { if (!obj && url == objUrl) QCoreApplication::exit(-1); }, Qt::QueuedConnection); engine.load(url);
Is this connecting the qml side to the cpp side? I didn't think about this as I was adapting an example. Do I need to add something like this?
edit - I also just noticed that there is a QQmlApplicationEngine created in the main.cpp, and I created another one in MainWindow.cpp - that has to be an issue...doesn't it?
@JKSH - I do apologize, I was trying to be thorough, but I understand the need for brevity when someone is trying to help and get to the heart of an issue.
-
It can be useful to stop the application if there's a failure to create the object from the file you passed as parameter.
It is however not directly related to the issue you are having.
-
Hi @SGaist ,
I created a small project and adapted the example you pointed to, and created a signal in c++ that would update a text in QML every few seconds using context properties and a Connections statement in QML:
QTimer* timer = new QTimer(this); timer->setInterval(3000); connect(timer, SIGNAL(timeout()), this, SLOT(sendOutSignal())); timer->start(); } void pMessageProcessor::sendOutSignal() { x+=1; number.setNum(x); emit mySignal(number); }
Text { id: myText text: {""} Connections { target: applicationData onMySignal: myText.text = myVariable } }
It works as expected.
Next I moved the code over to my application and connected one of my QML label texts to the signal and it works - the label updates with the timer and signal, however I still can't get my original signal to work.