c++ - Qt Serial Port - 一致读取数据

标签 c++ qt serial-port qtserialport

我正在通过串行端口向设备发送(写入)字节。我正在使用 QSerialPort ( http://qt-project.org/wiki/QtSerialPort ) 模块来实例化设备 IO 支持。当我向我的 INSTEON 调制解调器(串行)发送消息时,设备会在读取我的消息后发回我的消息拷贝 + 0x06(ACK 字节),然后是状态消息。

我已经使用 DockLight ( http://www.docklight.de/ ) 测试了我的消息。我发送以下消息来查询设备的状态:

    02 62 1D E9 4B 05 19 00

使用 Docklight,我收到响应:

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

返回的消息完全符合我的预期,即设备已打开。如果关闭,调制解调器将在设备关闭时在最后一个字节位置发回 0x00。现在,我的问题 - 我必须没有正确设置我的函数来发送和接收响应字节。我尝试了许多不同的示例和配置,目前我正在使用以下内容:

设置信号槽连接:

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

用于遍历设备 QList 的函数。如果设备是所需的类型(“Light”),那么我们将设备 ID 格式化为预期的 QByteArray 消息结构。将消息传递给线程进行发送。 (从 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 函数用于设置本地线程变量并执行(运行)线程。

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 函数 - 处理发送和接收

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函数,接收响应信号的SLOT

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

我收到以下输出:

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" 

这里有两个问题。

  1. 我从未收到关于第二个设备(卧室灯)的任何响应,这是第二个发送的消息。似乎发送被阻止了,你会如何建议我格式化我的发送,以便在收到第一次发送的响应后发送?只有 1 个 COM 端口可用于发送/接收。我相信我应该向设备 1 发送消息,接收设备 1 响应,发送到设备 2,接收设备 2。我最终会看到大量设备和使用等待条件的巨大交通拥堵吗?在执行设备 2 的通信过程之前,是否等待设备 1 的通信过程完成?

  2. 第一个读取包含接收的适当的第一半。 读取:“026220cbcf05190006”第二个接收包含第一个响应的第二半,然后是第二个响应的第一半:读取 2 - 读取:“025020cbcf1edaf721000002621de94b05190006” 适当的完整的响应是 02621DE94B05190006 025020CBCF1EDAF72100FF (注意 20CBCF 是完整响应示例中设备 2 的 ID)

应该对我从串行端口接收数据的方式进行哪些更正? 谢谢!

最佳答案

我相信我的问题已经从这个问题的范围转移了。在 Kuzulis 的帮助下,我实现了写入/读取功能,以一致地成功发送和读取串行消息。 Kuzulis 建议使用同步阻塞通信模式,但后来决定异步非阻塞方法最适合我的应用程序。

我的实现严格遵循 QSerialPort 源文件提供的“主”示例。

我使用 CurrentStatus 遍历 Device 对象的 QList。对于设备列表中的每个灯,我设置了一条 8 字节消息的格式来查询设备的当前状态(开/关)。

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

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

准备好要在 currentStatus 中发送的消息后,调用 emit writeRequest(msg);。这会调度一个连接到插槽 writeRequest 的信号。 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);
}

设置串口后,我将当前消息保存到msgRequest。如果出现错误,可能必须使用它来重新发送消息。在调用 serial.write() 之后,我设置了一个 400 毫秒的计时器。一旦这个计时器到期,我检查从串行端口读取的内容。

handleResponse() 是每次 QSerialPort 发出 readyRead() 信号时调用的槽。 readyRead() 将任何可用数据附加到 QByteArray response

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

400 毫秒后,serialTimer(单次定时器)将发出一个timeout() 信号。 serialTimer 在将我们请求的消息写入串行端口后立即启动。 processTimeout() 是我们在发送消息后最终检查从 PowerLinc 调制解调器收到的响应的地方。当消息发送到 INSTEON PowerLinc 调制解调器 (PLM) 时,PLM 回显消息并附加 0x06(肯定 ACK)或 0x15(NACK)。在 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();
}

我使用串行端口监视器 4.0(Eltima 软件)来验证串行端口上的写入和读取事务。下面,您可以看到 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

对于 20 次发送,我收到了相同的响应。因此,我可以肯定地说我的数据到达不一致问题已经解决。现在我正在努力处理多个写请求,但我相信这是一个需要调查的单独问题。感谢大家的支持。

关于c++ - Qt Serial Port - 一致读取数据,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15103599/

相关文章:

python - 我可以在 Linux 上使用 RS-232 并行端口吗?

.NET SerialPort DataReceived 事件未触发

c++ - 计算数组行中的总和

c# - 关于我的C编程中的逻辑方程

java - 变换矩阵集属性

c++ - 在Qt项目中包含dlib

c++ - Qt 链接问题

c++ - 设置 PATH 变量不起作用

c++ - QMetaObject 的 QT 单元测试 moc "unresolved external symbol"

c - 通过 termios 对伪终端通信进行编程。奇偶校验位选项不起作用