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. QT serial sending and receiving messages to external device
Forum Updated to NodeBB v4.3 + New Features

QT serial sending and receiving messages to external device

Scheduled Pinned Locked Moved Unsolved General and Desktop
4 Posts 3 Posters 310 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.
  • L Offline
    L Offline
    lukutis222
    wrote on last edited by lukutis222
    #1

    Hello all. I am working on QT application that will be used for testing external microcontroller devices using serial commands.

    Lets say I have a device that will respond to certain serial commands. If response is as expected, that means the device is OK, if not, that means device is faulty and should be put aside for repairs.

    I have a structure:

        struct testing_s{
            bool waiting_for_response;
            QString command;
            QString expected_response;
        };
    
        testing_s test_struct;
    

    The structure above contains a flag (waiting_for_response) that allows me to know if I have received a response or not.
    Initially when the program starts, this is set to 0 but when I send a command, I set it to 1 which means I will be waiting for the response.

    In my QT application, I am listening for any incoming serial data from the external device using

    connect(&serial_local->serial_connection, &QSerialPort::readyRead, this, &TestTool::Command_parser);
    

    When I want to send some command and expect certain response, I use the following command:

    void TestTool::Send_and_wait(QString command,QString expected_response){
        test_struct.command = command;
        test_struct.expected_response = expected_response;
        serial_local->serial_connection.write((command.toUtf8())); // send serial data
        test_struct.waiting_for_response = 1;
    }
    

    The command above will set the test_struct with the required information and send serial data to the device, then in my command_parser I am parsing all incoming data and filtering for my expected response:

    void TestTool::Command_parser()
    {
        while(serial_local->is_data_available(&serial_local->serial_connection)){
            QByteArray line = serial_local->read_data(&serial_local->serial_connection);
            QString DataAsString = QString(line);
            if(test_struct.waiting_for_response == 1){
                qDebug("response received = %s \n",DataAsString.toStdString().c_str());
                qDebug("expected response = %s \n",test_struct.expected_response.toStdString().c_str());
                if(strncmp(DataAsString.toStdString().c_str(),test_struct.expected_response.toStdString().c_str(),test_struct.expected_response.length()) == 0){
                    qDebug("response matched, ready to send new command \n");
                    test_struct.waiting_for_response = 0;
                }
            }
        }
    }
    

    If expected response is received, I am ready to send another command, if it is not received within certain time (lets say 5 seconds, then I should stop the testing and assume the device is faulty).

    In my QT application, I have a button "Start test". Once this button is pressed, I want to testing to start. I have tested this with only 1 command and it seemed to work. I send "ping" to the device and my device responded "pong".

    void TestTool::on_start_test_button_clicked(){
        qDebug("on_start_test_button_clicked \n");
        Send_and_wait("ping\n","pong");
    }
    

    The application looks like:

    0083ffd0-3f62-490a-a8c2-d5a31d9c6b89-image.png

    When the expected response is received, I will mark the radio button so the user can see at which stage the test is at any time.

    My question

    I am not fully understanding how can I ensure that more than 1 command is send one by one after I click START_TEST button. I have tried the following:

    void TestTool::on_start_test_button_clicked(){
    
        while(1){
            if(test_struct.waiting_for_response == 0){ // only allow sending command if we are currently not waiting for any respose
                Send_and_wait("ping\n","pong\n");
            }
            else{
                QThread::msleep(100);
            }
    
            if(test_struct.waiting_for_response == 0){ // only allow sending command if we are currently not waiting for any respose
                Send_and_wait("test_nvs\n","OK\n");
            }
            else{
                QThread::msleep(100);
            }
    
            if(test_struct.waiting_for_response == 0){ // only allow sending command if we are currently not waiting for any respose
                Send_and_wait("ping\n","pong\n");
            }
            else{
                QThread::msleep(100);
            }
        }
       
    }
    

    The function of the above is quite simple. When the button is clicked, I enter a testing loop. I check if I am already waiting for the response, if not I send the command and wait for the response. If the response is received, move to another command, if it is not received wait for 100ms.

    Unfortunately, the above logic does not work since I have infinite while loop which causes the rest of my application to hang. I would like to get some advice on how this can be achieved. Thank you in advance.

    Keep in mind that I have provided example with only 3 commands. The real tests that I will he doing might have 100 or so commands so I want to be able to scale up after I get this to work with simple 3 command example.

    I have made a very simple flowchart on how things should be working:

    de74959e-aed7-4a73-b47c-d39e62d69ade-image.png

    JonBJ 1 Reply Last reply
    1
    • L lukutis222

      Hello all. I am working on QT application that will be used for testing external microcontroller devices using serial commands.

      Lets say I have a device that will respond to certain serial commands. If response is as expected, that means the device is OK, if not, that means device is faulty and should be put aside for repairs.

      I have a structure:

          struct testing_s{
              bool waiting_for_response;
              QString command;
              QString expected_response;
          };
      
          testing_s test_struct;
      

      The structure above contains a flag (waiting_for_response) that allows me to know if I have received a response or not.
      Initially when the program starts, this is set to 0 but when I send a command, I set it to 1 which means I will be waiting for the response.

      In my QT application, I am listening for any incoming serial data from the external device using

      connect(&serial_local->serial_connection, &QSerialPort::readyRead, this, &TestTool::Command_parser);
      

      When I want to send some command and expect certain response, I use the following command:

      void TestTool::Send_and_wait(QString command,QString expected_response){
          test_struct.command = command;
          test_struct.expected_response = expected_response;
          serial_local->serial_connection.write((command.toUtf8())); // send serial data
          test_struct.waiting_for_response = 1;
      }
      

      The command above will set the test_struct with the required information and send serial data to the device, then in my command_parser I am parsing all incoming data and filtering for my expected response:

      void TestTool::Command_parser()
      {
          while(serial_local->is_data_available(&serial_local->serial_connection)){
              QByteArray line = serial_local->read_data(&serial_local->serial_connection);
              QString DataAsString = QString(line);
              if(test_struct.waiting_for_response == 1){
                  qDebug("response received = %s \n",DataAsString.toStdString().c_str());
                  qDebug("expected response = %s \n",test_struct.expected_response.toStdString().c_str());
                  if(strncmp(DataAsString.toStdString().c_str(),test_struct.expected_response.toStdString().c_str(),test_struct.expected_response.length()) == 0){
                      qDebug("response matched, ready to send new command \n");
                      test_struct.waiting_for_response = 0;
                  }
              }
          }
      }
      

      If expected response is received, I am ready to send another command, if it is not received within certain time (lets say 5 seconds, then I should stop the testing and assume the device is faulty).

      In my QT application, I have a button "Start test". Once this button is pressed, I want to testing to start. I have tested this with only 1 command and it seemed to work. I send "ping" to the device and my device responded "pong".

      void TestTool::on_start_test_button_clicked(){
          qDebug("on_start_test_button_clicked \n");
          Send_and_wait("ping\n","pong");
      }
      

      The application looks like:

      0083ffd0-3f62-490a-a8c2-d5a31d9c6b89-image.png

      When the expected response is received, I will mark the radio button so the user can see at which stage the test is at any time.

      My question

      I am not fully understanding how can I ensure that more than 1 command is send one by one after I click START_TEST button. I have tried the following:

      void TestTool::on_start_test_button_clicked(){
      
          while(1){
              if(test_struct.waiting_for_response == 0){ // only allow sending command if we are currently not waiting for any respose
                  Send_and_wait("ping\n","pong\n");
              }
              else{
                  QThread::msleep(100);
              }
      
              if(test_struct.waiting_for_response == 0){ // only allow sending command if we are currently not waiting for any respose
                  Send_and_wait("test_nvs\n","OK\n");
              }
              else{
                  QThread::msleep(100);
              }
      
              if(test_struct.waiting_for_response == 0){ // only allow sending command if we are currently not waiting for any respose
                  Send_and_wait("ping\n","pong\n");
              }
              else{
                  QThread::msleep(100);
              }
          }
         
      }
      

      The function of the above is quite simple. When the button is clicked, I enter a testing loop. I check if I am already waiting for the response, if not I send the command and wait for the response. If the response is received, move to another command, if it is not received wait for 100ms.

      Unfortunately, the above logic does not work since I have infinite while loop which causes the rest of my application to hang. I would like to get some advice on how this can be achieved. Thank you in advance.

      Keep in mind that I have provided example with only 3 commands. The real tests that I will he doing might have 100 or so commands so I want to be able to scale up after I get this to work with simple 3 command example.

      I have made a very simple flowchart on how things should be working:

      de74959e-aed7-4a73-b47c-d39e62d69ade-image.png

      JonBJ Offline
      JonBJ Offline
      JonB
      wrote on last edited by
      #2

      @lukutis222
      If you want the UI to remain responsive after you click the "Start test" button you will not want a while(1) loop in it. Nor do I know what you expect to happen with your QThread::msleep() calls, which will also block the thread.

      One way to " ensure that more than 1 command is send one by one after I click START_TEST button" is to send the first command and have a slot connected to a signal emitted when the command finishes. Then the slot can move onto the next test in the sequence and repeat this.

      If you have a "large" number of commands and your nice flowchart, you might consider using Qt's QStateMachine Class as the neatest way of implementing it.

      1 Reply Last reply
      2
      • C Offline
        C Offline
        ChrisW67
        wrote on last edited by
        #3

        Thanks for a very detailed outline of what you are attempting.

        Two major differences with how I would approach this:

        • Use Qt asynchronous processing rather than trying to force synchronous behaviour. This is the key to responsiveness.
        • Maintain a list of pending commands. Process the first until completion (good or bad) then move it to a completed list (or maintain an index to the current command). Repeat until list empty/finished.

        The lists allow for arbitrary test count and probably map better to a list view for your GUI.

        Something roughly like this untested brain dump:

        #ifndef TESTRUNNER_H
        #define TESTRUNNER_H
        
        #include <QByteArray>
        #include <QObject>
        #include <QSerialPort>
        
        struct TestCase {
        public:
            TestCase(const QString &command, const QString &expect);
        
            QString m_command;
            QString m_expect;
            QString m_actual;   // what we actually got
            int m_failed;
        };
        
        
        class TestRunner : public QObject
        {
            Q_OBJECT
        public:
            explicit TestRunner(QObject *parent = nullptr);
        
            void run(const QList<TestCase> &tests);
        
        signals:
            void finished();
        
        private:
            void initSerial();
        
        private slots:
            void sendCommand();
            void dataReceived();
        
        private:
            QSerialPort m_serial;
            QList<TestCase> m_queue;
            QByteArray m_receiveBuffer;
        };
        
        #endif // TESTRUNNER_H
        
        #include "testrunner.h"
        
        #include <QTimer>
        
        TestCase::TestCase(const QString &command, const QString &expect):
            m_command(command),
            m_expect(expect),
            m_actual(),
            m_failed(0)
        {}
        
        TestRunner::TestRunner(QObject *parent)
            : QObject{parent}
        {
            initSerial();
        }
        
        void TestRunner::initSerial()
        {
            // setup comm port etc.
        
            connect(&m_serial, &QSerialPort::readyRead, this, &TestRunner::dataReceived);
        }
        
        void TestRunner::sendCommand()
        {
            if (m_queue.count() > 0) {
                // send the command at the top of the list
                m_receiveBuffer.clear();
                m_serial.write(m_queue.at(0).m_command.toUtf8());
            }
            else
                emit finished();
        }
        
        void TestRunner::run(const QList<TestCase> &tests)
        {
            m_queue = tests;
            sendCommand();
        }
        
        void TestRunner::dataReceived()
        {
            m_receiveBuffer.append(m_serial.readAll());
        
            if (int index = m_receiveBuffer.indexOf('\n') != -1) {
                // we have accumulated a complete response
                m_queue.first().m_actual = QString(m_receiveBuffer.left(index-1));
        
                if (m_queue.at(0).m_actual == m_queue.at(0).m_expect) {
                    // success, do something with that here
                    m_queue.removeFirst();
                }
                else {
                    // failure, do something with that here
                    m_queue.first().m_failed++;
                    if ( m_queue.at(0).m_failed >= 5) { // maximum fail count
                        m_queue.removeFirst();
                    }
                }
        
                // Try the first command in the queue again after a short pause.
                QTimer::singleShot(100, this, &TestRunner::sendCommand);
            }
        
        }
        
        
        
        1 Reply Last reply
        2
        • L Offline
          L Offline
          lukutis222
          wrote on last edited by
          #4

          Thank you both for good suggestions. That put me on a right track :)

          1 Reply Last reply
          0

          • Login

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