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;
    }
    

  • Lifetime Qt Champion

    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 thread valve->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.


  • Moderators

    @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:

    1. The GUI is terribly slow.
    2. 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.

Log in to reply
 

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