Russian Qt Forum
Март 28, 2024, 14:06 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: [1]   Вниз
  Печать  
Автор Тема: QSerialPort - странный баг (?)  (Прочитано 15613 раз)
Racheengel
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2679


Я работал с дискетам 5.25 :(


Просмотр профиля
« : Декабрь 05, 2018, 18:17 »

Добрый день,

есть проект, использующий QSerialPort. Данное приложение является slave по отношению к управляющему PC, обе машины соеденены по RS-232 интерфейсу.
Протокол общения - простейший, master-PC раз в несколько секунд присылает 1- или 2- байтовые команды, которые читаются и подтверждаются необходимым ответом.
Заказчик начал ругаться на то, что раз в 1-2 суток происходит остановка коммуникации, иногда с завершением приложения на slave-PC.
После чего требуется физически перестартовать обе машины, чтобы восстановить коммуникацию. Грешили на убитый ком-порт на стороне заказчика.

Самое странное однако началось, когда мы попробовали собрать тестовую систему на базе двух PC, на стороне мастера запустив симуляцию протокола посредством макроса Terraterm.
6 дней подряд система работала без проблем, пока не проявился подобный эффект, как и у заказчика. Дебажный вывод показал, что со временем на стороне slave прекращается запись в QSerialPort с сообщением Cannot write to serial port, после чего увеличивается размер буфера данных для передачи (прием данных всё это время работает стабильно).
Перезапуск приложения помогает на несколько часов (больше ни разу не было тех изначальных стабильных 6 дней работы), после чего снова останавливается приём.

Можно ли объяснить данное поведение переполнением буфера отправки QSerialPort, если принимающая сторона перестанет на некоторое время вычитывать из него данные?
Что тогда произойдёт? До какого предела будет "расти" буфер?
Есть ли возможность в таком случае выполнить его насильную очистку (типа clearSendBuffer() или что-то вроде)?
Записан

What is the 11 in the C++11? It’s the number of feet they glued to C++ trying to obtain a better octopus.

COVID не волк, в лес не уйдёт
kuzulis
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2812


Просмотр профиля
« Ответ #1 : Декабрь 05, 2018, 19:07 »

Цитировать
Можно ли объяснить данное поведение переполнением буфера отправки QSerialPort, если принимающая сторона перестанет на некоторое время вычитывать из него данные?

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

PS: Какая ОС? Какая Qt? Что за код ошибки у "Cannot write to serial port" ?

« Последнее редактирование: Декабрь 05, 2018, 19:11 от kuzulis » Записан

ArchLinux x86_64 / Win10 64 bit
Racheengel
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2679


Я работал с дискетам 5.25 :(


Просмотр профиля
« Ответ #2 : Декабрь 05, 2018, 19:28 »

Да, забыл про самое важное, сорри Грустный

Ось - Винда 7-ка (32 бита), Qt 5.6.2, Visual Studio 2013.
Ошибка - SerialPortError::WriteError.

А как тогда "внутри" QSerialPort устроен, он же собирает где-то данные для отправки?
Какой-то физический буфер должен существовать, иначе bytesToWrite() всегда отдавал бы 0.
Или это значение каждый раз читается с железяки?
PS. Исходники пока что не смотрел, просто раньше с портом таких проблем не было, за много лет впервые такое вылезло...
Записан

What is the 11 in the C++11? It’s the number of feet they glued to C++ trying to obtain a better octopus.

COVID не волк, в лес не уйдёт
Old
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 4349



Просмотр профиля
« Ответ #3 : Декабрь 05, 2018, 20:04 »

Какой контроль потока вы используете? Аппаратный?
Записан
Racheengel
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2679


Я работал с дискетам 5.25 :(


Просмотр профиля
« Ответ #4 : Декабрь 05, 2018, 20:10 »

Какой контроль потока вы используете? Аппаратный?

Никакого. Всё выключено. Скорость 9600, четность выключена, 8 бит данных, 1.5 стопбитов.
Настройки продиктованы системой заказчика (master-PC), они ничего поменять не могут (старющий софт, исходников нету, поддерживать никто не умеет).
 
Записан

What is the 11 in the C++11? It’s the number of feet they glued to C++ trying to obtain a better octopus.

COVID не волк, в лес не уйдёт
Old
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 4349



Просмотр профиля
« Ответ #5 : Декабрь 05, 2018, 20:59 »

Дебажный вывод показал, что со временем на стороне slave прекращается запись в QSerialPort с сообщением Cannot write to serial port, после чего увеличивается размер буфера данных для передачи (прием данных всё это время работает стабильно).
Т.е. slave перестает отправлять ответы на запросы мастера? А запросы продолжают приходить штатно от master?
А буфер растет в вашей программе, из которого вы отправляете данные в последовательный порт?

Перезапуск приложения помогает на несколько часов (больше ни разу не было тех изначальных стабильных 6 дней работы), после чего снова останавливается приём.
Прием или отправка?
« Последнее редактирование: Декабрь 05, 2018, 21:18 от Old » Записан
kuzulis
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2812


Просмотр профиля
« Ответ #6 : Декабрь 05, 2018, 22:05 »

Цитировать
Ось - Винда 7-ка (32 бита), Qt 5.6.2, Visual Studio 2013.

Слишком старая версия Qt.

Цитировать
Ошибка - SerialPortError::WriteError.

Там она может только в одном случае возникнуть:

Код
C++ (Qt)
bool QSerialPortPrivate::_q_startAsyncWrite()
{
   /// << Если буфер передачи writeBuffer пуст, или если предыдущая операция записи еще не завершилась то ничо не делаем.
   if (writeBuffer.isEmpty() || writeStarted)
       return true;
 
   /// << Копируем все содержимое буфера передачи во временный массив writeChunkBuffer
   /// << и именно его данные передаем в WriteFile, т.к. данные из writeBuffer передавать низя!!!
   writeChunkBuffer = writeBuffer.read();
   ::ZeroMemory(&writeCompletionOverlapped, sizeof(writeCompletionOverlapped));
   if (!::WriteFile(handle, writeChunkBuffer.constData(),
                    writeChunkBuffer.size(), Q_NULLPTR, &writeCompletionOverlapped)) {
 
       QSerialPortErrorInfo error = getSystemError();
       if (error.errorCode != QSerialPort::NoError) {
           if (error.errorCode != QSerialPort::ResourceError)
               error.errorCode = QSerialPort::WriteError; /// << Вот тут именно твоя ошибка и возникает, т.к. больше по коду негде!!!
           setError(error);
           return false;
       }
   }
 
   writeStarted = true;
   return true;
}
 

Цитировать
А как тогда "внутри" QSerialPort устроен, он же собирает где-то данные для отправки?

Внутри QSP есть буффер writeBuffer в который данные добавляются при каждом QSP::write(). Как только данные добавились, то стартует _q_startAsyncWrite(), который дергает WriteFile() с данными из буфера writeBuffer (делается временная копия QByteArray которая передается в ф-ю) и то только если сейчас нет отложенной операции записи. Если уже идет операция записи (например, был ранее вызов) то метод _q_startAsyncWrite() ничего не делает. Как только операция записи завершается (мы узнаем это по IOCP событию на дескрипторе, когда срабатывает нотификатор) то вызывается completeAsyncWrite() и мы обрезаем реально переданные данные из хвоста writeBuffer (освобождаем буффер) и запускаем _q_startAsyncWrite() снова чтобы передать остаток данных из writeBuffer если они есть. Так будет продолжаться пока writeBuffer не опустеет.

Итого: В случае, если WriteFile() фейлится, то буфер writeBuffer, естественно, не очищается и вызов QSP::write() будет постоянно добавлять данные в writeBuffer пока память RAM не скушается.  Улыбающийся Нужно всегда обрабатывать ошибки из порта, т.е. переоткрывать порт (как минимум) при первой же возникшей ошибке.

По хорошему, ты можешь добавить отладочный вывод кода ошибки Win32 в qserialport_win.cpp:

Код
C++ (Qt)
QSerialPortErrorInfo QSerialPortPrivate::getSystemError(int systemErrorCode) const
{
   if (systemErrorCode == -1)
       systemErrorCode = ::GetLastError();
 
  qDebug() << "System error code:" << systemErrorCode ;
 
  ...
}
 
 

и пресобрать модуль, чтобы посмотреть, почему WriteFile фейлится.

Скорее всего какая-то фигня или с драйвером, или, если это USB CDC девайс, который реализуется на стороне "заказчика" в виде некоей прошивки - то см. что там накосячили. Улыбающийся

Для теста можно взять QSP, написать программку которая будет просто слать в порт периодически любые данные и смотреть, что будет с ошибкой...

ЗЫ: В общем, разбирайся сам. Улыбающийся
« Последнее редактирование: Декабрь 05, 2018, 22:22 от kuzulis » Записан

ArchLinux x86_64 / Win10 64 bit
sergek
Гипер активный житель
*****
Offline Offline

Сообщений: 861


Мы должны приносить пользу людям.


Просмотр профиля
« Ответ #7 : Декабрь 06, 2018, 09:56 »

Нужно всегда обрабатывать ошибки из порта, т.е. переоткрывать порт (как минимум) при первой же возникшей ошибке.
Ндаа.. Недавно потратил количество времени, чтобы прийти к такому выводу. А это, оказывается, знание, которое должен знать каждый ((
Записан

Qt 5.13.0 Qt Creator 5.0.1
Win10, Ubuntu 20.04
Racheengel
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2679


Я работал с дискетам 5.25 :(


Просмотр профиля
« Ответ #8 : Декабрь 06, 2018, 11:25 »

Дебажный вывод показал, что со временем на стороне slave прекращается запись в QSerialPort с сообщением Cannot write to serial port, после чего увеличивается размер буфера данных для передачи (прием данных всё это время работает стабильно).
Т.е. slave перестает отправлять ответы на запросы мастера? А запросы продолжают приходить штатно от master?
А буфер растет в вашей программе, из которого вы отправляете данные в последовательный порт?

Перезапуск приложения помогает на несколько часов (больше ни разу не было тех изначальных стабильных 6 дней работы), после чего снова останавливается приём.
Прием или отправка?


Да, прекращается отправка со стороны слейва (т.е. наш софт), соотв. приём на стороне мастера.
Запросы идут дальше, мастер по видимому умеет игнорить непришедшие ответы Улыбающийся
Буфер растёт в QSerialPort.
Записан

What is the 11 in the C++11? It’s the number of feet they glued to C++ trying to obtain a better octopus.

COVID не волк, в лес не уйдёт
Racheengel
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2679


Я работал с дискетам 5.25 :(


Просмотр профиля
« Ответ #9 : Декабрь 06, 2018, 11:27 »

Цитировать
Ось - Винда 7-ка (32 бита), Qt 5.6.2, Visual Studio 2013.

Слишком старая версия Qt.


Знаю, но обновить несколько проблематично Грустный


Итого: В случае, если WriteFile() фейлится, то буфер writeBuffer, естественно, не очищается и вызов QSP::write() будет постоянно добавлять данные в writeBuffer пока память RAM не скушается.  Улыбающийся Нужно всегда обрабатывать ошибки из порта, т.е. переоткрывать порт (как минимум) при первой же возникшей ошибке.


А вот это ценно, я сейчас попробую, спасибо.

С др. стороны, имхо нужно этот функционал в класс QSP по дефолту добавить. Иначе получаются "скрытые знания" Улыбающийся
Возникла ошибка - стоит флаг autoOpenPortOnFailure()? переоткрываем.

Записан

What is the 11 in the C++11? It’s the number of feet they glued to C++ trying to obtain a better octopus.

COVID не волк, в лес не уйдёт
kuzulis
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2812


Просмотр профиля
« Ответ #10 : Декабрь 06, 2018, 11:46 »

Цитировать
Иначе получаются "скрытые знания" Улыбающийся
Возникла ошибка - стоит флаг autoOpenPortOnFailure()? переоткрываем.

Неет ужж. Все в руках самого программиста.
Записан

ArchLinux x86_64 / Win10 64 bit
sergek
Гипер активный житель
*****
Offline Offline

Сообщений: 861


Мы должны приносить пользу людям.


Просмотр профиля
« Ответ #11 : Июль 01, 2023, 13:15 »

Итого: В случае, если WriteFile() фейлится, то буфер writeBuffer, естественно, не очищается и вызов QSP::write() будет постоянно добавлять данные в writeBuffer пока память RAM не скушается.  Улыбающийся Нужно всегда обрабатывать ошибки из порта, т.е. переоткрывать порт (как минимум) при первой же возникшей ошибке.
Появилась необходимость уточнить этот утверждение. На мой взгляд, не всегда нужно переоткрывать порт: например, при получении ошибки QSerialPort::OpenError - зачем это делать, если порт всего лишь говорит, что он уже открыт.
Поэтому у меня вопрос - при каких ошибках, генерируемых QSerialPort, требуется переподключение? Ну, или наоборот - какие не требуют?
Записан

Qt 5.13.0 Qt Creator 5.0.1
Win10, Ubuntu 20.04
Страниц: [1]   Вверх
  Печать  
 
Перейти в:  


Страница сгенерирована за 0.098 секунд. Запросов: 22.