Почему сигнал ReadyRead() срабатывает только один раз?

У меня имеется плата, которая отправляет пакеты данных через виртуальный COM-порт каждые 2 секунды. Если я запускаю свою программу сразу после подключения платы, функция ReadyRead() срабатывает только один раз. Однако, если я сначала подключаю плату, затем запускаю мониторинг COM-порта, после чего отключаю его и запускаю свою программу, она начинает корректно получать данные каждые 2 секунды. Что именно делает мониторинг COM-порта, что позволяет моей программе нормально получать данные после его запуска? 

У меня есть класс-обёртка над QSerialPort:

**ComPort.h**
```cpp
class ComPort : public QObject
{
        Q_OBJECT
    public:
        ComPort(const QString &com_name, QObject *parent = nullptr);
        ~ComPort();
 
    private slots:
        void HandleReadyRead();
 
    private:
        QPointer<QSerialPort> serial_;
        QByteArray read_buffer_;
};
```

**ComPort.cpp**
```cpp
ComPort::ComPort(const QString &com_name, QObject *parent) 
: QObject(parent),
read_buffer_{}
{
    serial_ = new QSerialPort(com_name);
    serial_->open(QIODevice::ReadWrite);
 
    serial_->setPortName(com_name);
    serial_->setBaudRate(QSerialPort::Baud115200);
    serial_->setDataBits(QSerialPort::Data8);
    serial_->setParity(QSerialPort::NoParity);
    serial_->setStopBits(QSerialPort::OneStop);
    serial_->setFlowControl(QSerialPort::NoFlowControl);
 
    connect(serial_, SIGNAL(readyRead()), SLOT(HandleReadyRead()));
}
 
ComPort::~ComPort()
{    
    serial_->close();
    delete serial_;
}
 
void ComPort::HandleReadyRead()
{
    read_buffer_.append(serial_->readAll());
    // Здесь ещё присоединяются данные
}
```
  • 28 декабря 2024 г. 14:53
Ответы на вопрос 2
Сигнал `readyRead()` в `QSerialPort` срабатывает, когда есть доступные данные для чтения. Если этот сигнал срабатывает только один раз, а затем не срабатывает снова, возможно, вы сталкиваетесь с некоторыми типичными проблемами, связанными с обработкой данных в вашем приложении. Вот несколько возможных причин и решений, на которые стоит обратить внимание:

1. **Проблемы с настройками порта**: Убедитесь, что настройки COM-порта (скорость передачи, четность, количество стоп-битов, контроль потока и т.д.) соответствуют настройкам вашего устройства. Неправильные настройки могут привести к тому, что данные не будут корректно приниматься.

2. **Ошибка открытия порта**: Проверьте, успешно ли ваш код открывает COM-порт. Если порт не открыт или открывается с ошибками, это может привести к тому, что `readyRead()` никогда не вызовется снова. Используйте `serial_->error()` для диагностики.

3. **Обработка данных в `HandleReadyRead`**: Убедитесь, что вы корректно обрабатываете данные, поступающие в `HandleReadyRead()`. Если вы не очищаете данные или не обрабатываете дополнительные пакеты должным образом, это может привести к тому, что ваш обработчик может неправильно работать. Возможно, данные остаются в буфере, и ваше приложение не готово их обрабатывать.

4. **Сигналы и слоты**: Убедитесь, что сигнал `readyRead()` правильно подключен к слоту `HandleReadyRead()`. Вы используете устаревший синтаксис соединений сигналов и слотов (через `SIGNAL` и `SLOT`), попробуйте использовать новый синтаксис:
   ```cpp
   connect(serial_, &QSerialPort::readyRead, this, &ComPort::HandleReadyRead);
   ```

5. **Мониторинг порта**: При подключении к виртуальному COM-порту некоторые утилиты для мониторинга могут инициализировать или перезагружать соединение, заставляя устройство начать отправлять данные корректно. Эти утилиты могут вызывать операции, которые не выполняет ваше приложение при первом запуске.

6. **Логи**: Добавьте вывод отладочной информации в метод `HandleReadyRead()`, чтобы убедиться, что данные действительно имеют место после первого сигнала. Например, вы можете добавить вывод данных или количество прочитанных байтов.

7. **Использование события**: Часто полезно добавить обработчик событий или периодически проверять наличие данных в порте с помощью метода таймера. Это может дать вам больше контроля и избежать пропуска данных.

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

Установка параметров порта.
Да, метод setPortName не возвращает ничего. Но, вызовы QSerialPort::setBaudRate , QSerialPort::setDataBits , QSerialPort::setParity , QSerialPort::setStopBits и QSerialPort::setFlowControl могут вернуть false . Пытаться открыть после этого порт явно не стоит. Лучше вызвать QSerialPort:error , чтобы обработать ошибку.

Сигналы и слоты.
QObject::connect (com_port_, &QSerialPort::readyRead, this, &ComPort::handleReadyRead);
QObject::connect (com_port_, &QSerialPort::errorOccurred, this, &ComPort::handleError);


Открытие порта.
Метод QSerialPort::open следует вызывать только после успешной установки параметров порта (см. выше). И даже после этого он может вернуть false . В этом случае ничего не остаётся, как вызвать QSerialPort:error , чтобы отреагировать на ошибку. Кстати, это рекомендации из официальной документации.

Чтение данных.
Во-первых, метод чтения данных QSerialPort::read может вернуть -1 в случае ошибки. А метод QSerialPort::readAll в случае ошибки вернёт пустой QByteArray. Что нужно делать в этом случае? Правильно! Вызвать QSerialPort:error , чтобы отреагировать на ошибку.

Во-вторых, я бы не стал полагаться на каждое срабатывание сигнала QSerialPort::readyRead , а использовал бы цикл с методом QIODevice::bytesAvailable , в котором считываются и обрабатываются все данные, о приходе которых стало известно по упомянутому выше сигналу.
void ComPort::HandleReadyRead()
{
  while (serial_->bytesAvailable () > 0) {

    // Чтение данных
    QByteArray data = serial_->readAll ();
    if (data.isEmpty ()) {
      handleError ();
      return;
    }

    read_buffer_.append (data);

    // Здесь ещё пристуствует логика обработки сообщения, я ищу символ конца
    // сообщения, и если его нахожу, то эмитирую сигнал, для вызова слота парсинга сообщения.
  }
}


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

Обработка ошибок.
Простите, но у вас она отсутствует как класс. Реализуйте, например, предлагаемый мною ComPort::handleError.
void ComPort::handleError (QSerialPort::SerialPortError error)
{
  qDebug () << __LINE__ << __FUNCTION__ << error;
}


Заключение.
На мой взгляд, только после указанных выше доработок можно приступить к рассмотрению вопроса по существу. И вообще хоть как-то локализовать проблему.
Похожие вопросы