Qt Serial Port — Чтение данных последовательно

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

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

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

6

Решение

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

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

3

По вопросам рекламы [email protected]