Receiving LCM messages via signals/slots in C++ with Qt?



  • In my application, I am receiving messages from LCM (Lightweight Communications and Marshalling) that contain data for multiple consumers within the application. I imagined this working with the LCM handler as a singleton so that there is one instance that each class could use. For example, each consumer class would have:

    QObject::connect(LCMHandler::getInstance(), SIGNAL(messageReceived()),
                     this, SLOT(processMessage()));
    

    Where lcmhandler.h is:

    class LCMHandler : public QObject
    {
        Q_OBJECT
    public:
        static LCMHandler* getInstance();
        LCMHandler();
        ~LCMHandler() {}
    
        void handleMessage(const lcm::ReceiveBuffer* rbuf,
                           const std::string &chan,
                           const example::example_t *msg);
    
    signals:
        void messageReceived();
    
    private:
        static LCMReceiver* _instance;
    };
    

    And lcmhandler.cpp is:

    LCMHandler* LCMHandler::_instance = 0;
    
    LCMHandler::LCMHandler()
    {
        lcm::LCM lcm;
        if(lcm.good())
        {
            lcm.subscribe("MyChannel", &LCMHandler::handleMessage, this);
            while(0 == lcm.handle());
        } else {
            std::cerr << "LCM Error" << std::endl;
        }
    }
    
    LCMHandler* LCMHandler::getInstance() {
        if (!_instance) {
            _instance = new LCMHandler();
        }
        return _instance;
    }
    
    void LCMHandler::handleMessage(const lcm::ReceiveBuffer *rbuf,
                                   const std::string &chan,
                                   const hlelcm::transponder_t *msg)
    {
    
        std::cout << "Received message on channel " <<  chan.c_str() << std::endl;
        emit messageReceived();
    }
    

    The application successfully prints "Received message on channel..." repeatedly; however, nothing else is executed, including code in the consumer class's processMessage(), presumably because the application gets stuck looping on handleMessage(...) and never executes the signal/slot procedure (or refreshes the UI components). So, if the implementation of processMessage() is:

    void Consumer::processMessage() {
        std::cout << "Message received" << std::endl;
    }
    

    It never executes, while handleMessage(...) loops infinitely. Similarly, the Qt UI never loads because handleMessage is busy looping.

    What is the best way to handle the incoming messages? Should I refrain from using a singleton for LCMHandler? What do I need to change to make this implementation work?



  • @marmoc said in Receiving LCM messages via signals/slots in C++ with Qt?:

    Should I refrain from using a singleton for LCMHandler?

    If you ask @kshegunov yes

    the solution you do not want to implement is is calling QApplication::processEvents() inside your loop. What you really should do is make handleMessage work asynchronously instead of being an infinite loop. Not knowing what's inside it is difficult to be more specific


  • Qt Champions 2016

    @VRonin said in Receiving LCM messages via signals/slots in C++ with Qt?:

    If you ask @kshegunov yes

    Well, I'm not that fanatical ... okay, maybe a little bit, but I think we all would agree the singletonian approach promotes tight coupling.

    @marmoc said in Receiving LCM messages via signals/slots in C++ with Qt?:
    You shouldn't block the event loop, much less in the constructor. So I suggest something like this:

    class LCMHandler : public QObject
    {
        Q_OBJECT
        Q_DISABLE_COPY(LCMHandler)
    
    public:
        LCMHandler()
        {
            Q_ASSERT(!instance);
            instance = this;
    
            if(lcm.good())
                lcm.subscribe("MyChannel", &LCMHandler::handleMessage, this);
            else
                std::cerr << "LCM Error" << std::endl; // You can set also a flag for the class so you know at a later stage that the initialization failed.
    
            // Schedule the message polling through the event loop
            QMetaObject::invokeMethod(this, "pollMessage", Qt::QueuedConnection);
        }
    
        static LCMHandler * getInstance()
        {
            Q_ASSERT(instance); // We want to catch any (possible) nullptr dereferencing at runtime. This is usually an overkill though.
            return instance;
        }
    
        // ...
        // Implementation of handle message and other goodies ...
    
    private:
        Q_INVOKABLE void pollMessage()
        {
            // Check if there's a message
            if (lcm.handle())  {
                // Pull the data and emit the signal 
                handleMessage( ... );
            }
    
            QMetaObject::invokeMethod(this, "pollMessage", Qt::QueuedConnection);  // Loop once more through the event loop
        }
    
    signals:
        void messageReceived();
    
    private:
        lcm::LCM lcm; //< This has no place as a local in the constructor
        static LCMHandler * instance;
    };
    
    LCMHandler * LCMHandler::instance = NULL; // This relies on the fact that trivial initialization are always done first
    

    Then you can create the object in main(), as usual, on the stack.

    int main(int argc, char ** argv)
    {
        QApplication app(argc, argv);
        LCMHandler lcmHander;
        Consumer consumer;
    
        QObject::connect(&lcmHandler, &LCMHandler::messageReceived, &consumer, &Consumer::processMessage);
    
        return QApplication::exec();
    }
    


  • @kshegunov's solution with lcm::LCM::subscribe I think he just forgot the void in Q_INVOKABLE pollMessage()

    To give context to the people lost like me, they are using this library https://lcm-proj.github.io/group__LcmCpp.html


  • Qt Champions 2016

    @VRonin said in Receiving LCM messages via signals/slots in C++ with Qt?:

    I think he just forgot the void in

    Yes, I did indeed.

    To give context to the people lost like me, they are using this library https://lcm-proj.github.io/group__LcmCpp.html

    Thanks, I just copied the OP's code blindly. :)
    Seeing how the library is designed, I'd say one rather has to use something along the lines of:

    Q_INVOKABLE void pollMessage()
    {
        // Check if there's a message
        if (!lcm.handle())
            ; // Just report the error or do nothing
    
        QMetaObject::invokeMethod(this, "pollMessage", Qt::QueuedConnection);  // Loop once more through the event loop
    }
    


  • Looking at the documentation of the library looks like you are over-complicating it, you are using while(0 == lcm.handle()); only to prevent lcm (awful thing calling a variable with the same name as a namespace btw) getting out of scope as the callback is managed already by the subscription



  • @kshegunov @VRonin Thanks for the replies and the great suggestions! This helped a lot!


Log in to reply
 

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