Russian Qt Forum

Qt => Вопросы новичков => Тема начата: mohax от Сентябрь 25, 2021, 08:28



Название: Проблема с получением данных с помощью QSerialPort::waitForReadyRead()
Отправлено: mohax от Сентябрь 25, 2021, 08:28
Доброго всем дня

Версия Qt 5.14.2

Суть проблемы.
Реализуется синхронный обмен данными с устройством. Необходим постоянный опрос данных по шине. Устройству посылаются команды, на которые получаются подтверждения выполнения, далее тут же идет опрос данных с их подтверждениями.
Реализовать это с помощью механизма сигнал-слот чрезвычайно запутанно и геморрно.
Наиболее подходящий метод - это реализация работы с последовательным портом с помощью waitForReadyRead() и waitForBytesWritten() в отдельном неграфическом потоке, в котором идет постоянный опрос шины(запись команд в порт и считывание данных с порта).
Тем более, что есть в Qt пример работы Blocking Master и Blocking Slave.

Так вот столкнулся с проблемой,что, послав запрос на пакет данных, waitForReadyRead() сообщает,что есть данные, но с помощью readAll() считывается только часть пришедшего пакета, а потом хоть сиди в этом waitForReadyRead() - он уже не сообщает о наличии данных. Хотя полностью весь пакет пришел - это видно с помощью шпиона компорта,который пришлось подключить для прослушки. Когда же я повторно запрашиваю данные, то пара waitForReadyRead()  - readAll() выдает мне вместе вторую часть непришедшего вовремя пакета и часть следующего пакета.
Но это для моей задачи уже бесполезно,т.к. я не могу дать подтверждение на пакетное получение данных.

кусок проги такой
Код:
 // запись данных
  serial.write(dataRequest);
  // ожидание записи в порт
  if (serial.waitForBytesWritten(100) == false) return false;

  // ожидание прихода полного кадра
  flag = true;
  *receivear = new QByteArray();
  receive = *receivear;

  do{
    // ждем ответные данные
    if (serial.waitForReadyRead(100) == false){
      qDebug() << "fail";
      return false;
    };

    // считывание данных
    readData = serial.readAll(); while (serial.waitForReadyRead(10)) readData += serial.readAll();
    qDebug() << "Data read: " << readData.toHex(' ');

    // анализ кадра
    if (cadrAnalysis(readData, *receive) == true) flag = false;
  } while (flag);
Этот кусок кода из функции,которая вызывается из void ProtocolThread::run(),соответственно, ProtocolThread это потомок QThread.

Я вполне понимаю,что сейчас прибегут гуру и скажут, что надо работать с помощью механизма "сигнал-слот", но, извините, Qt задекларировала функцию waitForReadyRead(), и она должна работать нормально, а не через пень колоду. Да и механизм "сигнал-слот" в моем случае не подходит.

Вопрос такой у меня: что можно сделать с этим waitForReadyRead(), чтобы все заработало? Если делаю вызов waitForReadyRead(-1), то просто поток блокируется на этом вызове, хотя данные из устройства в компорт пришли.

Согласно справке, waitForReadyRead() блокируется до тех пор, пока новые данные не будут доступны для чтения  и не будет выдан сигнал readyRead(). Получается,что на вторую часть пакета этот сигнал не выдается и waitForReadyRead() не разблокируется.
Попробовал сделать опрос с помощью механизма "сигнал-слот", подготовив предварительно устройство к опросу с помощью другой программы, то в этом случае видно,что часто пакет считывается полностью после обработки двух последовательно пришедших сигналов readyRead()

ПС. Эта проблема заметилась,когда начал читать пакеты с длиной более 250 байт. Когда надо было читать пакеты с длиной 64 байта, было все нормально


Название: Re: Проблема с получением данных с помощью QSerialPort::waitForReadyRead()
Отправлено: qate от Сентябрь 25, 2021, 11:59
бред в коде написан, везде

переделай на события !


Название: Re: Проблема с получением данных с помощью QSerialPort::waitForReadyRead()
Отправлено: mohax от Сентябрь 25, 2021, 12:27
бред в коде написан, везде

переделай на события !

т.е. бред написан,значит, в примерах Qt? это во-первых.
Во-вторых, сама функция waitForReadyRead() разработчиками QSerialPort должна работать как заявлено и тогда код тоже будет работать окей



