2013-02-27 27 views
6

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

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

Setup:

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 функция -.. Handled отправка и получение

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. Я не получаю никакого ответа относительно второго устройства (Light Bedroom), это сообщение, которое отправляется вторым. Кажется, что отправка блокируется, как бы вы порекомендовали мне отформатировать мою отправку, чтобы отправить сообщение после получения ответа для первой отправки? Существует только 1 COM-порт, который можно использовать для отправки/получения. Я считаю, что я должен отправить сообщение на устройство 1, получить ответ устройства 1, отправить на устройство 2, получить устройство 2. Могу ли я в конечном итоге увидеть огромную пробку с большим количеством устройств и использовать условия ожидания, т.е. дождаться завершения коммуникационного процесса устройства 1 до выполнения комм-процесса для устройства 2?

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

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

ответ

3

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

Моя реализация внимательно следит за примером «Мастер», предоставленным исходными файлами 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. 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(); 
} 

Я использовал Serial Port Monitor 4.0 (Eltima Software), чтобы проверить транзакции записи и чтения на последовательном порту. Ниже вы можете просмотреть распечатку журнала для 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 отправлений я получил тот же ответ. Таким образом, я могу с уверенностью сказать, что мои проблемы с непоследовательным приходом данных были решены. Теперь я борюсь с несколькими запросами на запись, но я считаю, что это отдельный вопрос для исследования. Я ценю поддержку каждого.

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

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

+1

Благодарим вас за комментарии. Я скорректировал свой код довольно существенно. Теперь я получаю более «последовательный» ответ, однако он по-прежнему не соответствует моим намерениям. –

+0

+1 для «невообразимого лапши-кода» ... и полезный ответ. – cgmb

+0

Проблема с использованием bytesAvailable() заключается в том, что ответ не является установленным числом байтов. Каждый ответ может быть переменной длины в зависимости от типа и формата сообщения, отправленного на устройство. –

Смежные вопросы