Задать вопрос
@Ballantrae

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

Дело происходит на Windows 10. У меня есть плата, которая шлёт мне пакеты данных по виртуальному компорту каждые 2 секунды. Физически она подключается по usb. Если сразу после её подключения я запускаю свою программу, то ReadyRead() вызывается один раз. Если же я включу плату, затем запущу монитор ком порта, выключу его и запущу свою программу, то всё работает - я получаю данные каждые 2 секунды, как и должно быть. В чём может быть дело? Что такого делает монитор ком порта, что после его запуска, моя программа начинает нормально получать данные?

У меня есть класс обёртка над QSerialPort
ComPort.h
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
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());
    // Здесь ещё пристуствует логика обработки сообщения, я ищу символ конца
    // сообщения, и если его нахожу, то эмитирую сигнал, для вызова слота парсинга сообщения.
}


MainWindow.h
class MyMainWindow : public QWidget
{
    Q_OBJECT
 
public:
    MyMainWindow(const char* path_to_exe, QString com_name, QWidget *parent = nullptr);
    ~MyMainWindow();
 
private:
    QPointer<ComPort> com_port_;
};


MainWindow.cpp
MyMainWindow::MyMainWindow(const char* path_to_exe, QString com_name, QWidget *parent)
    : QWidget(parent)
    , ui_(new Ui::MyMainWindow)
    , path_to_exe_(path_to_exe)
{
    ui_->setupUi(this);
    
    com_port_ = new ComPort(com_name);
 
    // ComPort
    connect(com_port_,            SIGNAL(NeedParseData(const QByteArray&)), cmd_processor_, SLOT(ParseData(const QByteArray&)));
}
 
MyMainWindow::~MyMainWindow()
{
    delete com_port_;
}
  • Вопрос задан
  • 98 просмотров
Подписаться Средний 1 комментарий
Пригласить эксперта
Ответы на вопрос 2
@Ballantrae Автор вопроса
В общем, проблему решил костыльно. С помощью win api.
void* hSerial;
    char c1;
    DWORD bytesRead, bWritten;
    BOOL fSuccess;
    DWORD dwParam, dwThreadId;
    const uint16_t TIMEOUT = 100;

    DCB dcb;
    COMMTIMEOUTS timeouts;
    timeouts.ReadIntervalTimeout = MAXDWORD;
    timeouts.ReadTotalTimeoutMultiplier = MAXDWORD;
    timeouts.ReadTotalTimeoutConstant = TIMEOUT;
    timeouts.WriteTotalTimeoutMultiplier = 10;
    timeouts.WriteTotalTimeoutConstant = TIMEOUT;

    hSerial = CreateFile(L"COM3", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
    if (hSerial != INVALID_HANDLE_VALUE)
    {
        fSuccess = SetCommTimeouts(hSerial, &timeouts);

        GetCommState(hSerial, &dcb);
        dcb.BaudRate = CBR_115200;
        dcb.fDtrControl = DTR_CONTROL_DISABLE;
        dcb.fRtsControl = RTS_CONTROL_DISABLE;
        dcb.ByteSize = 8;
        dcb.Parity = NOPARITY;
        dcb.StopBits = ONESTOPBIT;
        dcb.DCBlength = sizeof(DCB);
        fSuccess = SetCommState(hSerial, &dcb);

        CloseHandle(hSerial);
    }


После этого кода qt-шный QSerialPort начинает работать как надо. В принципе, можно написать свою обёртку над виндовским сериал портом и забить на qt, но потом.
Ответ написан
Комментировать
vxblog
@vxblog
Инженер-программист
Что ж, с вашего позволения, устрою небольшое code review.

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

Сигналы и слоты.
  1. Cоединить сигнал QSerialPort::readyRead со слотом ComPort::HandleReadyRead можно сразу после создания экземпляра QSerialPort. До установки параметров порта и его открытия.
  2. Однозначно, необходимо подключить сигнал QSerialPort::errorOccurred и реализовать обработку ошибок. Ну хотя бы их вывод в консоль во время отладки.
  3. Пожалуйста, не используйте макросы SIGNAL и SLOT. Переходите на функторы.


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;
}


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

Войдите, чтобы написать ответ

Похожие вопросы