Название: Re: Проблема с получением данных с помощью QSerialPort::waitForReadyRead()
Отправлено: Igors от Сентябрь 25, 2021, 12:55
Так вот столкнулся с проблемой,что, послав запрос на пакет данных, waitForReadyRead() сообщает,что есть данные, но с помощью readAll() считывается только часть пришедшего пакета, а потом хоть ..
А Вы рассчитывали что придет весь пакет, потому что "времени дали достаточно"? На это полагаться не стоит, ну хотя бы потому что может прийти и кусок следующего или вообще 100 пакетов.

Когда же я повторно запрашиваю данные, то пара waitForReadyRead()  - readAll() выдает мне вместе вторую часть непришедшего вовремя пакета и часть следующего пакета.
Нет никакого "вовремя", когда придет - тогда и придет. И сколько придет - хз. Если хотите синхронку нужно буферировать самому и повторять "пару" до тех пор пока пакет не будет полностью вычитан. Начинать с проверки что пакет уже в Вашем буфере.

Qt задекларировала функцию waitForReadyRead(), и она должна работать нормально, а не через пень колоду.
Ну так обращайтесь к Qt :) И еще есть народная примета: если человек не поздоровался создавая тему - отвечать ему не стоит. Ладно, нарушим правило и посмотрим  :)


Название: Re: Проблема с получением данных с помощью QSerialPort::waitForReadyRead()
Отправлено: sergek от Сентябрь 25, 2021, 13:54
Реализовать это с помощью механизма сигнал-слот чрезвычайно запутанно и геморрно.
К счастью, это вина не самого механизма, а некоторой спутанности вашего сознания :)
Цитировать
Я вполне понимаю,что сейчас прибегут гуру и скажут, что надо работать с помощью механизма "сигнал-слот", но,
Странный подход - спросить совета и тут же отказаться от помощи наиболее опытных участников форума.
Вы сначала определитесь - хотите понять, как организовать синхронный обмен с портом или нет. Если появится желание, то поможем.
Прошу прощения, себя к гуру никоим образом не отношу, но есть рабочие схемы.


Название: Re: Проблема с получением данных с помощью QSerialPort::waitForReadyRead()
Отправлено: mohax от Сентябрь 25, 2021, 14:22
Вы сначала определитесь - хотите понять, как организовать синхронный обмен с портом или нет. Если появится желание, то поможем.
Очень хочу,но мне непонятно, как организовать синхронный обмен с помощью сигналов и слотов.
Как идет работа с шиной у нас:
операция чтения данных происходит в последовательности:
1. запрос данных
2. чтение данных
3. запрос статуса
4. чтение статуса для подтверждения чтения данных

операция записи команд/данных происходит в последовательности:
1. запись команды
2.чтение статуса для подтверждения записи команд/данных

внутри этих блоков все должно идти последовательно.
Читаться данные должны как можно быстрее и постоянно.
Сделал отдельный неграфический поток, в котором в функции run() идет постоянный опрос шины через QSerialPort, чтоб не зависал графич.интерфейс

Если делать с помощью сигналов-слотов, то надо же как-то синхронизировать отправку сигнала с получением данных, да еще чтоб друг за другом все шло. Вот это непонятно,как сделать.









Название: Re: Проблема с получением данных с помощью QSerialPort::waitForReadyRead()
Отправлено: kuzulis от Сентябрь 25, 2021, 16:05
Жаль что кутешники не выкинули эти блокирующие вызовы вообще из АПИ. Эти вызовы только все усложняют, как реализацию самих кутешных модулей, так и всего прочего.

Проще все делается на сигналах/слотах, я не понимаю, в чем проблема вообще?

1. Методом QSP:::write() пиши данные (шли команды/запросы)
2. В обработчике Foo::onReadyRead() парси пришедшие данные/ответы.

Код
C++ (Qt)
void Foo::sendDataRequest()
{
   ...
   serial->write(request);
   ...
}
 
void Foo::sendStatusRequest()
{
   ...
   serial->write(request);
   ...
}
 
void Foo::onReadyRead()
{
   m_packet += serial->readAll();
 
   // Проверяем пакет. Если приняли не полный пакет - выходим.
   if (notFullPackage())
       return;
 
   // Если пакет - есть ответ данных, то шлем запрос статуса.
   if (packageIsData()) {
       sendStatusRequest();
       return;
   }
 
   // Если пакет - есть ответ статуса, то делаем что-то еще.
   if (packageIsStatus()) {
       doSomething();
       return;
   }
}
 
