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