Unsolved QSerialPort and QThread
-
Hi all!
I have a small project:
mainwindow.cpp
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); ComPort::get().open(); thread = new QThread(this); connect(this, SIGNAL(destroyed(QObject*)), thread, SLOT(quit())); valve = new Valve(7); connect(valve, SIGNAL(remoteStatus(bool)), this, SLOT(remoteStatus(bool))); valve->moveToThread(thread); QTimer *valvesReadTimer = new QTimer(this); connect(valvesReadTimer, SIGNAL(timeout()), valve, SLOT(getAllStates())); valvesReadTimer->start(1000); connect(passform, SIGNAL(manualModeEmit(bool)), this, SLOT(manualMode(bool))); emergency = new EmergencyResetOfPressure(); connect(emergency, SIGNAL(openValveSignal(int)), this, SLOT(openValve(int))); connect(emergency, SIGNAL(closeValveSignal(int)), this, SLOT(closeValve(int))); //emergency->start(); emergency->moveToThread(emergency); emergency->start(); thread->start(); initActionConnection(); } MainWindow::~MainWindow() { delete ui; } void MainWindow::valveSwitch(int id) //переключатель клапанов { if (valve->getState(id)) closeValve(id); else openValve(id); } void MainWindow::openValve(int id) { QString str = "Клапан №" + QString::number(id+1); valveButton[id]->setEnabled(false); if (valve->open(id)) { if (manualModeState) valveButton[id]->setEnabled(true); //valveButton[id]->setPalette(QPalette(Qt::green)); //valveButton[id]->setStyleSheet(VALVE_OPEN_COLOR); QString style = QString(DEFAULT_STYLE_BUTTON) + QString(DEFAULT_BACKGROUND_BUTTON); valveButton[id]->setStyleSheet(style); ui->mainLabel->setText(str + " открыл! :)"); } else { if (manualModeState) valveButton[id]->setEnabled(true); ui->mainLabel->setText("Не могу открыть " + str); remoteStatus(0); } } void MainWindow::closeValve(int id) { QString str = "Клапан №" + QString::number(id+1); valveButton[id]->setEnabled(false); if (valve->close(id)) { if (manualModeState) valveButton[id]->setEnabled(true); //valveButton[id]->setPalette(style()->standardPalette()); valveButton[id]->setStyleSheet(""); ui->mainLabel->setText(str + " закрыл! :)"); } else { if (manualModeState) valveButton[id]->setEnabled(true); ui->mainLabel->setText("Не могу закрыть " + str); remoteStatus(0); } } void MainWindow::pressureDrop() //Испытание по методу "Спад давления" { emergency->begin(); ui->mainLabel->setText("Испытание по методу \n Спад давления"); } void MainWindow::initActionConnection() { //обработка нажатий на кнопки клапанов QSignalMapper* signalMapper = new QSignalMapper (this); //чтобы можно было обработать ф-ю с аргументом в Слоте for(int i = 0; i < 7; i++) signalMapper->setMapping(valveButton[i], i); for(int i = 0; i < 7; i++) connect(valveButton[i], SIGNAL(clicked()), signalMapper, SLOT(map())); connect(signalMapper, SIGNAL(mapped(int)), this, SLOT(valveSwitch(int))); connect(ui->pressureTestButton, SIGNAL(clicked(bool)), this, SLOT(pressureDrop())); //опрессовка и испытание на прочность } EmergencyResetOfPressure::EmergencyResetOfPressure(QObject *parent) : QThread(parent) { } EmergencyResetOfPressure::~EmergencyResetOfPressure() { } void EmergencyResetOfPressure::begin() { for (int i = 0; i<7; i++) { //sleep(1); emit openValveSignal(i); } for (int i = 0; i<7; i++) { //sleep(1); emit closeValveSignal(i); } }
File for working with valves and port (singleton class)
class ComPort : public QObject { //класс синглтон Q_OBJECT private: QString portName; QSerialPort *serial; explicit ComPort(QObject *parent = 0); ~ComPort(); //защита от копирования ComPort(ComPort const&) = delete; ComPort& operator= (ComPort const&) = delete; int timeoutCount = 0; int responseCount = 0; public: QByteArray buffer; static ComPort& get() { static ComPort instance; return instance; } void open(); void close(); QByteArray requestResponse(const QByteArray &data); void write(const QByteArray &data); bool crcCheck(const QByteArray &data); private slots: void readData(); void handleError(QSerialPort::SerialPortError error); };
.cpp
Valve::Valve(int size, QObject *parent) : QObject(parent) //конструктор { valveState.resize(size); for(int i = 0; i < size; i++) { valveState[i] = false; } } Valve::~Valve() //деструктор { } bool Valve::open(int id) { QByteArray arr; arr.resize(7); arr[0] = 0xAB; arr[1] = 0x01; arr[2] = 0x02; arr[3] = 0x02; arr[4] = id+1; arr[5] = 0xFF; arr[6] = 0x00 - arr[1] - arr[2] - arr[3] - arr[4] - arr[5]; QByteArray response = ComPort::get().requestResponse(arr); if(response[0] == arr[0]) { qDebug() << "клапан №: " << id+1 << " открыт!"; valveState[id] = true; emit remoteStatus(1); return 1; } emit remoteStatus(0); return 0; } bool Valve::close(int id) { QByteArray arr; arr.resize(7); arr[0] = 0xAB; arr[1] = 0x01; arr[2] = 0x02; arr[3] = 0x02; arr[4] = id+1; arr[5] = 0x00; arr[6] = 0x00 - arr[1] - arr[2] - arr[3] - arr[4] - arr[5]; QByteArray response = ComPort::get().requestResponse(arr); if(response[0] == arr[0]) { qDebug() << "клапан №: " << id+1 << " закрыт!"; valveState[id] = false; emit remoteStatus(1); return 1; } emit remoteStatus(0); return 0; } /***************************************** * Класс для работы с COM портом * **************************************/ ComPort::ComPort(QObject *parent) : QObject(parent) { buffer = ""; serial = new QSerialPort(); connect(serial, SIGNAL(error(QSerialPort::SerialPortError)), this, SLOT(handleError(QSerialPort::SerialPortError))); } ComPort::~ComPort() { } void ComPort::open() { if(serial->isOpen()) close(); if(portName != Config::get().getValue("COM/name").toString()) { qDebug() << "Порт " << portName << "сменился на " << Config::get().getValue("COM/name").toString(); portName = Config::get().getValue("COM/name").toString(); } serial->setPortName(portName); if (serial->open(QIODevice::ReadWrite)) { if (serial->setBaudRate(QSerialPort::Baud115200) && serial->setFlowControl(QSerialPort::NoFlowControl)) { qDebug() << "Порт открыт"; } else { //QMessageBox::critical(this, "Error", serial->errorString()); qDebug() << QString(serial->errorString()); serial->close(); } } else { //QMessageBox::critical(this, QObject::tr("Error"), serial->errorString()); } } QByteArray ComPort::requestResponse(const QByteArray &data) { QByteArray readBuf; qDebug() << "-------------------------"; int attempts = 1; while (attempts <= REQATTEMPTS) { //3 попытки if (serial->isWritable()) { serial->write(data); qDebug() << "Попытка № " << attempts; qDebug() << "Запрос: " << data.toHex(); while (serial->waitForReadyRead(WAITFORREADY)) { readBuf += serial->readAll(); if (crcCheck(readBuf) && data[2] == readBuf[2] ){ //если CRC и команда сошлись -- успех! qDebug() << "Ответ: " << readBuf.toHex(); responseCount++; qDebug() << "Кол-во запросов: " << responseCount; qDebug() << "Кол-во таймаутов: " << timeoutCount; float percent = timeoutCount * 100; percent = percent / responseCount; qDebug() << "Процент косяков: " << QString::number(percent, 'f', 3) << "%"; close(); open(); return readBuf; } } readBuf.clear(); qDebug() << "Таймаут..."; timeoutCount++; close(); open(); attempts++; } else { qDebug() << "Порт " << portName << " не пишется!"; return 0; } } // close(); // open(); return 0; } void ComPort::close() { serial->close(); qDebug() << "Порт закрыт"; } void ComPort::write(const QByteArray &data) { serial->write(data); qDebug() << "Запрос: " << data.toHex(); } void ComPort::readData() { buffer = serial->readAll(); if (ComPort::get().crcCheck(buffer)) { qDebug() << "Ответ: " << buffer.toHex(); qDebug() << "---------------------------"; } } void ComPort::handleError(QSerialPort::SerialPortError error) { if (error == QSerialPort::ResourceError) { ComPort::get().close(); qDebug() << "что-то не так!"; } }
While working, I get an error:
QObject: Cannot create children for a parent that is in a different thread. (Parent is QSerialPort(0x197b7f8), parent's thread is QThread(0xc16e50), current thread is QThread(0x197c620)
How can I get rid of this error?
I hope for any help. Thanks! -
Which function is supposed to be executed in thread ?. For me code looked as if no function is executed in thread. Also with error I feel it is coming from getallstates. Can you paste getallstates code ? U must be passing object created in one thread as parent to another object created another thread. Just for testing purpose can avoid passing parent while creating object ?
-
In the class of the main window, I create instances of classes of sensors, valves, etc. They are interrogated with a certain frequency by the timer. I want them to work in a separate thread so that the GUI does not hang.
getAllStates:
bool Valve::getAllStates() { QByteArray arr; arr.resize(5); arr[0] = 0xAB; arr[1] = 0x01; arr[2] = 0x00; arr[3] = 0x00; arr[4] = 0x00 - arr[1] - arr[2] - arr[3]; QByteArray response = ComPort::get().requestResponse(arr); if(response[0] == arr[0]) { QBitArray bitStates(16); //8 на клапана, 8 на фиттинги for (int i = 4; i<6; i++) // конвертируем 4й и 5й байты (HEX) в биты (BIN) for (int b = 0; b<8; b++) bitStates.setBit((i-4)*8+b, response.at(i)&(1<<b)); //qDebug() << bitStates; for (int i = 0; i < valveState.size(); i++) //обновляем состояния клапанов valveState[i] = bitStates[i]; for (uint i = 0; i < sizeof(fittingState); i++) //обновляем состояния фиттингов fittingState[i] = bitStates[i+8]; emit remoteStatus(1); return 1; } emit remoteStatus(0); return 0; }
getAllStates is executed, for example, once per second. In the same thread, another function is executed with a frequency of three times per second:
bool PressureSensors::readSensors() { QByteArray arr; arr.resize(5); arr[0] = 0xAB; arr[1] = 0x01; arr[2] = 0x03; arr[3] = 0x00; arr[4] = 0x00 - arr[1] - arr[2] - arr[3]; QByteArray response = ComPort::get().requestResponse(arr); if(response[0] == arr[0]) { QByteArray dd1 = response.mid(4, 2); QByteArray dd2 = response.mid(6, 2); QByteArray dd3 = response.mid(8, 2); QByteArray dd4 = response.mid(10, 4); pressureArray[3] = hexToFloat(reverseHex(dd4)); float Pmax = 160.0; pressureArray[2] = hexToDec(reverseHex(dd3)) * Pmax / maxADC; pressureArray[1] = hexToDec(reverseHex(dd2)) * Pmax / maxADC; pressureArray[0] = hexToDec(reverseHex(dd1)) * Pmax / maxADC; emit remoteStatus(1); return 1; } emit remoteStatus(0); return 0; }
-
Hi,
Can you show the code where you do your thread handling ?
-
serial = new QSerialPort(this);
you have missed this, Luke!
PS: Don't use threads at all (if you don't know how to do it right)!
-
There are some crucial parts of your valve-class missing to be sure, but I believe valve tries to access ther SerialPort created in your main thread
ComPort::get().open();
form a different threadvalve->moveToThread(thread);
@maratk1n said in QSerialPort and QThread:
ComPort::get().open();
thread = new QThread(this); connect(this, SIGNAL(destroyed(QObject*)), thread, SLOT(quit())); valve = new Valve(7);
connect(valve, SIGNAL(remoteStatus(bool)), this, SLOT(remoteStatus(bool)));
valve->moveToThread(thread);
That won't work, you'll need to create the port in the same thread it is accessed from or handle that via SIGNAL/SLOTS at all times.
-
@kuzulis said in QSerialPort and QThread:
serial = new QSerialPort(this);
you have missed this, Luke!Tried it. Does not help.
@SGaist said in QSerialPort and QThread:
Can you show the code where you do your thread handling ?
What do you mean?..
@J.Hilk said in QSerialPort and QThread:
That won't work, you'll need to create the port in the same thread it is accessed from or handle that via SIGNAL/SLOTS at all times.
Redo the class methods in slots? But in fact the instance of the class QSerialPort will still be created in the main thread.
-
@maratk1n said in QSerialPort and QThread:
What do you mean?..
The code where you create and start your threads.
"Redo the class methods in slots?" - do not create instance variables in the constructor of QThread derived classes! Such a class is created in the main thread - that means constructor is executed in the main thread and this means - everything created in the constructor is created in main thread. Create everything your thread needs in run() - run() is executed in that new thread.
See "It is important to remember that a QThread instance lives in the old thread that instantiated it, not in the new thread that calls run()." in http://doc.qt.io/qt-5.8/qthread.html
And take a look at https://mayaposch.wordpress.com/2011/11/01/how-to-really-truly-use-qthreads-the-full-explanation/ -
@jsulm said in QSerialPort and QThread:
The code where you create and start your threads.
This is written at the very beginning of the post, if I understood correctly
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); ComPort::get().open(); thread = new QThread(this); connect(this, SIGNAL(destroyed(QObject*)), thread, SLOT(quit())); valve = new Valve(7); connect(valve, SIGNAL(remoteStatus(bool)), this, SLOT(remoteStatus(bool))); valve->moveToThread(thread); QTimer *valvesReadTimer = new QTimer(this); connect(valvesReadTimer, SIGNAL(timeout()), valve, SLOT(getAllStates())); valvesReadTimer->start(1000); connect(passform, SIGNAL(manualModeEmit(bool)), this, SLOT(manualMode(bool))); emergency = new EmergencyResetOfPressure(); connect(emergency, SIGNAL(openValveSignal(int)), this, SLOT(openValve(int))); connect(emergency, SIGNAL(closeValveSignal(int)), this, SLOT(closeValve(int))); //emergency->start(); emergency->moveToThread(emergency); emergency->start(); thread->start(); initActionConnection(); }
-
Has a little changed work with port:
bool Valve::getAllStates() { QByteArray arr; arr.resize(5); arr[0] = 0xAB; arr[1] = 0x01; arr[2] = 0x00; arr[3] = 0x00; arr[4] = 0x00 - arr[1] - arr[2] - arr[3]; emit requestResponse(arr); // QByteArray response = ComPort::get().requestResponse(arr); // if(response[0] == arr[0]) // { // QBitArray bitStates(16); //8 на клапана, 8 на фиттинги // for (int i = 4; i<6; i++) // конвертируем 4й и 5й байты (HEX) в биты (BIN) // for (int b = 0; b<8; b++) // bitStates.setBit((i-4)*8+b, response.at(i)&(1<<b)); // //qDebug() << bitStates; // for (int i = 0; i < valveState.size(); i++) //обновляем состояния клапанов // valveState[i] = bitStates[i]; // for (uint i = 0; i < sizeof(fittingState); i++) //обновляем состояния фиттингов // fittingState[i] = bitStates[i+8]; // emit remoteStatus(1); // return 1; // } // emit remoteStatus(0); // return 0; }
And I configured the connection in the designer of the valve class:
connect(this, SIGNAL(requestResponse(QByteArray)), &ComPort::get(), SLOT(requestResponse(QByteArray)));
The error has disappeared
QObject: Cannot create children for a parent that is in a different thread. (Parent is QSerialPort(0x197b7f8), parent's thread is QThread(0xc16e50), current thread is QThread(0x197c620)
But there were other problems:
- The GUI is terribly slow.
- The port class function is used by a variety of classes, so it is not clear to me how to determine which of them came the answer.