Qt последовательный порт - чтение данных последовательно

я отправляю (пишу) байты на устройство через мой последовательный порт. Я использую QSerialPort (http://qt-project.org/wiki/QtSerialPort) модуль для создания экземпляра устройства поддержки ввода-вывода. Когда я отправляю сообщения на модем INSTEON (последовательный), при чтении моего сообщения устройство отправляет копию моего сообщения + 0x06 (ACK Byte), а затем сообщение о состоянии.

я испытал мое сообщение через 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 устройств. Если устройство является желаемым типом ("свет"), то мы форматируем идентификатор устройства в предполагаемую структуру сообщений 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 функция, слот, который получает ответный сигнал

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. Могу ли я в конечном итоге увидеть огромную пробку с большим количеством устройств и использовать условия ожидания, т. е. дождитесь завершения процесса связи устройства 1 Перед выполнением процесса связи для устройства 2?

  2. самое первое чтение содержит соответствующую 1-ю половину получить. Read: "026220cbcf05190006" второй прием содержит 2-ю половину 1-го ответа, а затем 1-ю половину второго ответа: Читать 2 -Read: "025020cbcf1edaf721000002621de94b05190006" соответствующий полный ответ 02621DE94B05190006 025020CBCF1EDAF72100FF (Примечание 20CBCF является идентификатором устройства 2 в полном примере ответа)

какие исправления следует внести в способ получения данных из последовательного порта? Спасибо!

2 ответов


я считаю, что мои проблемы перешли из сферы данного вопроса. С помощью Kuzulis я реализовал функции записи / чтения для успешной отправки и чтения последовательных сообщений последовательно. Кузулис рекомендовал использовать синхронный блокирующий шаблон связи, однако позже было решено, что асинхронный неблокирующий метод лучше всего подходит для моего приложения.

моя реализация близко следует примеру "Master", предоставленному с источником QSerialPort файлы.

я использую CurrentStatus для итерации через QList Device объекты. Для каждого индикатора в списке устройств я форматирую 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(){

    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. записи запрос используется для настройте и фактически напишите сообщение к серийному порту.

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() вызывается, я устанавливаю таймер для 400ms. Как только этот таймер истекает, я проверяю, что было прочитано из последовательного порта.

handleResponse() это слот, который называется каждый раз, когда QSerialPort испускает readyRead() сигнал. readyRead() добавляет все доступные данные в класс QByteArray response.

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

после 400ms, 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 отправляет, я получил тот же ответ. Таким образом, я могу с уверенностью сказать, что мои проблемы с непоследовательным поступлением данных были решены. Теперь я борюсь с несколькими пишите запросы, но я считаю, что это отдельный вопрос для исследования. Я ценю всеобщую поддержку.


  1. см. пример BlockingMaster в репозитории и прочитайте документацию о блокирующем вводе-выводе. также не используйте блокирующий ввод-вывод без необходимости.

  2. использовать bytesAvailable (), чтобы получить количество доступных данных для чтения, потому что не факт, что вы сразу получите полный пакет ответ.