Foo::onStartTransaction()
{
   sendDataRequest();
}
 

это в общих чертах. Что тут сложного то?


Название: Re: Проблема с получением данных с помощью QSerialPort::waitForReadyRead()
Отправлено: sergek от Сентябрь 25, 2021, 16:32
Если делать с помощью сигналов-слотов, то надо же как-то синхронизировать отправку сигнала с получением данных, да еще чтоб друг за другом все шло. Вот это непонятно,как сделать.
Так это отдельный вопрос ;) Это можно реализовать с помощью очереди запросов. А там свои заморочки. Я попробую подготовить упрощенный пример, заодно дам возможность попинать меня гуру))


Название: Re: Проблема с получением данных с помощью QSerialPort::waitForReadyRead()
Отправлено: sergek от Сентябрь 25, 2021, 16:47
это в общих чертах. Что тут сложного то?
Вы нарисовали асинхронную схему обработки ответа. В этой схеме для решения задачи TC нужно связать запрос и ответ, замкнуть транзакцию с помощью какого-нибудь идентификатора.
В случае, когда шина используется монопольно, это сделать просто - добавить, например, член Foo::idRequest. Соответственно, идентификатор генерируется перед передачей запроса и в doSomething определяется, на какой запрос пришел ответ.
Может, уже и не надо делать пример квазисинхронного взаимодействия?


Название: Re: Проблема с получением данных с помощью QSerialPort::waitForReadyRead()
Отправлено: qate от Сентябрь 25, 2021, 20:09
ТС - научайся мыслить при обмене асинхронно - как с сокетами, так и с serial портом
пришли данные  - обработай их
отправил данные - и не жди когда они реально уйдут -  ты (код юзер-спейса) не умнее ядра и не будешь никогда (а еще буфер отправки микросхемы есть)
если ты пишешь waitFor* - это костыль, далее это обсуждать неинтересно




Название: Re: Проблема с получением данных с помощью QSerialPort::waitForReadyRead()
Отправлено: Igors от Сентябрь 26, 2021, 07:13
Вы нарисовали асинхронную схему обработки ответа. В этой схеме для решения задачи TC нужно связать запрос и ответ, замкнуть транзакцию с помощью какого-нибудь идентификатора.
В случае, когда шина используется монопольно, это сделать просто - добавить, например, член Foo::idRequest. Соответственно, идентификатор генерируется перед передачей запроса и в doSomething определяется, на какой запрос пришел ответ.
Да, та же песня возникает и в др задачах, напр при обмене командами плагин-хост. Однако это уже вопрос управления/диспетчеризации пакетов, а ТС спрашивал всего лишь как получить пакет :)

Конечно асинхронка  более привлекательна, но и синхронный прием (в своей нитке) - абсолютно законный и корректный способ работы. Да и вообще как-то несолидно, "тут не выходит - ну давайте попробуем так" :)


Название: Re: Проблема с получением данных с помощью QSerialPort::waitForReadyRead()
Отправлено: sergek от Сентябрь 26, 2021, 10:26
Хотел сделать упрощенный пример, но увлекся :) Получился вполне себе рабочий вариант с очередью и двумя типами запросов (асинхронным и синхронным). Цитировать его не буду, кому интересно - посмотрит исходники.
При использовании асинхронного запроса (sendAsyncRequest) обработчик данных основной программы нужно связать с сигналом responseReceived. Для идентификации ответа используется только идентификатор запроса. Возможно, этого будет мало и понадобится тип запроса, чтобы знать, что за данные поступили. Тогда нужно будет его добавить в параметры sendAsyncRequest и членом в класс CRequest, а также в сигнал responseReceived.
В синхронном запросе я сделал одинаковыми таймауты ожидания ответа устройства на шине и в функции синхронного запроса
Код
C++ (Qt)
QByteArray CSerialIface::sendSyncRequest(QByteArray &data) {
 
   CWaitLoop loop;
   QTimer::singleShot(responseTimeout, &loop, &CWaitLoop::quit);
   connect(this, &CSerialIface::responseReceived, &loop, &CWaitLoop::exitSlot);
 
   sendAsyncRequest(data);
   loop.exec();
 
   return loop.response;
}
 
На самом деле последний таймаут должен быть чуть больше, но в предлагаемом примере ответ при ошибке и таймауте одинаков (пустые данные). Правда, я тут не сделал проверку requestId запроса и ответа, но это просто.