简体   繁体   English

Qt串行端口 - 一致地读取数据

[英]Qt Serial Port - Reading data consistently

I am sending (writing) bytes to a device via my serial port. 我通过串口向设备发送(写入)字节。 I am using the QSerialPort ( http://qt-project.org/wiki/QtSerialPort ) module to instantiate device IO support. 我正在使用QSerialPort( http://qt-project.org/wiki/QtSerialPort )模块来实例化设备IO支持。 When I send messages to my INSTEON modem (serial), upon reading my message the device sends back a copy of my message + 0x06 (ACK Byte) followed by a status message. 当我向INSTEON调制解调器(串行)发送消息时,在读取我的消息时,设备会发回我的消息副本+ 0x06(ACK Byte),然后是状态消息。

I have tested my message using DockLight ( http://www.docklight.de/ ). 我使用DockLight( http://www.docklight.de/ )测试了我的消息。 I send the following message to query the state of the device: 我发送以下消息来查询设备的状态:

    02 62 1D E9 4B 05 19 00

Using Docklight, I receive the response: 使用Docklight,我收到回复:

    02 62 1D E9 4B 05 19 00 06 02 50 20 CB CF 1E DA F7 21 00 FF

The returned message indicates exactly what I would expect, that the device is on. 返回的消息准确表明了我期望的设备是什么。 If off, the modem would send back 0x00 in the last byte position if the device was off. 如果关闭,如果设备关闭,调制解调器将在最后一个字节位置发回0x00。 Now, my problem - I must not have my function setup properly to send and then receive the response bytes. 现在,我的问题 - 我必须正确设置我的功能才能发送然后接收响应字节。 I have tried many different examples and configurations, currently I am using the following: 我尝试了很多不同的示例和配置,目前我正在使用以下内容:

Setup signal-slot connections: 设置信号槽连接:

QObject::connect(&thread, SIGNAL(sendResponse(QByteArray)), 
    this, SLOT(handleResponse(QByteArray)));
QObject::connect(&thread, SIGNAL(error(QString)), 
    this, SLOT(processError(QString)));
QObject::connect(&thread, SIGNAL(timeout(QString)), 
    this, SLOT(processTimeout(QString)));

Function used to iterate through QList of devices. 用于迭代设备QList的函数。 If device is desired type ("Light"), then we format the device ID to the intended QByteArray message structure. 如果设备是所需类型(“Light”),那么我们将设备ID格式化为预期的QByteArray消息结构。 Pass message to thread for sending. 将消息传递给线程以进行发送。 (Thread modified from QSerialPort BlockingMaster example. (从QSerialPort BlockingMaster示例修改的线程。

void Device::currentStatus(QList<Device *> * deviceList){
    QString devID, updateQry;
    int devStatus, updateStatus;
    updateStatus=0;
    QSqlQuery query;
    for(int i=0; i<deviceList->size(); i++){
        if(deviceList->at(i)->type == "Light"){
            devStatus = deviceList->at(i)->status;
            devID = deviceList->at(i)->deviceID;
            QByteArray msg;
            bool msgStatus;
            msg.resize(8);

            msg[0] = 0x02;
            msg[1] = 0x62;
            msg[2] = 0x00;
            msg[3] = 0x00;
            msg[4] = 0x00;
            msg[5] = 0x05;
            msg[6] = 0x19;
            msg[7] = 0x00;
            msg.replace(2, 3, QByteArray::fromHex( devID.toLocal8Bit() ) );
            qDebug() << "Has device " << deviceList->at(i)->name << "Changed?";
            //send(msg,&msgStatus, &updateStatus);
            //msg.clear();
            thread.setupPort("COM3",500,msg);
            if(devStatus!=updateStatus){
                qDebug() << deviceList->at(i)->name << " is now: " << updateStatus;
                updateStatus = !updateStatus;
            }
        }
    }
}

SetupThread function used to set local thread variables and executes (runs) thread. SetupThread函数用于设置本地线程变量并执行(运行)线程。

void serialThread::setupPort(const QString &portName, int waitTimeout, const QByteArray &msg){
    qDebug() << "Send Message " << msg.toHex();
    QMutexLocker locker(&mutex);
    this->portName = portName;
    this->waitTimeout = waitTimeout;
    this->msg = msg;
    if(!isRunning())
        start();
    else
        cond.wakeOne();
}

Run Function - Handled sending and receiving Run功能 - 处理发送和接收

void serialThread::run(){
    bool currentPortNameChanged = false;
    qDebug() << "Thread executed";
    mutex.lock();
    QString currentPortName;
    if(currentPortName != portName){
        currentPortName = portName;
        currentPortNameChanged = true;
    }

    int currentWaitTimeout = waitTimeout;
    QByteArray sendMsg = msg;
    mutex.unlock();
    QSerialPort serial;

    while(!quit){
        if(currentPortNameChanged){
            serial.close();
            serial.setPortName("COM3");

            if (!serial.open(QIODevice::ReadWrite)) {
                emit error(tr("Can't open %1, error code %2")
                           .arg(portName).arg(serial.error()));
                return;
            }

            if (!serial.setBaudRate(QSerialPort::Baud19200)) {
                emit error(tr("Can't set baud rate 9600 baud to port %1, error code %2")
                           .arg(portName).arg(serial.error()));
                return;
            }

            if (!serial.setDataBits(QSerialPort::Data8)) {
                emit error(tr("Can't set 8 data bits to port %1, error code %2")
                           .arg(portName).arg(serial.error()));
                return;
            }

            if (!serial.setParity(QSerialPort::NoParity)) {
                emit error(tr("Can't set no patity to port %1, error code %2")
                           .arg(portName).arg(serial.error()));
                return;
            }

            if (!serial.setStopBits(QSerialPort::OneStop)) {
                emit error(tr("Can't set 1 stop bit to port %1, error code %2")
                           .arg(portName).arg(serial.error()));
                return;
            }

            if (!serial.setFlowControl(QSerialPort::NoFlowControl)) {
                emit error(tr("Can't set no flow control to port %1, error code %2")
                           .arg(portName).arg(serial.error()));
                return;
            }
        }

        //write request
        serial.write(msg);
        if (serial.waitForBytesWritten(waitTimeout)) {
            //! [8] //! [10]
            // read response
            if (serial.waitForReadyRead(currentWaitTimeout)) {
                QByteArray responseData = serial.readAll();
                while (serial.waitForReadyRead(10)){
                    responseData += serial.readAll();
                }

                QByteArray response = responseData;
                //! [12]
                emit this->sendResponse(response);
                //! [10] //! [11] //! [12]
            } else {
                emit this->timeout(tr("Wait read response timeout %1")
                             .arg(QTime::currentTime().toString()));
            }
            //! [9] //! [11]
        } else {
            emit timeout(tr("Wait write request timeout %1")
                         .arg(QTime::currentTime().toString()));
        }
        mutex.lock();
        cond.wait(&mutex);
        if (currentPortName != portName) {
            currentPortName = portName;
            currentPortNameChanged = true;
        } else {
            currentPortNameChanged = false;
        }
        currentWaitTimeout = waitTimeout;
        sendMsg = msg;
        mutex.unlock();
    }
    serial.close();
}

handleResponse function, SLOT which receives response signal handleResponse函数,SLOT接收响应信号

void Device::handleResponse(const QByteArray &msg){
    qDebug() << "Read: " << msg.toHex();
}

I receive the following output: 我收到以下输出:

Has device  "Living Room Light" Changed? 
Send Message  "02621de94b051900" 
Has device  "Bedroom Light" Changed? 
Send Message  "026220cbcf051900" 
Thread executed 
Read:  "026220cbcf05190006" 
Polling for changes... 
Has device  "Living Room Light" Changed? 
Send Message  "02621de94b051900" 
Has device  "Bedroom Light" Changed? 
Send Message  "026220cbcf051900" 
Read:  "025020cbcf1edaf721000002621de94b05190006" 
Polling for changes... 
Has device  "Living Room Light" Changed? 
Send Message  "02621de94b051900" 
Has device  "Bedroom Light" Changed? 
Send Message  "026220cbcf051900" 
Read:  "02501de94b1edaf72100ff02621de94b05190006" 

Two issues here. 这里有两个问题。

  1. I never receive any response regarding the second device (Bedroom Light), this is the message that is sent second. 我从未收到有关第二个设备(卧室灯)的任何回复,这是第二个发送的消息。 It seems that the send is being blocked, how would you recommend I format my sending so that I send after the response is received for the first send? 似乎发送被阻止,你会如何建议我格式化我的发送,以便在收到第一次发送的响应后发送? There is only 1 COM port that can be used to send/receive. 只有1个COM端口可用于发送/接收。 I believe I should Send message to Device 1, receive Device 1 response, Send to Device 2, receive Device 2. Could I end up seeing a huge traffic jam with a lot of devices and using wait conditions, ie. 我相信我应该发送消息到设备1,接收设备1响应,发送到设备2,接收设备2.我最终会看到很多设备和使用等待条件的巨大交通堵塞,即。 wait for device 1 communication process to finish before executing comm process for device 2? 在执行设备2的通信过程之前,等待设备1通信过程完成?

  2. The very first read contains the appropriate 1st half of the receive. 第一次读取包含适当的前半部分接收。 Read: "026220cbcf05190006" The second receive contains the 2nd half of the 1st response followed by the 1st half of the second response: Read 2 - Read: "025020cbcf1edaf721000002621de94b05190006" The appropriate full response is 02621DE94B05190006 025020CBCF1EDAF72100FF (note 20CBCF is Device 2's ID in the full response example) Read: "026220cbcf05190006"第二次接收包含第一次响应的后半部分,然后是第二次响应的上半部分:读取2 - Read: "025020cbcf1edaf721000002621de94b05190006"相应的完整响应是02621DE94B05190006 025020CBCF1EDAF72100FF (注意20CBCF是设备2的ID完整的回复示例)

What corrections should be made to the way I am receiving data from the serial port? 我应该对从串口接收数据的方式进行哪些更正? Thank you! 谢谢!

  1. See the BlockingMaster example in the repository and read the documentation about the blocking I/O. 请参阅存储库中的BlockingMaster示例,并阅读有关阻止I / O的文档。 Also, do not use blocking I/O unnecessarily. 另外,不要不必要地使用阻塞I / O.

  2. Use bytesAvailable() to get the number of available data for reading, because not the fact that you immediately receive a complete response package. 使用bytesAvailable()来获取可用于读取的数据的数量,因为不是您立即收到完整的响应包的事实。

I believe my problems have shifted from the scope of this question. 我相信我的问题已经从这个问题的范围转移了。 With help from Kuzulis I have implemented Write/Read functions to successfully send and read serial messages consistently. 在Kuzulis的帮助下,我实现了写/读功能,可以一致地成功发送和读取串行消息。 Kuzulis recommended using the Synchronous blocking communication pattern, however it was later decided that the Asynchronous Non-Blocking method would be best fit for my application. Kuzulis建议使用同步阻塞通信模式,但后来决定异步非阻塞方法最适合我的应用程序。

My implementation closely follows the "Master" example provided with the QSerialPort source files. 我的实现紧跟在QSerialPort源文件提供的“Master”示例之后。

I use CurrentStatus to iterate through a QList of Device objects. 我使用CurrentStatus迭代Device对象的QList。 For each Light in the Device list, I format an 8 Byte message to query the current status of the device (ON/OFF). 对于设备列表中的每个Light,我格式化一个8字节消息以查询设备的当前状态(ON / OFF)。

void Device::currentStatus(QList<Device *> * deviceList){
    QString devID, updateQry;
    int devStatus, updateStatus;
    updateStatus=0;
    QSqlQuery query;
    for(int i=0; i<deviceList->size(); i++){
        if(deviceList->at(i)->type == "Light"){
            devStatus = deviceList->at(i)->status;
            devID = deviceList->at(i)->deviceID;
            QByteArray msg;
            msg.resize(8);

            msg[0] = 0x02;
            msg[1] = 0x62;
            msg[2] = 0x00;
            msg[3] = 0x00;
            msg[4] = 0x00;
            msg[5] = 0x05;
            msg[6] = 0x19;
            msg[7] = 0x00;
            msg.replace(2, 3, QByteArray::fromHex( devID.toLocal8Bit() ) );
            qDebug() << "Has device " << deviceList->at(i)->name << "Changed?";

            emit writeRequest(msg);

            if(devStatus!=updateStatus){
                qDebug() << deviceList->at(i)->name << " is now: " << updateStatus;
                updateStatus = !updateStatus;
            }
        }
    }
}

In the Device class constructor, I connect the signals and slots: 在Device类构造函数中,我连接信号和插槽:

Device::Device(){

    serialTimer.setSingleShot(true);
    QObject::connect(&serial, SIGNAL(readyRead()),
                     this, SLOT(handleResponse()));
    QObject::connect(&serialTimer, SIGNAL(timeout()),
                     this, SLOT(processTimeout()));
    QObject::connect(this, SIGNAL(writeRequest(QByteArray)),
                     this, SLOT(writeSerial(QByteArray)));
}

After the message to send in currentStatus has been prepared, emit writeRequest(msg); currentStatus中发送消息后, emit writeRequest(msg); is called. 叫做。 This dispatches a signal that is connected to the slot writeRequest . 这将调度连接到插槽writeRequest的信号。 writeRequest is used to setup and actually write the message to the serial port. writeRequest用于设置并实际将消息写入串行端口。

void Device::writeSerial(const QByteArray &msg){
    if (serial.portName() != "COM3") {
        serial.close();
        serial.setPortName("COM3");

        if (!serial.open(QIODevice::ReadWrite)) {
            processError(tr("Can't open %1, error code %2")
                         .arg(serial.portName()).arg(serial.error()));
            return;
        }

        if (!serial.setBaudRate(QSerialPort::Baud19200)) {
            processError(tr("Can't set rate 19200 baud to port %1, error code %2")
                         .arg(serial.portName()).arg(serial.error()));
            return;
        }

        if (!serial.setDataBits(QSerialPort::Data8)) {
            processError(tr("Can't set 8 data bits to port %1, error code %2")
                         .arg(serial.portName()).arg(serial.error()));
            return;
        }

        if (!serial.setParity(QSerialPort::NoParity)) {
            processError(tr("Can't set no patity to port %1, error code %2")
                         .arg(serial.portName()).arg(serial.error()));
            return;
        }

        if (!serial.setStopBits(QSerialPort::OneStop)) {
            processError(tr("Can't set 1 stop bit to port %1, error code %2")
                         .arg(serial.portName()).arg(serial.error()));
            return;
        }

        if (!serial.setFlowControl(QSerialPort::NoFlowControl)) {
            processError(tr("Can't set no flow control to port %1, error code %2")
                         .arg(serial.portName()).arg(serial.error()));
            return;
        }
    }
    qDebug() << "Message written";
    this->msgRequest = msg;
    serial.write(msgRequest);
    serialTimer.start(400);
}

After setting up the serial port, I save the current message to msgRequest . 设置串口后,我将当前消息保存到msgRequest This may have to be used to resend the message if there is an error. 如果出现错误,可能必须使用此选项重新发送消息。 After serial.write() is called, I setup a timer for 400ms. 调用serial.write()后,我设置了400ms的计时器。 Once this timer expires, I check what was read from the serial port. 一旦这个计时器到期,我检查从串口读取的内容。

handleResponse() is a slot that is called everytime QSerialPort emits the readyRead() signal. handleResponse()是每次QSerialPort发出readyRead()信号时调用的一个槽。 readyRead() appends any available data to the QByteArray response . readyRead()将任何可用数据附加到QByteArray response

void Device::handleResponse(){
    response.append(serial.readAll());  
}

After 400ms, serialTimer (one shot Timer) will emit a timeout() signal. 400ms后,serialTimer(单次定时器)将发出timeout()信号。 serialTimer was started right after writing our requested message to the serial port. 在将请求的消息写入串行端口后serialTimer启动serialTimer processTimeout() is where we finally check the response received from the PowerLinc Modem after sending our message. processTimeout()是我们在发送消息后最终检查从PowerLinc调制解调器收到的响应的地方。 When messages are sent to the INSTEON PowerLinc Modem (PLM), the PLM echoes back the message and appends either 0x06 (Positive ACK) or 0x15 (NACK). 当消息发送到INSTEON PowerLinc调制解调器(PLM)时,PLM回送消息并附加0x06(正ACK)或0x15(NACK)。 In processTimeout() I check to make sure the last byte received is the ACK byte, if not - resend our originally requested message. processTimeout()我检查以确保收到的最后一个字节是ACK字节,如果不是 - 重新发送我们最初请求的消息。

void Device::processTimeout(){
    qDebug() << "Read: " << response.toHex();
    int msgLength = this->msgRequest.length();
    if(response.at(msgLength)!=0x06){
        qDebug() << "Error, resend.";
        emit writeRequest(msgRequest);
    }
    response.clear();
}

I used the Serial Port Monitor 4.0 (Eltima Software) to verify the write and read transactions on the serial port. 我使用串行端口监视器4.0(Eltima软件)来验证串行端口上的写入和读取事务。 Below, you can see the log printout for 1 sample transaction. 在下面,您可以看到1个样本事务的日志打印输出。

20:44:30:666 STATUS_SUCCESS 02 62 1d e9 4b 05 19 00   <--- Send
20:44:30:669 STATUS_SUCCESS 02 62 1d e9 4b 05 19 00 06   <--- Receive
20:44:30:875 STATUS_SUCCESS 02  <--- Receive
20:44:30:881 STATUS_SUCCESS 50 1d e9 4b 1e da f7 21 00 ff   <--- Receive

For 20 sends, I received the same response. 对于20次发送,我收到了相同的回复。 Thus, I can safely say my issues with inconsistent data arrival have been resolved. 因此,我可以有把握地说我的数据到达不一致的问题已经解决了。 Now I am struggling with multiple write requests, but I believe that is a separate question to be investigated. 现在我正在努力处理多个写请求,但我认为这是一个需要调查的单独问题。 I appreciate everyone's support. 我感谢大家的支持。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM