Optimizing regular QCustomPlot
-
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
andqv_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 myreadData
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 theplot_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?
-
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 toreadyRead
. 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 myMainWindow
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 callingaddPoint_current
andaddPoint_voltage
methods to append the data to the Vectors that are public variables ofserialworker
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 ofserialworker
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 theserialworker
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: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?@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. -
Don't plot too many points.
How many point do you have when it starts to bee too slow? -
@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:
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:
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 :)
-
@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:
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:
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 :)
@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. -
@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.@jsulm
Yep you are right. Callingupdate
is not necessary. Callingreplot
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 thatplot_voltage_current
take some time to execute, I only care about the fact that it does prevent myreadData
to capture the samples as the plot is being executed. -
@jsulm
Yep you are right. Callingupdate
is not necessary. Callingreplot
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 thatplot_voltage_current
take some time to execute, I only care about the fact that it does prevent myreadData
to capture the samples as the plot is being executed.@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.
-
@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.
@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 -
@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@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. -
@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.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` -
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 toreadyRead
. 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 myMainWindow
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 callingaddPoint_current
andaddPoint_voltage
methods to append the data to the Vectors that are public variables ofserialworker
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 ofserialworker
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 theserialworker
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: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? -
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 toreadyRead
. 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 myMainWindow
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 callingaddPoint_current
andaddPoint_voltage
methods to append the data to the Vectors that are public variables ofserialworker
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 ofserialworker
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 theserialworker
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: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?@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.
-
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 toreadyRead
. 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 myMainWindow
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 callingaddPoint_current
andaddPoint_voltage
methods to append the data to the Vectors that are public variables ofserialworker
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 ofserialworker
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 theserialworker
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: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?@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/readserial_worker->qv_y
/qv_y_voltage
etc. At the same timeSerialWorker::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()
onlyaddData()
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 afteraddData()
maybe when it hits the event loop if left to its own devices? Or, maybe, if you do do theaddData()
s every time but only do the replot/update on a periodic timer does that improve behaviour? -
@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/readserial_worker->qv_y
/qv_y_voltage
etc. At the same timeSerialWorker::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()
onlyaddData()
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 afteraddData()
maybe when it hits the event loop if left to its own devices? Or, maybe, if you do do theaddData()
s every time but only do the replot/update on a periodic timer does that improve behaviour?@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 threadserialwork
from receiving serial dataI 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 ofupdate_graph
slows down the other thread. I do not think this has anything to do with QCustomPlot specifically -
UPDATE
As I have mentioned in my earlier post, I was going to simplify
update_graph
and instead of plotting (lets takeQCustomPlot
away from the equation), I can useQObject().thread()->usleep
in my main thread and it should not affect theserialworker
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:
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, thereadData
is stopped for 0.5 seconds.Is this expected behaviour or am I not understanding something?
-
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 toreadyRead
. 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 myMainWindow
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 callingaddPoint_current
andaddPoint_voltage
methods to append the data to the Vectors that are public variables ofserialworker
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 ofserialworker
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 theserialworker
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: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?@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. -
@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.@jsulm
Wow well spotted. This was totally the issue. I didon_detect_button_clicked
because as soon as worker thread is started, I call thaton_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 thereadData
function.Thank you very much, although I am not so sure why that happened. Perhaps you can clarify:
- Why I cannot do:
connect(workerThread, &QThread::started, this, &MainWindow::on_detect_button_clicked);
in my
mainwindow.cpp
? How exactly it affects myserialworker
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?
-
-
-
-
@jsulm
Wow well spotted. This was totally the issue. I didon_detect_button_clicked
because as soon as worker thread is started, I call thaton_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 thereadData
function.Thank you very much, although I am not so sure why that happened. Perhaps you can clarify:
- Why I cannot do:
connect(workerThread, &QThread::started, this, &MainWindow::on_detect_button_clicked);
in my
mainwindow.cpp
? How exactly it affects myserialworker
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?
@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.
-
@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.
Thank you very much.
So since I now have a seperate thread
serialworker
running. I need to be able to update variousui
elements based on the serial data received. It has been earlier mentioned that updating theui
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 onmainwindow
class. Is using slotprocessData
correct in this particular example? I am slightly confused whether I can makeprocessData
run on a different thread (same thread asserialworker
even though it is not part ofserialworker
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 theprocessData
will still lag behind due to running on main thread that is being used by plotting the graph. -
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 toreadyRead
. 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 myMainWindow
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 callingaddPoint_current
andaddPoint_voltage
methods to append the data to the Vectors that are public variables ofserialworker
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 ofserialworker
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 theserialworker
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: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?@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 managingQThread
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 aMainWindow
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 -
@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 managingQThread
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 aMainWindow
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@Pl45m4 Thanks
@Pl45m4 said in Optimizing regular QCustomPlot:
Holy mother of C cast .... (╯°□°)╯︵ ┻━┻
:DYeah I guess thats a bit extra :D Il get it fixed :D