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. Optimizing regular QCustomPlot
Forum Updated to NodeBB v4.3 + New Features

Optimizing regular QCustomPlot

Scheduled Pinned Locked Moved Solved General and Desktop
22 Posts 6 Posters 2.2k Views 2 Watching
  • 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

    I have an application that is collecting current and voltage measurement readings (power analyzer) from external device via Serial interface and then plots a current and voltage graph.

    Some of my code:

    
    
    
    void MainWindow::setup_graph(QCustomPlot *customPlot)
    {
    
        current_upper_limit = 1;
        current_lower_limit = 0;
    
        voltage_upper_limit = 3;
        voltage_lower_limit = 0;
    
        QColor accentColor = QColor(Qt::red);
        QColor themeColor = QColor(Qt::white);
        QColor themeBackgroundColor = QColor(55, 57, 53);
    
    
        foreach (QCPAxisRect *rect, customPlot->axisRects())
        {
            foreach (QCPAxis *axis, rect->axes())
            {
                axis->setLabelColor(themeColor);
                axis->setBasePen(QPen(themeColor, 1));
                axis->setTickPen(QPen(themeColor, 1));
                axis->setSubTickPen(QPen(themeColor, 1));
                axis->setTickLabelColor(themeColor);
                axis->grid()->setPen(QPen(themeColor, 0.5, Qt::DotLine));
                axis->grid()->setSubGridVisible(false);
    
                axis->setSelectedTickLabelColor(accentColor);
                axis->setSelectedLabelColor(accentColor);
                axis->setSelectedBasePen(QPen(accentColor, 1));
                axis->setSelectedSubTickPen(QPen(accentColor, 1));
                axis->setSelectedTickPen(QPen(accentColor, 1));
            }
        }
       // customPlot->setOpenGl(1);
       // bool open_gl = customPlot->openGl();
       // qDebug("open_gl = %u \n",open_gl);
        customPlot->setBackground(QBrush(themeBackgroundColor));
    
        // SETUP GRAPH 0 for Current
        customPlot->addGraph();
        customPlot->graph(0)->setScatterStyle(QCPScatterStyle::ssCircle);
        customPlot->graph(0)->setLineStyle(QCPGraph::lsLine);
        QPen pen_current;
        pen_current.setWidth(0);
        pen_current.setColor(QColor(80, 200, 120));
        customPlot->graph(0)->setPen(pen_current);
    
    
    
        // SETUP GRAPH 1 for Voltage
        customPlot->addGraph(customPlot->xAxis, customPlot->yAxis2);
        customPlot->graph(1)->setScatterStyle(QCPScatterStyle::ssCross);
        customPlot->graph(1)->setLineStyle(QCPGraph::lsLine);
        QPen pen_voltage;
        pen_voltage.setWidth(0);
        pen_voltage.setColor(QColor(225,225,50));
        customPlot->graph(1)->setPen(pen_voltage);
    
    
        // SETUP GRAPH 2 for cursors
        customPlot->addGraph(customPlot->xAxis, customPlot->yAxis);
        customPlot->graph(2)->setLineStyle((QCPGraph::LineStyle)QCPGraph::lsNone);
        customPlot->graph(2)->setScatterStyle(QCPScatterStyle::ssTriangle);
        QPen pen_cursor;
        pen_cursor.setWidth(20);
        pen_cursor.setColor(QColor(244, 200, 0));
        customPlot->graph(2)->setPen(pen_cursor);
    
    
    
    
        customPlot->xAxis->setLabel("Time(h:m:s)");
        QSharedPointer<CustomTimeTicker> customTicker(new CustomTimeTicker);
        customPlot->xAxis->setTicker(customTicker);
    
        customPlot->yAxis->setLabel("Current (mA)");
        customPlot->yAxis->setRange(current_lower_limit, current_upper_limit);
        customPlot->yAxis2->setVisible(true);
        customPlot->yAxis2->setLabel("Voltage (V)");
        customPlot->yAxis2->setRange(voltage_lower_limit, voltage_upper_limit);
    
        connect(ui->customPlot->xAxis, SIGNAL(rangeChanged(QCPRange)), this, SLOT(xAxisChanged(QCPRange)));
    
    
    
        customPlot->legend->setVisible(true);
        customPlot->graph(0)->setName("Current");
        customPlot->graph(1)->setName("Voltage");
        customPlot->graph(2)->setName("Cursor");
    
    
        customPlot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectPlottables | QCP::iMultiSelect);
        customPlot->setSelectionRectMode(QCP::srmSelect);
    
        customPlot->graph(0)->setSelectable(QCP::stDataRange);
        customPlot->graph(1)->setSelectable(QCP::stDataRange);
        customPlot->graph(2)->setSelectable(QCP::stSingleData);
    
        //customPlot->setNoAntialiasingOnDrag(true);
    
        foreach (QCPAxisRect *rect, customPlot->axisRects())
        {
            rect->setRangeDrag(Qt::Horizontal);
            rect->setRangeZoom(Qt::Horizontal);
        }
    
    
    }
    
    
    //Append list of qv_x and qv_y which contains all current meaurement points
    void MainWindow::addPoint_current(double x, double y)
    {
        if (y > current_upper_limit)
        {
            current_upper_limit = y + 20;
            ui->customPlot->yAxis->setRange(0, current_upper_limit);
        }
        if(autoscroll_enabled == 1){
            ui->customPlot->xAxis->setRange(0, qv_x.count() / sampling_info.sampling_frequency);
        }
        qv_x.append(x);
        qv_y.append(y);
    }
    
    
    //Append list of qv_x_voltage and qv_y_voltage which contains all voltage measuremen points
    void MainWindow::addPoint_voltage(double x, double y)
    {
        if (y > voltage_upper_limit)
        {
            voltage_upper_limit = y + 2;
            ui->customPlot->yAxis2->setRange(0, voltage_upper_limit);
        }
        qv_x_voltage.append(x);
        qv_y_voltage.append(y);
    }
    
    /* 
    this is connected to readyRead signal (    connect(&serial_local->serial_connection, &QSerialPort::readyRead, this, &MainWindow::readData);
    )
    */
    void MainWindow::readData()
    {
        const QByteArray data = serial_local->serial_connection.readAll();
            if (uCurrent_plot_enabled == 1)
            {
                // Read first byte and determine OP CODE
                switch(data[0]){
                    case(0x01):
                    {
                        uint32_t voltage_data;
                        uint32_t current_data;
                        measurement_s info;
    
                        voltage_data = (uint8_t)data[2] << 24 | (uint8_t)data[3] << 16 | (uint8_t)data[4] << 8 | (uint8_t)data[5];
                        current_data = (uint8_t)data[6] << 24 | (uint8_t)data[7] << 16 | (uint8_t)data[8] << 8 | (uint8_t)data[9];
                        info.voltage = float(voltage_data/1000.0f);
                        info.current = float(current_data/1000.0f);
    
                        ui->instantenous_edit->setText(QString::number(info.current)+" mA");
                        ui->voltage_edit->setText(QString::number(info.voltage)+" V");
    
                        if (sample_counter_1_sec >= sampling_info.sampling_time && sampling_info.sampling_time != UNLIMITED)
                        {
                            qDebug("sampling time over \n");
                            ui->sampling_time_combo->setEnabled(true);
                            uCurrent_plot_enabled = 0;
                        }
    
                        sample_1_sec_sum = sample_1_sec_sum + info.current; // EVERY 1 SAMPLE CALCULATE AVERAGE OF 1 SECOND
                        if (sample_counter % sampling_info.sampling_frequency == 0 && (sample_counter != 0))
                        { // EVERY 1 SECOND
                            float avg_1_sec = Calculate_every_1_sec(sample_1_sec_sum);
    
    
                            if ((sample_counter_1_sec % 60) == 0 && (sample_counter_1_sec != 0))
                            { // EVERY MINUTE
                                final_average_s final_average = Calculate_every_1_min(sample_counter_1_sec, avg_1_sec);
                            }
                            sample_1_sec_sum = 0;
                        }
    
                        addPoint_current(double(double(sample_counter) / double(sampling_info.sampling_frequency)), info.current);
                        addPoint_voltage(double(double(sample_counter) / double(sampling_info.sampling_frequency)), info.voltage);
    
                        sample_counter++;
                        sample_counter_1_sec = sample_counter / sampling_info.sampling_frequency;
                        qDebug()<<QDateTime::currentMSecsSinceEpoch();
                        qDebug("sample_counter = %u \n",sample_counter);
                        qDebug("sample_counter_1_sec = %u \n",sample_counter_1_sec);
    
                        break;
                    }
                }
            }
    }
    

    As you can see from the functions above, I am receiving serial data at frequent intervals (10Hz or 20Hz), I then parse the data and append the QVectors:
    qv_x , qv_y , qv_x_voltage and qv_y_voltage

    I have a timer configured to fire every 1 second. This timer update and plots the graph:

    //This triggers every 1 second
    void MainWindow::update_graph()
    {
       qDebug("Plotting\n");
       plot_voltage_current(ui->customPlot);
    }
    
    void MainWindow::plot_voltage_current(QCustomPlot *customPlot)
    {
        customPlot->graph(1)->addData(qv_x_voltage, qv_y_voltage);
        customPlot->graph(0)->addData(qv_x, qv_y);
    
        customPlot->replot();
        customPlot->update();
    }
    
    
    

    The issue I am having:

    When I initially start drawing a graph, everything seems to be running smoothly. As the points on the graph increase, the plot_voltage_current takes longer and longer to execute which causes my readData function stop capturing samples at proper intervals. Even though my external device is sending data at regular intervals (for example 10Hz), the ReadData function can no longer keep up with reading and plotting at 10Hz because the plot_voltage_current method sometimes takes 100-200ms to execute which means I can not read the data during this time.

    I have even tried to setup opengl for the QCustomPlot but it did not seem to have any affect.

    I would like to get some advice on how to optimize this. What is the most optimal solution to ensure that plotting does not cause any issues?

    1 Reply Last reply
    0
    • L lukutis222

      @jsulm

      Hello again. I have been reading and learning a little bit about QThread. I have came up with a potential solution..

      I have created a serialworker class which connects to readyRead . This class responsibility is to receive and parse serial data and then append it to the Vectors that are public members of a class which will then later be used by my MainWindow class to replot the graph.

      Some code of the serialworker:

      SerialWorker::SerialWorker(QObject *parent)
          : QObject{parent}
      {
          serial = new QSerialPort(this);
          connect(serial, &QSerialPort::readyRead, this, &SerialWorker::readData);
      
      
          automatic_detection.detection_flag = 0;
          automatic_detection.current_device_id = 0;
          automatic_detection.command = "uCurrent?";
          automatic_detection.response = "uCurrent_OK";
          automatic_detection.max_retries = 3;
          automatic_detection.retry_count = 0;
          automatic_detection.timeout = 400;
      
          available_devices.resize(10);
          //timer_detect = new QTimer(this);
          //connect(timer_detect, &QTimer::timeout, this, &SerialWorker::detect_timeout);
      
      
      
      
          // set default sampling info
          sampling_info.sampling_time = UNLIMITED;
          sampling_info.sampling_frequency = 1; // 1HZ default // set 100
          sample_counter = 0;
          sample_counter_1_sec = 0;
      
          current_1_min_avg.resize(60);
          current_30_min_avg.resize(30);
          current_60_min_avg.resize(60);
          current_24_hr_avg.resize(1440);
      
      }
      
      
      void SerialWorker::readData() {
          QByteArray data = serial->readAll();
          switch(data[0])
          {
              case(0x01):
              {
                  uint32_t voltage_data;
                  uint32_t current_data;
      
                  voltage_data = (uint8_t)data[2] << 24 | (uint8_t)data[3] << 16 | (uint8_t)data[4] << 8 | (uint8_t)data[5];
                  current_data = (uint8_t)data[6] << 24 | (uint8_t)data[7] << 16 | (uint8_t)data[8] << 8 | (uint8_t)data[9];
                  float voltage = float(voltage_data/1000.0f);
                  float current = float(current_data/1000.0f);
      
      
                  addPoint_current(double(double(sample_counter) / double(20)), current);
                  addPoint_voltage(double(double(sample_counter) / double(20)), voltage);
                  sample_counter++;
                  sample_counter_1_sec = sample_counter / 20;
                  qDebug()<<QDateTime::currentMSecsSinceEpoch();
                  return;
              }
          }
      }
      
      

      As you can see, in the readData method, I am receiving serial data and calling addPoint_current and addPoint_voltage methods to append the data to the Vectors that are public variables of serialworker class.

      //Append list of qv_x and qv_y which contains all current meaurement points
      void SerialWorker::addPoint_current(double x, double y)
      {
          qv_x.append(x);
          qv_y.append(y);
      }
      
      
      //Append list of qv_x_voltage and qv_y_voltage which contains all voltage measuremen points
      void SerialWorker::addPoint_voltage(double x, double y)
      {
          qv_x_voltage.append(x);
          qv_y_voltage.append(y);
      }
      

      In my mainwindow.cpp I have the following code to instantiate serialworker class and place it another thread using moveToThread

        serial_worker = new SerialWorker();
          QThread *workerThread = new QThread(this);
          serial_worker->moveToThread(workerThread);
          connect(workerThread, &QThread::started, this, &MainWindow::on_detect_button_clicked);
          connect(this, &MainWindow::finished, serial_worker, &SerialWorker::Serial_disconnect);
          connect(this, &MainWindow::finished, workerThread, &QThread::quit);
          connect(workerThread, &QThread::finished, serial_worker, &QObject::deleteLater);
          connect(this, &MainWindow::finished, serial_worker, &SerialWorker::Serial_disconnect);
          workerThread->start();
      

      And in my Mainwindow.cpp I also have update_graph function (same as before) which is accessing public members of serialworker class to replot the graph:

      //This triggers every 1 second
      void MainWindow::update_graph()
      {
         qDebug("Plot \n");
         ui->customPlot->graph(0)->addData(serial_worker->qv_x, serial_worker->qv_y);
         ui->customPlot->graph(1)->addData(serial_worker->qv_x_voltage, serial_worker->qv_y_voltage);
         ui->customPlot->replot();
      }
      

      I have tested this out and I can confirm that this did not solve the issue. Calling update_graph method stops the serialworker class from receiving and parsing data even though it is running in a seperate thread. This is a serial log after about 10 minutes of running the program:

      image.png

      As you can see from the log, when it is time to plot the graph, it prevents serialworker to receive data which results in loosing samples. So even though I have moved data parsing into a seperate thread, the results are exactly the same as my Initial method..
      Perhaps I am misunderstanding something? Could you give some insights?

      jsulmJ Offline
      jsulmJ Offline
      jsulm
      Lifetime Qt Champion
      wrote on last edited by
      #15

      @lukutis222 said in Optimizing regular QCustomPlot:

      connect(workerThread, &QThread::started, this, &MainWindow::on_detect_button_clicked);

      Why are you starting MainWindow::on_detect_button_clicked in your worker thread?!
      You need to start a method of your worker class in the thread.
      SerialWorker should also allocate everything it needs in that method (not in constructor) to make sure everything lives in the worker thread.

      https://forum.qt.io/topic/113070/qt-code-of-conduct

      L 1 Reply Last reply
      1
      • jsulmJ Offline
        jsulmJ Offline
        jsulm
        Lifetime Qt Champion
        wrote on last edited by
        #2

        Don't plot too many points.
        How many point do you have when it starts to bee too slow?

        https://forum.qt.io/topic/113070/qt-code-of-conduct

        L 1 Reply Last reply
        0
        • jsulmJ jsulm

          Don't plot too many points.
          How many point do you have when it starts to bee too slow?

          L Offline
          L Offline
          lukutis222
          wrote on last edited by lukutis222
          #3

          @jsulm
          That is not an ideal option. Instead of not plotting too many points I'd rather plot as many points as possible in an optimal way.

          Its not even that many. To simply test this, I have added a couple of debug print statements whenever I receive new serial data. For this particular test I have configured data to be sent at 20Hz frequency. Which means I am getting new samples every 50 ms.

                              addPoint_current(double(double(sample_counter) / double(sampling_info.sampling_frequency)), info.current);
                              addPoint_voltage(double(double(sample_counter) / double(sampling_info.sampling_frequency)), info.voltage);
                              sample_counter++;
                              sample_counter_1_sec = sample_counter / sampling_info.sampling_frequency;
                              qDebug()<<QDateTime::currentMSecsSinceEpoch();
                              qDebug("sample_counter = %u \n",sample_counter);
          

          I have also added a print statement whenever the update_graph is triggered:

          //This triggers every 1 second
          void MainWindow::update_graph()
          {
             qDebug("Plotting\n");
             plot_voltage_current(ui->customPlot);
          }
          

          This is serial terminal log at the beggining of a plotting:
          e4bc702e-45af-4336-9e18-b33965fb7666-image.png

          As you can see from the log above, plotting does not add any significant delay.

          I let the plotting run for 10 minutes. In 10 minutes it should have received about 3000 samples (10 minutes * 60 seconds * 20samples per second ) = 12000 total samples which is not even that many..

          After 10 minutes (I have counted time using Stopwatch), my application only received 11500 samples. 500 samples were lost due to delay in plotting. See my debug prints towards the end:

          a743160d-0272-42ca-8124-aa05353fedb9-image.png

          As you can see I am receiving samples at regular intervals (50ms) but when plotting triggered through QTimer (every 1 second), my application stops receiving samples for about 0.3 seconds (See the epoch time printed in miliseconds) to perform the plotting. As the graph grows longer and longer, this time will increase. If I let the graph run for couple of hours, the plotting is going to take a couple of seconds.

          I hope now it is more clear what exactly is the issue. If it is not fully clear about the log prints that I have displayed I can try and explain in more detail, just let me know :)

          jsulmJ 1 Reply Last reply
          0
          • L lukutis222

            @jsulm
            That is not an ideal option. Instead of not plotting too many points I'd rather plot as many points as possible in an optimal way.

            Its not even that many. To simply test this, I have added a couple of debug print statements whenever I receive new serial data. For this particular test I have configured data to be sent at 20Hz frequency. Which means I am getting new samples every 50 ms.

                                addPoint_current(double(double(sample_counter) / double(sampling_info.sampling_frequency)), info.current);
                                addPoint_voltage(double(double(sample_counter) / double(sampling_info.sampling_frequency)), info.voltage);
                                sample_counter++;
                                sample_counter_1_sec = sample_counter / sampling_info.sampling_frequency;
                                qDebug()<<QDateTime::currentMSecsSinceEpoch();
                                qDebug("sample_counter = %u \n",sample_counter);
            

            I have also added a print statement whenever the update_graph is triggered:

            //This triggers every 1 second
            void MainWindow::update_graph()
            {
               qDebug("Plotting\n");
               plot_voltage_current(ui->customPlot);
            }
            

            This is serial terminal log at the beggining of a plotting:
            e4bc702e-45af-4336-9e18-b33965fb7666-image.png

            As you can see from the log above, plotting does not add any significant delay.

            I let the plotting run for 10 minutes. In 10 minutes it should have received about 3000 samples (10 minutes * 60 seconds * 20samples per second ) = 12000 total samples which is not even that many..

            After 10 minutes (I have counted time using Stopwatch), my application only received 11500 samples. 500 samples were lost due to delay in plotting. See my debug prints towards the end:

            a743160d-0272-42ca-8124-aa05353fedb9-image.png

            As you can see I am receiving samples at regular intervals (50ms) but when plotting triggered through QTimer (every 1 second), my application stops receiving samples for about 0.3 seconds (See the epoch time printed in miliseconds) to perform the plotting. As the graph grows longer and longer, this time will increase. If I let the graph run for couple of hours, the plotting is going to take a couple of seconds.

            I hope now it is more clear what exactly is the issue. If it is not fully clear about the log prints that I have displayed I can try and explain in more detail, just let me know :)

            jsulmJ Offline
            jsulmJ Offline
            jsulm
            Lifetime Qt Champion
            wrote on last edited by
            #4

            @lukutis222 Why do you call customPlot->update(); ? I don't think it is needed, but probably also not the reason for your problem.
            You should consider to ask QCustomPlot community as it is not part of Qt.

            https://forum.qt.io/topic/113070/qt-code-of-conduct

            L 1 Reply Last reply
            0
            • jsulmJ jsulm

              @lukutis222 Why do you call customPlot->update(); ? I don't think it is needed, but probably also not the reason for your problem.
              You should consider to ask QCustomPlot community as it is not part of Qt.

              L Offline
              L Offline
              lukutis222
              wrote on last edited by lukutis222
              #5

              @jsulm
              Yep you are right. Calling update is not necessary. Calling replot is enough.

              I have adjusted my code and fixed this issue but as you have mentioned, this is not going to solve the delay problem.

              I will ask QCustomPlot community as you have suggested.

              Do you think placing plot_voltage_current in seperate thread would be smart idea? I do not mind that plot_voltage_current take some time to execute, I only care about the fact that it does prevent my readData to capture the samples as the plot is being executed.

              jsulmJ 1 Reply Last reply
              0
              • L lukutis222

                @jsulm
                Yep you are right. Calling update is not necessary. Calling replot is enough.

                I have adjusted my code and fixed this issue but as you have mentioned, this is not going to solve the delay problem.

                I will ask QCustomPlot community as you have suggested.

                Do you think placing plot_voltage_current in seperate thread would be smart idea? I do not mind that plot_voltage_current take some time to execute, I only care about the fact that it does prevent my readData to capture the samples as the plot is being executed.

                jsulmJ Offline
                jsulmJ Offline
                jsulm
                Lifetime Qt Champion
                wrote on last edited by
                #6

                @lukutis222 said in Optimizing regular QCustomPlot:

                Do you think placing plot_voltage_current in seperate thread would be smart idea?

                No, accessing UI from other threads than UI/main thread is not supported. If you want to move something to another thread than that should be the data receiving part.

                https://forum.qt.io/topic/113070/qt-code-of-conduct

                L 1 Reply Last reply
                0
                • jsulmJ jsulm

                  @lukutis222 said in Optimizing regular QCustomPlot:

                  Do you think placing plot_voltage_current in seperate thread would be smart idea?

                  No, accessing UI from other threads than UI/main thread is not supported. If you want to move something to another thread than that should be the data receiving part.

                  L Offline
                  L Offline
                  lukutis222
                  wrote on last edited by lukutis222
                  #7

                  @jsulm
                  I see. I dont think that would do anything in this particular case since I know my application is more than capable of receiving and parsing samples at 50ms intervals within reasonable accuracy. It is just the plotting that causing the issues. Il post back an update here if I find anything

                  jsulmJ 1 Reply Last reply
                  0
                  • L lukutis222

                    @jsulm
                    I see. I dont think that would do anything in this particular case since I know my application is more than capable of receiving and parsing samples at 50ms intervals within reasonable accuracy. It is just the plotting that causing the issues. Il post back an update here if I find anything

                    jsulmJ Offline
                    jsulmJ Offline
                    jsulm
                    Lifetime Qt Champion
                    wrote on last edited by
                    #8

                    @lukutis222 said in Optimizing regular QCustomPlot:

                    I dont think that would do anything in this particular case since I know my application is more than capable of receiving and parsing samples at 50ms

                    It will make a difference, because now plotting blocks receiving because both run in the same thread.
                    But it will not solve the actual issue: slow rendering. At some point application will become unresposive because it is busy with plotting.

                    https://forum.qt.io/topic/113070/qt-code-of-conduct

                    L 1 Reply Last reply
                    0
                    • jsulmJ jsulm

                      @lukutis222 said in Optimizing regular QCustomPlot:

                      I dont think that would do anything in this particular case since I know my application is more than capable of receiving and parsing samples at 50ms

                      It will make a difference, because now plotting blocks receiving because both run in the same thread.
                      But it will not solve the actual issue: slow rendering. At some point application will become unresposive because it is busy with plotting.

                      L Offline
                      L Offline
                      lukutis222
                      wrote on last edited by lukutis222
                      #9

                      @jsulm

                      Yeah I guess you are right. I will try to implement my readData in method in seperate thread and see how it goes. Regarding the slow rendering, il ask around in QCustomPlot community`

                      1 Reply Last reply
                      0
                      • L Offline
                        L Offline
                        lukutis222
                        wrote on last edited by lukutis222
                        #10

                        @jsulm

                        Hello again. I have been reading and learning a little bit about QThread. I have came up with a potential solution..

                        I have created a serialworker class which connects to readyRead . This class responsibility is to receive and parse serial data and then append it to the Vectors that are public members of a class which will then later be used by my MainWindow class to replot the graph.

                        Some code of the serialworker:

                        SerialWorker::SerialWorker(QObject *parent)
                            : QObject{parent}
                        {
                            serial = new QSerialPort(this);
                            connect(serial, &QSerialPort::readyRead, this, &SerialWorker::readData);
                        
                        
                            automatic_detection.detection_flag = 0;
                            automatic_detection.current_device_id = 0;
                            automatic_detection.command = "uCurrent?";
                            automatic_detection.response = "uCurrent_OK";
                            automatic_detection.max_retries = 3;
                            automatic_detection.retry_count = 0;
                            automatic_detection.timeout = 400;
                        
                            available_devices.resize(10);
                            //timer_detect = new QTimer(this);
                            //connect(timer_detect, &QTimer::timeout, this, &SerialWorker::detect_timeout);
                        
                        
                        
                        
                            // set default sampling info
                            sampling_info.sampling_time = UNLIMITED;
                            sampling_info.sampling_frequency = 1; // 1HZ default // set 100
                            sample_counter = 0;
                            sample_counter_1_sec = 0;
                        
                            current_1_min_avg.resize(60);
                            current_30_min_avg.resize(30);
                            current_60_min_avg.resize(60);
                            current_24_hr_avg.resize(1440);
                        
                        }
                        
                        
                        void SerialWorker::readData() {
                            QByteArray data = serial->readAll();
                            switch(data[0])
                            {
                                case(0x01):
                                {
                                    uint32_t voltage_data;
                                    uint32_t current_data;
                        
                                    voltage_data = (uint8_t)data[2] << 24 | (uint8_t)data[3] << 16 | (uint8_t)data[4] << 8 | (uint8_t)data[5];
                                    current_data = (uint8_t)data[6] << 24 | (uint8_t)data[7] << 16 | (uint8_t)data[8] << 8 | (uint8_t)data[9];
                                    float voltage = float(voltage_data/1000.0f);
                                    float current = float(current_data/1000.0f);
                        
                        
                                    addPoint_current(double(double(sample_counter) / double(20)), current);
                                    addPoint_voltage(double(double(sample_counter) / double(20)), voltage);
                                    sample_counter++;
                                    sample_counter_1_sec = sample_counter / 20;
                                    qDebug()<<QDateTime::currentMSecsSinceEpoch();
                                    return;
                                }
                            }
                        }
                        
                        

                        As you can see, in the readData method, I am receiving serial data and calling addPoint_current and addPoint_voltage methods to append the data to the Vectors that are public variables of serialworker class.

                        //Append list of qv_x and qv_y which contains all current meaurement points
                        void SerialWorker::addPoint_current(double x, double y)
                        {
                            qv_x.append(x);
                            qv_y.append(y);
                        }
                        
                        
                        //Append list of qv_x_voltage and qv_y_voltage which contains all voltage measuremen points
                        void SerialWorker::addPoint_voltage(double x, double y)
                        {
                            qv_x_voltage.append(x);
                            qv_y_voltage.append(y);
                        }
                        

                        In my mainwindow.cpp I have the following code to instantiate serialworker class and place it another thread using moveToThread

                          serial_worker = new SerialWorker();
                            QThread *workerThread = new QThread(this);
                            serial_worker->moveToThread(workerThread);
                            connect(workerThread, &QThread::started, this, &MainWindow::on_detect_button_clicked);
                            connect(this, &MainWindow::finished, serial_worker, &SerialWorker::Serial_disconnect);
                            connect(this, &MainWindow::finished, workerThread, &QThread::quit);
                            connect(workerThread, &QThread::finished, serial_worker, &QObject::deleteLater);
                            connect(this, &MainWindow::finished, serial_worker, &SerialWorker::Serial_disconnect);
                            workerThread->start();
                        

                        And in my Mainwindow.cpp I also have update_graph function (same as before) which is accessing public members of serialworker class to replot the graph:

                        //This triggers every 1 second
                        void MainWindow::update_graph()
                        {
                           qDebug("Plot \n");
                           ui->customPlot->graph(0)->addData(serial_worker->qv_x, serial_worker->qv_y);
                           ui->customPlot->graph(1)->addData(serial_worker->qv_x_voltage, serial_worker->qv_y_voltage);
                           ui->customPlot->replot();
                        }
                        

                        I have tested this out and I can confirm that this did not solve the issue. Calling update_graph method stops the serialworker class from receiving and parsing data even though it is running in a seperate thread. This is a serial log after about 10 minutes of running the program:

                        image.png

                        As you can see from the log, when it is time to plot the graph, it prevents serialworker to receive data which results in loosing samples. So even though I have moved data parsing into a seperate thread, the results are exactly the same as my Initial method..
                        Perhaps I am misunderstanding something? Could you give some insights?

                        B JonBJ jsulmJ Pl45m4P 4 Replies Last reply
                        0
                        • L lukutis222

                          @jsulm

                          Hello again. I have been reading and learning a little bit about QThread. I have came up with a potential solution..

                          I have created a serialworker class which connects to readyRead . This class responsibility is to receive and parse serial data and then append it to the Vectors that are public members of a class which will then later be used by my MainWindow class to replot the graph.

                          Some code of the serialworker:

                          SerialWorker::SerialWorker(QObject *parent)
                              : QObject{parent}
                          {
                              serial = new QSerialPort(this);
                              connect(serial, &QSerialPort::readyRead, this, &SerialWorker::readData);
                          
                          
                              automatic_detection.detection_flag = 0;
                              automatic_detection.current_device_id = 0;
                              automatic_detection.command = "uCurrent?";
                              automatic_detection.response = "uCurrent_OK";
                              automatic_detection.max_retries = 3;
                              automatic_detection.retry_count = 0;
                              automatic_detection.timeout = 400;
                          
                              available_devices.resize(10);
                              //timer_detect = new QTimer(this);
                              //connect(timer_detect, &QTimer::timeout, this, &SerialWorker::detect_timeout);
                          
                          
                          
                          
                              // set default sampling info
                              sampling_info.sampling_time = UNLIMITED;
                              sampling_info.sampling_frequency = 1; // 1HZ default // set 100
                              sample_counter = 0;
                              sample_counter_1_sec = 0;
                          
                              current_1_min_avg.resize(60);
                              current_30_min_avg.resize(30);
                              current_60_min_avg.resize(60);
                              current_24_hr_avg.resize(1440);
                          
                          }
                          
                          
                          void SerialWorker::readData() {
                              QByteArray data = serial->readAll();
                              switch(data[0])
                              {
                                  case(0x01):
                                  {
                                      uint32_t voltage_data;
                                      uint32_t current_data;
                          
                                      voltage_data = (uint8_t)data[2] << 24 | (uint8_t)data[3] << 16 | (uint8_t)data[4] << 8 | (uint8_t)data[5];
                                      current_data = (uint8_t)data[6] << 24 | (uint8_t)data[7] << 16 | (uint8_t)data[8] << 8 | (uint8_t)data[9];
                                      float voltage = float(voltage_data/1000.0f);
                                      float current = float(current_data/1000.0f);
                          
                          
                                      addPoint_current(double(double(sample_counter) / double(20)), current);
                                      addPoint_voltage(double(double(sample_counter) / double(20)), voltage);
                                      sample_counter++;
                                      sample_counter_1_sec = sample_counter / 20;
                                      qDebug()<<QDateTime::currentMSecsSinceEpoch();
                                      return;
                                  }
                              }
                          }
                          
                          

                          As you can see, in the readData method, I am receiving serial data and calling addPoint_current and addPoint_voltage methods to append the data to the Vectors that are public variables of serialworker class.

                          //Append list of qv_x and qv_y which contains all current meaurement points
                          void SerialWorker::addPoint_current(double x, double y)
                          {
                              qv_x.append(x);
                              qv_y.append(y);
                          }
                          
                          
                          //Append list of qv_x_voltage and qv_y_voltage which contains all voltage measuremen points
                          void SerialWorker::addPoint_voltage(double x, double y)
                          {
                              qv_x_voltage.append(x);
                              qv_y_voltage.append(y);
                          }
                          

                          In my mainwindow.cpp I have the following code to instantiate serialworker class and place it another thread using moveToThread

                            serial_worker = new SerialWorker();
                              QThread *workerThread = new QThread(this);
                              serial_worker->moveToThread(workerThread);
                              connect(workerThread, &QThread::started, this, &MainWindow::on_detect_button_clicked);
                              connect(this, &MainWindow::finished, serial_worker, &SerialWorker::Serial_disconnect);
                              connect(this, &MainWindow::finished, workerThread, &QThread::quit);
                              connect(workerThread, &QThread::finished, serial_worker, &QObject::deleteLater);
                              connect(this, &MainWindow::finished, serial_worker, &SerialWorker::Serial_disconnect);
                              workerThread->start();
                          

                          And in my Mainwindow.cpp I also have update_graph function (same as before) which is accessing public members of serialworker class to replot the graph:

                          //This triggers every 1 second
                          void MainWindow::update_graph()
                          {
                             qDebug("Plot \n");
                             ui->customPlot->graph(0)->addData(serial_worker->qv_x, serial_worker->qv_y);
                             ui->customPlot->graph(1)->addData(serial_worker->qv_x_voltage, serial_worker->qv_y_voltage);
                             ui->customPlot->replot();
                          }
                          

                          I have tested this out and I can confirm that this did not solve the issue. Calling update_graph method stops the serialworker class from receiving and parsing data even though it is running in a seperate thread. This is a serial log after about 10 minutes of running the program:

                          image.png

                          As you can see from the log, when it is time to plot the graph, it prevents serialworker to receive data which results in loosing samples. So even though I have moved data parsing into a seperate thread, the results are exactly the same as my Initial method..
                          Perhaps I am misunderstanding something? Could you give some insights?

                          B Offline
                          B Offline
                          Bob64
                          wrote on last edited by Bob64
                          #11

                          @lukutis222 said in Optimizing regular QCustomPlot:

                          As you can see from the log, when it is time to plot the graph, it prevents serialworker to receive data which results in loosing samples.

                          I don't understand how this is happening if your data collection work is being done on another thread. You will need to figure out what is really happening here.

                          Edit: what JonB says below makes a lot of sense. I hadn't read your code carefully enough to see how you were communicating the data updates.

                          1 Reply Last reply
                          0
                          • L lukutis222

                            @jsulm

                            Hello again. I have been reading and learning a little bit about QThread. I have came up with a potential solution..

                            I have created a serialworker class which connects to readyRead . This class responsibility is to receive and parse serial data and then append it to the Vectors that are public members of a class which will then later be used by my MainWindow class to replot the graph.

                            Some code of the serialworker:

                            SerialWorker::SerialWorker(QObject *parent)
                                : QObject{parent}
                            {
                                serial = new QSerialPort(this);
                                connect(serial, &QSerialPort::readyRead, this, &SerialWorker::readData);
                            
                            
                                automatic_detection.detection_flag = 0;
                                automatic_detection.current_device_id = 0;
                                automatic_detection.command = "uCurrent?";
                                automatic_detection.response = "uCurrent_OK";
                                automatic_detection.max_retries = 3;
                                automatic_detection.retry_count = 0;
                                automatic_detection.timeout = 400;
                            
                                available_devices.resize(10);
                                //timer_detect = new QTimer(this);
                                //connect(timer_detect, &QTimer::timeout, this, &SerialWorker::detect_timeout);
                            
                            
                            
                            
                                // set default sampling info
                                sampling_info.sampling_time = UNLIMITED;
                                sampling_info.sampling_frequency = 1; // 1HZ default // set 100
                                sample_counter = 0;
                                sample_counter_1_sec = 0;
                            
                                current_1_min_avg.resize(60);
                                current_30_min_avg.resize(30);
                                current_60_min_avg.resize(60);
                                current_24_hr_avg.resize(1440);
                            
                            }
                            
                            
                            void SerialWorker::readData() {
                                QByteArray data = serial->readAll();
                                switch(data[0])
                                {
                                    case(0x01):
                                    {
                                        uint32_t voltage_data;
                                        uint32_t current_data;
                            
                                        voltage_data = (uint8_t)data[2] << 24 | (uint8_t)data[3] << 16 | (uint8_t)data[4] << 8 | (uint8_t)data[5];
                                        current_data = (uint8_t)data[6] << 24 | (uint8_t)data[7] << 16 | (uint8_t)data[8] << 8 | (uint8_t)data[9];
                                        float voltage = float(voltage_data/1000.0f);
                                        float current = float(current_data/1000.0f);
                            
                            
                                        addPoint_current(double(double(sample_counter) / double(20)), current);
                                        addPoint_voltage(double(double(sample_counter) / double(20)), voltage);
                                        sample_counter++;
                                        sample_counter_1_sec = sample_counter / 20;
                                        qDebug()<<QDateTime::currentMSecsSinceEpoch();
                                        return;
                                    }
                                }
                            }
                            
                            

                            As you can see, in the readData method, I am receiving serial data and calling addPoint_current and addPoint_voltage methods to append the data to the Vectors that are public variables of serialworker class.

                            //Append list of qv_x and qv_y which contains all current meaurement points
                            void SerialWorker::addPoint_current(double x, double y)
                            {
                                qv_x.append(x);
                                qv_y.append(y);
                            }
                            
                            
                            //Append list of qv_x_voltage and qv_y_voltage which contains all voltage measuremen points
                            void SerialWorker::addPoint_voltage(double x, double y)
                            {
                                qv_x_voltage.append(x);
                                qv_y_voltage.append(y);
                            }
                            

                            In my mainwindow.cpp I have the following code to instantiate serialworker class and place it another thread using moveToThread

                              serial_worker = new SerialWorker();
                                QThread *workerThread = new QThread(this);
                                serial_worker->moveToThread(workerThread);
                                connect(workerThread, &QThread::started, this, &MainWindow::on_detect_button_clicked);
                                connect(this, &MainWindow::finished, serial_worker, &SerialWorker::Serial_disconnect);
                                connect(this, &MainWindow::finished, workerThread, &QThread::quit);
                                connect(workerThread, &QThread::finished, serial_worker, &QObject::deleteLater);
                                connect(this, &MainWindow::finished, serial_worker, &SerialWorker::Serial_disconnect);
                                workerThread->start();
                            

                            And in my Mainwindow.cpp I also have update_graph function (same as before) which is accessing public members of serialworker class to replot the graph:

                            //This triggers every 1 second
                            void MainWindow::update_graph()
                            {
                               qDebug("Plot \n");
                               ui->customPlot->graph(0)->addData(serial_worker->qv_x, serial_worker->qv_y);
                               ui->customPlot->graph(1)->addData(serial_worker->qv_x_voltage, serial_worker->qv_y_voltage);
                               ui->customPlot->replot();
                            }
                            

                            I have tested this out and I can confirm that this did not solve the issue. Calling update_graph method stops the serialworker class from receiving and parsing data even though it is running in a seperate thread. This is a serial log after about 10 minutes of running the program:

                            image.png

                            As you can see from the log, when it is time to plot the graph, it prevents serialworker to receive data which results in loosing samples. So even though I have moved data parsing into a seperate thread, the results are exactly the same as my Initial method..
                            Perhaps I am misunderstanding something? Could you give some insights?

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

                            @lukutis222
                            I don't know about your behaviour, but it seems to me it ought be even slower, if you write it correctly!

                            In MainWindow::update_graph() you add data to the graph from/read serial_worker->qv_y/qv_y_voltage etc. At the same time SerialWorker::addPoint_current() may be writing to those (qv_x.append(x) etc.). Where is your "mutex" or similar? So even if this approach were to be faster I think it is "illegal/undefined" and by the time you put in a mutex it would only be slower?

                            This may not end up being the fastest way but I would start with a basic, legal way:

                            • Worker thread receives data and constructs list of new points to be added in its own variable.
                            • From time to time it sends a signal, which copies the data, to a main thread slot which adds the plots to the graph. Worker clears out its copy of sent points and starts afresh.

                            I would get that working to make sure the worker never misses a data input and the main ends up with all the points plotted. That ought work. After that work on how you might optimize the speed.

                            Alternatively, a non-thread possibility might be: get serial data in main thread but make plot_voltage_current() only addData() a chunk of them at a time, on a timer. But you would need to discover how safe a "chunk" size is OK.

                            BTW, I know nothing about QCustomPlot but my guess is that customPlot->replot()/update() are what is really slow? Do you really need those, does it do this after addData() maybe when it hits the event loop if left to its own devices? Or, maybe, if you do do the addData()s every time but only do the replot/update on a periodic timer does that improve behaviour?

                            L 1 Reply Last reply
                            1
                            • JonBJ JonB

                              @lukutis222
                              I don't know about your behaviour, but it seems to me it ought be even slower, if you write it correctly!

                              In MainWindow::update_graph() you add data to the graph from/read serial_worker->qv_y/qv_y_voltage etc. At the same time SerialWorker::addPoint_current() may be writing to those (qv_x.append(x) etc.). Where is your "mutex" or similar? So even if this approach were to be faster I think it is "illegal/undefined" and by the time you put in a mutex it would only be slower?

                              This may not end up being the fastest way but I would start with a basic, legal way:

                              • Worker thread receives data and constructs list of new points to be added in its own variable.
                              • From time to time it sends a signal, which copies the data, to a main thread slot which adds the plots to the graph. Worker clears out its copy of sent points and starts afresh.

                              I would get that working to make sure the worker never misses a data input and the main ends up with all the points plotted. That ought work. After that work on how you might optimize the speed.

                              Alternatively, a non-thread possibility might be: get serial data in main thread but make plot_voltage_current() only addData() a chunk of them at a time, on a timer. But you would need to discover how safe a "chunk" size is OK.

                              BTW, I know nothing about QCustomPlot but my guess is that customPlot->replot()/update() are what is really slow? Do you really need those, does it do this after addData() maybe when it hits the event loop if left to its own devices? Or, maybe, if you do do the addData()s every time but only do the replot/update on a periodic timer does that improve behaviour?

                              L Offline
                              L Offline
                              lukutis222
                              wrote on last edited by lukutis222
                              #13

                              @JonB
                              You are correct about me not having a mutex where I am trying to access the same data. This is not correct but for the time being I just wanted to test out the multithreading to see how they perform.

                              The fact that I do not have mutex and my solution is not very refined does not have anything to do with the fact that I now have 2 seperate threads running and somehow the plotting (that runs in the main thread) is stopping the other thread that is responsible for receiving the data.

                              @JonB said in Optimizing regular QCustomPlot:

                              This may not end up being the fastest way but I would start with a basic, legal way:

                              Worker thread receives data and constructs list of new points to be added in its own variable.
                              From time to time it sends a signal, which copies the data, to a main thread slot which adds the plots to the graph. Worker clears out its copy of sent points and starts afresh.

                              What you described here is very simillar to my current approach except not so much refined. I am collecting data in my worker thread and appending it to a vector. Then instead of sending a signal from time to time, I have a timer running on a main thread every 1 second to perform Plotting.

                              There are many methods to achieve this as well as 2 methods that you have suggested, but I am now very curious about the issue that I have with my current solution as this seem very strange. How can update_graph method that runs in the main thread stop the other thread serialwork from receiving serial data

                              I will try to simplify this even further and instead of actually plotting the data inside update_graph I can use some other operation or function that takes a while to execute to test whether the execution of update_graph slows down the other thread. I do not think this has anything to do with QCustomPlot specifically

                              1 Reply Last reply
                              0
                              • L Offline
                                L Offline
                                lukutis222
                                wrote on last edited by lukutis222
                                #14

                                UPDATE

                                As I have mentioned in my earlier post, I was going to simplify update_graph and instead of plotting (lets take QCustomPlot away from the equation), I can use QObject().thread()->usleep in my main thread and it should not affect the serialworker thread right?

                                I have updated my update_graph function:

                                void MainWindow::update_graph()
                                {
                                    QObject().thread()->usleep(1000*1000*0.5);
                                   qDebug("Plot \n");
                                   //qDebug()<<(this->thread());
                                   //plot_voltage_current(ui->customPlot);
                                }
                                

                                Now all it does it is being triggered by timer every 1 second and it sleeps for 0.5 seconds. Even though this thread is sleeping for 0.5 seconds, the other thread should continue running without any issues right?

                                But I can see that this is not how it actually works:

                                image.png

                                As you can see from the log above, I am receiving the serial data periodically every 50ms (20Hz frequency), and everytime the update_graph is triggered, the readData is stopped for 0.5 seconds.

                                Is this expected behaviour or am I not understanding something?

                                1 Reply Last reply
                                0
                                • L lukutis222

                                  @jsulm

                                  Hello again. I have been reading and learning a little bit about QThread. I have came up with a potential solution..

                                  I have created a serialworker class which connects to readyRead . This class responsibility is to receive and parse serial data and then append it to the Vectors that are public members of a class which will then later be used by my MainWindow class to replot the graph.

                                  Some code of the serialworker:

                                  SerialWorker::SerialWorker(QObject *parent)
                                      : QObject{parent}
                                  {
                                      serial = new QSerialPort(this);
                                      connect(serial, &QSerialPort::readyRead, this, &SerialWorker::readData);
                                  
                                  
                                      automatic_detection.detection_flag = 0;
                                      automatic_detection.current_device_id = 0;
                                      automatic_detection.command = "uCurrent?";
                                      automatic_detection.response = "uCurrent_OK";
                                      automatic_detection.max_retries = 3;
                                      automatic_detection.retry_count = 0;
                                      automatic_detection.timeout = 400;
                                  
                                      available_devices.resize(10);
                                      //timer_detect = new QTimer(this);
                                      //connect(timer_detect, &QTimer::timeout, this, &SerialWorker::detect_timeout);
                                  
                                  
                                  
                                  
                                      // set default sampling info
                                      sampling_info.sampling_time = UNLIMITED;
                                      sampling_info.sampling_frequency = 1; // 1HZ default // set 100
                                      sample_counter = 0;
                                      sample_counter_1_sec = 0;
                                  
                                      current_1_min_avg.resize(60);
                                      current_30_min_avg.resize(30);
                                      current_60_min_avg.resize(60);
                                      current_24_hr_avg.resize(1440);
                                  
                                  }
                                  
                                  
                                  void SerialWorker::readData() {
                                      QByteArray data = serial->readAll();
                                      switch(data[0])
                                      {
                                          case(0x01):
                                          {
                                              uint32_t voltage_data;
                                              uint32_t current_data;
                                  
                                              voltage_data = (uint8_t)data[2] << 24 | (uint8_t)data[3] << 16 | (uint8_t)data[4] << 8 | (uint8_t)data[5];
                                              current_data = (uint8_t)data[6] << 24 | (uint8_t)data[7] << 16 | (uint8_t)data[8] << 8 | (uint8_t)data[9];
                                              float voltage = float(voltage_data/1000.0f);
                                              float current = float(current_data/1000.0f);
                                  
                                  
                                              addPoint_current(double(double(sample_counter) / double(20)), current);
                                              addPoint_voltage(double(double(sample_counter) / double(20)), voltage);
                                              sample_counter++;
                                              sample_counter_1_sec = sample_counter / 20;
                                              qDebug()<<QDateTime::currentMSecsSinceEpoch();
                                              return;
                                          }
                                      }
                                  }
                                  
                                  

                                  As you can see, in the readData method, I am receiving serial data and calling addPoint_current and addPoint_voltage methods to append the data to the Vectors that are public variables of serialworker class.

                                  //Append list of qv_x and qv_y which contains all current meaurement points
                                  void SerialWorker::addPoint_current(double x, double y)
                                  {
                                      qv_x.append(x);
                                      qv_y.append(y);
                                  }
                                  
                                  
                                  //Append list of qv_x_voltage and qv_y_voltage which contains all voltage measuremen points
                                  void SerialWorker::addPoint_voltage(double x, double y)
                                  {
                                      qv_x_voltage.append(x);
                                      qv_y_voltage.append(y);
                                  }
                                  

                                  In my mainwindow.cpp I have the following code to instantiate serialworker class and place it another thread using moveToThread

                                    serial_worker = new SerialWorker();
                                      QThread *workerThread = new QThread(this);
                                      serial_worker->moveToThread(workerThread);
                                      connect(workerThread, &QThread::started, this, &MainWindow::on_detect_button_clicked);
                                      connect(this, &MainWindow::finished, serial_worker, &SerialWorker::Serial_disconnect);
                                      connect(this, &MainWindow::finished, workerThread, &QThread::quit);
                                      connect(workerThread, &QThread::finished, serial_worker, &QObject::deleteLater);
                                      connect(this, &MainWindow::finished, serial_worker, &SerialWorker::Serial_disconnect);
                                      workerThread->start();
                                  

                                  And in my Mainwindow.cpp I also have update_graph function (same as before) which is accessing public members of serialworker class to replot the graph:

                                  //This triggers every 1 second
                                  void MainWindow::update_graph()
                                  {
                                     qDebug("Plot \n");
                                     ui->customPlot->graph(0)->addData(serial_worker->qv_x, serial_worker->qv_y);
                                     ui->customPlot->graph(1)->addData(serial_worker->qv_x_voltage, serial_worker->qv_y_voltage);
                                     ui->customPlot->replot();
                                  }
                                  

                                  I have tested this out and I can confirm that this did not solve the issue. Calling update_graph method stops the serialworker class from receiving and parsing data even though it is running in a seperate thread. This is a serial log after about 10 minutes of running the program:

                                  image.png

                                  As you can see from the log, when it is time to plot the graph, it prevents serialworker to receive data which results in loosing samples. So even though I have moved data parsing into a seperate thread, the results are exactly the same as my Initial method..
                                  Perhaps I am misunderstanding something? Could you give some insights?

                                  jsulmJ Offline
                                  jsulmJ Offline
                                  jsulm
                                  Lifetime Qt Champion
                                  wrote on last edited by
                                  #15

                                  @lukutis222 said in Optimizing regular QCustomPlot:

                                  connect(workerThread, &QThread::started, this, &MainWindow::on_detect_button_clicked);

                                  Why are you starting MainWindow::on_detect_button_clicked in your worker thread?!
                                  You need to start a method of your worker class in the thread.
                                  SerialWorker should also allocate everything it needs in that method (not in constructor) to make sure everything lives in the worker thread.

                                  https://forum.qt.io/topic/113070/qt-code-of-conduct

                                  L 1 Reply Last reply
                                  1
                                  • jsulmJ jsulm

                                    @lukutis222 said in Optimizing regular QCustomPlot:

                                    connect(workerThread, &QThread::started, this, &MainWindow::on_detect_button_clicked);

                                    Why are you starting MainWindow::on_detect_button_clicked in your worker thread?!
                                    You need to start a method of your worker class in the thread.
                                    SerialWorker should also allocate everything it needs in that method (not in constructor) to make sure everything lives in the worker thread.

                                    L Offline
                                    L Offline
                                    lukutis222
                                    wrote on last edited by lukutis222
                                    #16

                                    @jsulm
                                    Wow well spotted. This was totally the issue. I did on_detect_button_clicked because as soon as worker thread is started, I call that on_detect_button_clicked method which does the following:

                                    void MainWindow::on_detect_button_clicked()
                                    {
                                        ui->uCurrent_detected_label->setText("uCurrent being detected");
                                        ui->uCurrent_detected_label->setStyleSheet("QLabel {color : rgb(255, 165, 00); font : 700 }");
                                        if (serial_worker->Scan_serial_devices() > 0)
                                        {
                                            serial_worker->Automatic_detect(0);
                                        }
                                        else
                                        {
                                            ui->uCurrent_detected_label->setText("uCurrent not detected");
                                            ui->uCurrent_detected_label->setStyleSheet("QLabel {color : rgb(255, 0, 00); font : 700 }");
                                        }
                                    }
                                    

                                    It is intended to call serial_worker public methods to automatically find the and connect to the correct serialport.

                                    If I comment out this line:

                                    connect(workerThread, &QThread::started, this, &MainWindow::on_detect_button_clicked);
                                    

                                    and I manually connect to the serial port in the worker class constructor (just for testing purposes):

                                    SerialWorker::SerialWorker(QObject *parent)
                                        : QObject{parent}
                                    {
                                        serial = new QSerialPort(this);
                                        connect(serial, &QSerialPort::readyRead, this, &SerialWorker::readData);
                                    
                                    
                                        automatic_detection.detection_flag = 0;
                                        automatic_detection.current_device_id = 0;
                                        automatic_detection.command = "uCurrent?";
                                        automatic_detection.response = "uCurrent_OK";
                                        automatic_detection.max_retries = 3;
                                        automatic_detection.retry_count = 0;
                                        automatic_detection.timeout = 400;
                                    
                                        available_devices.resize(10);
                                        //timer_detect = new QTimer(this);
                                        //connect(timer_detect, &QTimer::timeout, this, &SerialWorker::detect_timeout);
                                    
                                    
                                    
                                    
                                        // set default sampling info
                                        sampling_info.sampling_time = UNLIMITED;
                                        sampling_info.sampling_frequency = 1; // 1HZ default // set 100
                                        sample_counter = 0;
                                        sample_counter_1_sec = 0;
                                    
                                        current_1_min_avg.resize(60);
                                        current_30_min_avg.resize(30);
                                        current_60_min_avg.resize(60);
                                        current_24_hr_avg.resize(1440);
                                    
                                        serial->setPortName("COM3");  // Setting the port name to COM3
                                        serial->setBaudRate(QSerialPort::Baud115200);  // Setting the baud rate to 115200
                                        serial->setDataBits(QSerialPort::Data8);
                                        serial->setStopBits(QSerialPort::OneStop);
                                        serial->setParity(QSerialPort::NoParity);
                                        serial->setFlowControl(QSerialPort::NoFlowControl);
                                    
                                        Serial_connect();
                                    
                                    
                                    
                                    }
                                    

                                    Everything works as expected! My update_graph function no longer stops the readData function.

                                    Thank you very much, although I am not so sure why that happened. Perhaps you can clarify:

                                    1. Why I cannot do:
                                    connect(workerThread, &QThread::started, this, &MainWindow::on_detect_button_clicked);
                                    

                                    in my mainwindow.cpp ? How exactly it affects my serialworker class?

                                    @jsulm said in Optimizing regular QCustomPlot:

                                    SerialWorker should also allocate everything it needs in that method (not in constructor) to make sure everything lives in the worker thread.

                                    Can you clarify a little bit what you mean by that?

                                    jsulmJ S 2 Replies Last reply
                                    0
                                    • L lukutis222 has marked this topic as solved on
                                    • L lukutis222 has marked this topic as solved on
                                    • L lukutis222 has marked this topic as solved on
                                    • L lukutis222

                                      @jsulm
                                      Wow well spotted. This was totally the issue. I did on_detect_button_clicked because as soon as worker thread is started, I call that on_detect_button_clicked method which does the following:

                                      void MainWindow::on_detect_button_clicked()
                                      {
                                          ui->uCurrent_detected_label->setText("uCurrent being detected");
                                          ui->uCurrent_detected_label->setStyleSheet("QLabel {color : rgb(255, 165, 00); font : 700 }");
                                          if (serial_worker->Scan_serial_devices() > 0)
                                          {
                                              serial_worker->Automatic_detect(0);
                                          }
                                          else
                                          {
                                              ui->uCurrent_detected_label->setText("uCurrent not detected");
                                              ui->uCurrent_detected_label->setStyleSheet("QLabel {color : rgb(255, 0, 00); font : 700 }");
                                          }
                                      }
                                      

                                      It is intended to call serial_worker public methods to automatically find the and connect to the correct serialport.

                                      If I comment out this line:

                                      connect(workerThread, &QThread::started, this, &MainWindow::on_detect_button_clicked);
                                      

                                      and I manually connect to the serial port in the worker class constructor (just for testing purposes):

                                      SerialWorker::SerialWorker(QObject *parent)
                                          : QObject{parent}
                                      {
                                          serial = new QSerialPort(this);
                                          connect(serial, &QSerialPort::readyRead, this, &SerialWorker::readData);
                                      
                                      
                                          automatic_detection.detection_flag = 0;
                                          automatic_detection.current_device_id = 0;
                                          automatic_detection.command = "uCurrent?";
                                          automatic_detection.response = "uCurrent_OK";
                                          automatic_detection.max_retries = 3;
                                          automatic_detection.retry_count = 0;
                                          automatic_detection.timeout = 400;
                                      
                                          available_devices.resize(10);
                                          //timer_detect = new QTimer(this);
                                          //connect(timer_detect, &QTimer::timeout, this, &SerialWorker::detect_timeout);
                                      
                                      
                                      
                                      
                                          // set default sampling info
                                          sampling_info.sampling_time = UNLIMITED;
                                          sampling_info.sampling_frequency = 1; // 1HZ default // set 100
                                          sample_counter = 0;
                                          sample_counter_1_sec = 0;
                                      
                                          current_1_min_avg.resize(60);
                                          current_30_min_avg.resize(30);
                                          current_60_min_avg.resize(60);
                                          current_24_hr_avg.resize(1440);
                                      
                                          serial->setPortName("COM3");  // Setting the port name to COM3
                                          serial->setBaudRate(QSerialPort::Baud115200);  // Setting the baud rate to 115200
                                          serial->setDataBits(QSerialPort::Data8);
                                          serial->setStopBits(QSerialPort::OneStop);
                                          serial->setParity(QSerialPort::NoParity);
                                          serial->setFlowControl(QSerialPort::NoFlowControl);
                                      
                                          Serial_connect();
                                      
                                      
                                      
                                      }
                                      

                                      Everything works as expected! My update_graph function no longer stops the readData function.

                                      Thank you very much, although I am not so sure why that happened. Perhaps you can clarify:

                                      1. Why I cannot do:
                                      connect(workerThread, &QThread::started, this, &MainWindow::on_detect_button_clicked);
                                      

                                      in my mainwindow.cpp ? How exactly it affects my serialworker class?

                                      @jsulm said in Optimizing regular QCustomPlot:

                                      SerialWorker should also allocate everything it needs in that method (not in constructor) to make sure everything lives in the worker thread.

                                      Can you clarify a little bit what you mean by that?

                                      jsulmJ Offline
                                      jsulmJ Offline
                                      jsulm
                                      Lifetime Qt Champion
                                      wrote on last edited by
                                      #17

                                      @lukutis222 said in Optimizing regular QCustomPlot:

                                      How exactly it affects my serialworker class?

                                      You were calling serial_worker->Automatic_detect(0) in main thread, my guess is that then also the signals from the serial port you connected in serial_worker->Automatic_detect(0) were executed in main thread (I mean the slots were executed in main thread).

                                      "Can you clarify a little bit what you mean by that?" - everything created in the constructor of the worker thread will live in the main thread even after moveToThread. Only objects derived from QObject with "this" as parent will also be moved to worker thread when their parent ("this") is moved to worker thread. I think in your case it is fine because you set "this" as parent in QSerialPort.

                                      https://forum.qt.io/topic/113070/qt-code-of-conduct

                                      L 1 Reply Last reply
                                      2
                                      • jsulmJ jsulm

                                        @lukutis222 said in Optimizing regular QCustomPlot:

                                        How exactly it affects my serialworker class?

                                        You were calling serial_worker->Automatic_detect(0) in main thread, my guess is that then also the signals from the serial port you connected in serial_worker->Automatic_detect(0) were executed in main thread (I mean the slots were executed in main thread).

                                        "Can you clarify a little bit what you mean by that?" - everything created in the constructor of the worker thread will live in the main thread even after moveToThread. Only objects derived from QObject with "this" as parent will also be moved to worker thread when their parent ("this") is moved to worker thread. I think in your case it is fine because you set "this" as parent in QSerialPort.

                                        L Offline
                                        L Offline
                                        lukutis222
                                        wrote on last edited by lukutis222
                                        #18

                                        @jsulm

                                        Thank you very much.

                                        So since I now have a seperate thread serialworker running. I need to be able to update various ui elements based on the serial data received. It has been earlier mentioned that updating the ui via other threads is not currently supported in the QT hence I am looking for another optimal method.

                                        Do you think this method is appropriate:

                                        void SerialWorker::readData() {
                                            QByteArray data = serial->readAll();
                                            emit dataReceived(data);
                                        }
                                        

                                        And in my mainwindow.cpp

                                            connect(serial_worker, &SerialWorker::dataReceived, this, &MainWindow::processData);
                                        
                                        
                                        void MainWindow::processData(const QByteArray &data){
                                            switch(data[0])
                                            {
                                                case(0x01):
                                                {
                                                    uint32_t voltage_data;
                                                    uint32_t current_data;
                                        
                                                    voltage_data = (uint8_t)data[2] << 24 | (uint8_t)data[3] << 16 | (uint8_t)data[4] << 8 | (uint8_t)data[5];
                                                    current_data = (uint8_t)data[6] << 24 | (uint8_t)data[7] << 16 | (uint8_t)data[8] << 8 | (uint8_t)data[9];
                                                    float voltage = float(voltage_data/1000.0f);
                                                    float current = float(current_data/1000.0f);
                                        
                                        
                                                    addPoint_current(double(double(sample_counter) / double(20)), current);
                                                    addPoint_voltage(double(double(sample_counter) / double(20)), voltage);
                                                    sample_counter++;
                                                    sample_counter_1_sec = sample_counter / 20;
                                                    qDebug()<<QDateTime::currentMSecsSinceEpoch();
                                                    break;
                                                }
                                                case(0x02):
                                                {
                                                        ui->flash_label->setText("Flashing");
                                                        break;
                                                 }
                                                case(0x03):
                                                {
                                                        ui->flash_label->setText("Flashing complete");
                                                        break;
                                                }
                                            }
                                        }
                                        

                                        This way, I can still benefit of receiving serial data in a different thread but I also have an advantage since I can easily access the ui to update various elements in my application because I am on mainwindow class. Is using slot processData correct in this particular example? I am slightly confused whether I can make processData run on a different thread (same thread as serialworkereven though it is not part of serialworker class.

                                        If it will not be running on a different thread, that means that even if my serialworker is able to send a signal at regular intervals the processData will still lag behind due to running on main thread that is being used by plotting the graph.

                                        1 Reply Last reply
                                        0
                                        • L lukutis222

                                          @jsulm

                                          Hello again. I have been reading and learning a little bit about QThread. I have came up with a potential solution..

                                          I have created a serialworker class which connects to readyRead . This class responsibility is to receive and parse serial data and then append it to the Vectors that are public members of a class which will then later be used by my MainWindow class to replot the graph.

                                          Some code of the serialworker:

                                          SerialWorker::SerialWorker(QObject *parent)
                                              : QObject{parent}
                                          {
                                              serial = new QSerialPort(this);
                                              connect(serial, &QSerialPort::readyRead, this, &SerialWorker::readData);
                                          
                                          
                                              automatic_detection.detection_flag = 0;
                                              automatic_detection.current_device_id = 0;
                                              automatic_detection.command = "uCurrent?";
                                              automatic_detection.response = "uCurrent_OK";
                                              automatic_detection.max_retries = 3;
                                              automatic_detection.retry_count = 0;
                                              automatic_detection.timeout = 400;
                                          
                                              available_devices.resize(10);
                                              //timer_detect = new QTimer(this);
                                              //connect(timer_detect, &QTimer::timeout, this, &SerialWorker::detect_timeout);
                                          
                                          
                                          
                                          
                                              // set default sampling info
                                              sampling_info.sampling_time = UNLIMITED;
                                              sampling_info.sampling_frequency = 1; // 1HZ default // set 100
                                              sample_counter = 0;
                                              sample_counter_1_sec = 0;
                                          
                                              current_1_min_avg.resize(60);
                                              current_30_min_avg.resize(30);
                                              current_60_min_avg.resize(60);
                                              current_24_hr_avg.resize(1440);
                                          
                                          }
                                          
                                          
                                          void SerialWorker::readData() {
                                              QByteArray data = serial->readAll();
                                              switch(data[0])
                                              {
                                                  case(0x01):
                                                  {
                                                      uint32_t voltage_data;
                                                      uint32_t current_data;
                                          
                                                      voltage_data = (uint8_t)data[2] << 24 | (uint8_t)data[3] << 16 | (uint8_t)data[4] << 8 | (uint8_t)data[5];
                                                      current_data = (uint8_t)data[6] << 24 | (uint8_t)data[7] << 16 | (uint8_t)data[8] << 8 | (uint8_t)data[9];
                                                      float voltage = float(voltage_data/1000.0f);
                                                      float current = float(current_data/1000.0f);
                                          
                                          
                                                      addPoint_current(double(double(sample_counter) / double(20)), current);
                                                      addPoint_voltage(double(double(sample_counter) / double(20)), voltage);
                                                      sample_counter++;
                                                      sample_counter_1_sec = sample_counter / 20;
                                                      qDebug()<<QDateTime::currentMSecsSinceEpoch();
                                                      return;
                                                  }
                                              }
                                          }
                                          
                                          

                                          As you can see, in the readData method, I am receiving serial data and calling addPoint_current and addPoint_voltage methods to append the data to the Vectors that are public variables of serialworker class.

                                          //Append list of qv_x and qv_y which contains all current meaurement points
                                          void SerialWorker::addPoint_current(double x, double y)
                                          {
                                              qv_x.append(x);
                                              qv_y.append(y);
                                          }
                                          
                                          
                                          //Append list of qv_x_voltage and qv_y_voltage which contains all voltage measuremen points
                                          void SerialWorker::addPoint_voltage(double x, double y)
                                          {
                                              qv_x_voltage.append(x);
                                              qv_y_voltage.append(y);
                                          }
                                          

                                          In my mainwindow.cpp I have the following code to instantiate serialworker class and place it another thread using moveToThread

                                            serial_worker = new SerialWorker();
                                              QThread *workerThread = new QThread(this);
                                              serial_worker->moveToThread(workerThread);
                                              connect(workerThread, &QThread::started, this, &MainWindow::on_detect_button_clicked);
                                              connect(this, &MainWindow::finished, serial_worker, &SerialWorker::Serial_disconnect);
                                              connect(this, &MainWindow::finished, workerThread, &QThread::quit);
                                              connect(workerThread, &QThread::finished, serial_worker, &QObject::deleteLater);
                                              connect(this, &MainWindow::finished, serial_worker, &SerialWorker::Serial_disconnect);
                                              workerThread->start();
                                          

                                          And in my Mainwindow.cpp I also have update_graph function (same as before) which is accessing public members of serialworker class to replot the graph:

                                          //This triggers every 1 second
                                          void MainWindow::update_graph()
                                          {
                                             qDebug("Plot \n");
                                             ui->customPlot->graph(0)->addData(serial_worker->qv_x, serial_worker->qv_y);
                                             ui->customPlot->graph(1)->addData(serial_worker->qv_x_voltage, serial_worker->qv_y_voltage);
                                             ui->customPlot->replot();
                                          }
                                          

                                          I have tested this out and I can confirm that this did not solve the issue. Calling update_graph method stops the serialworker class from receiving and parsing data even though it is running in a seperate thread. This is a serial log after about 10 minutes of running the program:

                                          image.png

                                          As you can see from the log, when it is time to plot the graph, it prevents serialworker to receive data which results in loosing samples. So even though I have moved data parsing into a seperate thread, the results are exactly the same as my Initial method..
                                          Perhaps I am misunderstanding something? Could you give some insights?

                                          Pl45m4P Offline
                                          Pl45m4P Offline
                                          Pl45m4
                                          wrote on last edited by Pl45m4
                                          #19

                                          @lukutis222 said in Optimizing regular QCustomPlot:

                                          QThread *workerThread = new QThread(this);
                                          serial_worker->moveToThread(workerThread);
                                          connect(workerThread, &QThread::started, this, &MainWindow::on_detect_button_clicked);
                                          connect(this, &MainWindow::finished, serial_worker, &SerialWorker::Serial_disconnect);
                                          connect(this, &MainWindow::finished, workerThread, &QThread::quit);
                                          connect(workerThread, &QThread::finished, serial_worker, &QObject::deleteLater);
                                          connect(this, &MainWindow::finished, serial_worker, &SerialWorker::Serial_disconnect);
                                          

                                          Hi, I would also remove the parent-child coupling of your MainWindow and your secondary thread managing QThread object .

                                          QThread *workerThread = new QThread();
                                          // then, delete QThread when done using this connection:
                                          // ICYW: QThread::quit() will emit QThread::finished() before exiting
                                          connect( workerThread, &QThread::finished, workerThread, &QThread::deleteLater );
                                          

                                          Or, as the snippet from the documentation even shows, you can put your workerThread as a MainWindow member variable on stack. Then there's no need to worry about its deletion.

                                          Even if it makes no difference in your case, but to maintain consistency, I would change

                                          // this:
                                          connect(workerThread, &QThread::finished, serial_worker, &QObject::deleteLater);
                                          // to this:
                                          connect(workerThread, &QThread::finished, serial_worker, &SerialWorker::deleteLater);
                                          

                                          Edit:

                                          @lukutis222 said in Optimizing regular QCustomPlot:

                                          float voltage = float(voltage_data/1000.0f);
                                          float current = float(current_data/1000.0f);
                                          addPoint_current(double(double(sample_counter) / double(20)), current);
                                          addPoint_voltage(double(double(sample_counter) / double(20)), voltage);
                                          

                                          Holy mother of C cast .... (╯°□°)╯︵ ┻━┻
                                          :D


                                          If debugging is the process of removing software bugs, then programming must be the process of putting them in.

                                          ~E. W. Dijkstra

                                          L 1 Reply Last reply
                                          0
                                          • Pl45m4P Pl45m4

                                            @lukutis222 said in Optimizing regular QCustomPlot:

                                            QThread *workerThread = new QThread(this);
                                            serial_worker->moveToThread(workerThread);
                                            connect(workerThread, &QThread::started, this, &MainWindow::on_detect_button_clicked);
                                            connect(this, &MainWindow::finished, serial_worker, &SerialWorker::Serial_disconnect);
                                            connect(this, &MainWindow::finished, workerThread, &QThread::quit);
                                            connect(workerThread, &QThread::finished, serial_worker, &QObject::deleteLater);
                                            connect(this, &MainWindow::finished, serial_worker, &SerialWorker::Serial_disconnect);
                                            

                                            Hi, I would also remove the parent-child coupling of your MainWindow and your secondary thread managing QThread object .

                                            QThread *workerThread = new QThread();
                                            // then, delete QThread when done using this connection:
                                            // ICYW: QThread::quit() will emit QThread::finished() before exiting
                                            connect( workerThread, &QThread::finished, workerThread, &QThread::deleteLater );
                                            

                                            Or, as the snippet from the documentation even shows, you can put your workerThread as a MainWindow member variable on stack. Then there's no need to worry about its deletion.

                                            Even if it makes no difference in your case, but to maintain consistency, I would change

                                            // this:
                                            connect(workerThread, &QThread::finished, serial_worker, &QObject::deleteLater);
                                            // to this:
                                            connect(workerThread, &QThread::finished, serial_worker, &SerialWorker::deleteLater);
                                            

                                            Edit:

                                            @lukutis222 said in Optimizing regular QCustomPlot:

                                            float voltage = float(voltage_data/1000.0f);
                                            float current = float(current_data/1000.0f);
                                            addPoint_current(double(double(sample_counter) / double(20)), current);
                                            addPoint_voltage(double(double(sample_counter) / double(20)), voltage);
                                            

                                            Holy mother of C cast .... (╯°□°)╯︵ ┻━┻
                                            :D

                                            L Offline
                                            L Offline
                                            lukutis222
                                            wrote on last edited by lukutis222
                                            #20

                                            @Pl45m4 Thanks

                                            @Pl45m4 said in Optimizing regular QCustomPlot:

                                            Holy mother of C cast .... (╯°□°)╯︵ ┻━┻
                                            :D

                                            Yeah I guess thats a bit extra :D Il get it fixed :D

                                            JonBJ 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