QThread + QSerialPort! Крутим в отдельном потоке работу с СOM портом.

В такой задаче ИМХО целесообразно крутить работу с каждым портом в отдельном потоке. Вот на этом месте и начались все проблемы… Проведя жуткоскучный и мучительный лит. поиск «ГУГЛ в помощь» результаты были весьма и весьма неоднозначны. Мои подзатыльники самому себе и хочу здесь привести на примере создания простейшей ГУЕвины терминалки с использованием QserialPort и Qthread. Надеюсь топик поможет многим, да и мне возможность получить дополнительных подзатыльников=)))
Постановка задачи: Рассмотреть возможные варианты реализации данной задачи и реализовать наиболее приемлемый и работоспособный. Привести пример простейшей терминалки. Получить подзатыльников и исправить свои ошибки (а их много).
Исходя из лит.анализа, основное: Поток должен быть независим, правильно унаследован. Все обращения к потоку и операции с потоком должны организовываться по сигнально-слотовой системе (никакого прямого обращения к методам в классе).
Краткие теоретические сведенья:
Кьют — бесплатный и кроссплатформенный инструмент для разработки ПО на C++.
QserialPort — дополнение к библиотеке Qt для работы с аппаратными и виртуальными СOM портами. Начиная с 5й версии Qt-а официально в него входит.
Qthread — какая-то жуткая магия для работы с потоками. Независимая задача, которая выполняется внутри процесса и разделяет вместе с ним общее адресное пространство, код и глобальные данные.
COM порт (RS232) — интерфейс с последовательной передачей данных стандарта RS232.
RS485 — стандарт физического уровня. Прикручивается к UART…
Цели: обобщить наработанные результаты и поделиться ими. Получить критический анализ и оценку от сообщества для повышения маны.
Моменты создания приложения в Qt пропускаю… Здесь же приведу код класса и то как его обьявлять.
Создаем новый класс:
файл.h
#ifndef PORT_H
#define PORT_H
#include <QObject>
#include <QtSerialPort/QserialPort>//Обьявляем работу с портом
struct Settings {//Структура с настройками порта
QString name;
qint32 baudRate;
QSerialPort::DataBits dataBits;
QSerialPort::Parity parity;
QSerialPort::StopBits stopBits;
QSerialPort::FlowControl flowControl;
};
class Port : public QObject
{
Q_OBJECT
public:
explicit Port(QObject *parent = 0);
~Port();
QSerialPort thisPort;
Settings SettingsPort;
signals:
void finished_Port(); //Сигнал закрытия класса
void error_(QString err);//Сигнал ошибок порта
void outPort(QString data); //Сигнал вывода полученных данных
public slots:
void DisconnectPort(); // Слот отключения порта
void ConnectPort(void); // Слот подключения порта
void Write_Settings_Port(QString name, int baudrate, int DataBits, int Parity, int StopBits, int FlowControl);// Слот занесение настроек порта в класс
void process_Port(); //Тело
void WriteToPort(QByteArray data); // Слот от правки данных в порт
private slots:
void handleError(QSerialPort::SerialPortError error);//Слот обработки ощибок
void ReadInPort(); //Слот чтения из порта по ReadyRead
public:
};
#endif // PORT_H
файл.с
#include "port.h"
#include <qdebug.h>
Port::Port(QObject *parent) :
QObject(parent)
{
}
Port::~Port()
{
qDebug("By in Thread!");
emit finished_Port();//Сигнал о завершении работы
}
void Port :: process_Port(){//Выполняется при старте класса
qDebug("Hello World in Thread!");
connect(&thisPort,SIGNAL(error(QSerialPort::SerialPortError)), this, SLOT(handleError(QSerialPort::SerialPortError))); // подключаем проверку ошибок порта
connect(&thisPort, SIGNAL(readyRead()),this,SLOT(ReadInPort()));//подключаем чтение с порта по сигналу readyRead()
}
void Port :: Write_Settings_Port(QString name, int baudrate,int DataBits,
int Parity,int StopBits, int FlowControl){//заносим параметры порта в структуру данных
SettingsPort.name = name;
SettingsPort.baudRate = (QSerialPort::BaudRate) baudrate;
SettingsPort.dataBits = (QSerialPort::DataBits) DataBits;
SettingsPort.parity = (QSerialPort::Parity) Parity;
SettingsPort.stopBits = (QSerialPort::StopBits) StopBits;
SettingsPort.flowControl = (QSerialPort::FlowControl) FlowControl;
}
void Port :: ConnectPort(void){//процедура подключения
thisPort.setPortName(SettingsPort.name);
if (thisPort.open(QIODevice::ReadWrite)) {
if (thisPort.setBaudRate(SettingsPort.baudRate)
&& thisPort.setDataBits(SettingsPort.dataBits)//DataBits
&& thisPort.setParity(SettingsPort.parity)
&& thisPort.setStopBits(SettingsPort.stopBits)
&& thisPort.setFlowControl(SettingsPort.flowControl))
{
if (thisPort.isOpen()){
error_((SettingsPort.name+ " >> Открыт!\r").toLocal8Bit());
}
} else {
thisPort.close();
error_(thisPort.errorString().toLocal8Bit());
}
} else {
thisPort.close();
error_(thisPort.errorString().toLocal8Bit());
}
}
void Port::handleError(QSerialPort::SerialPortError error)//проверка ошибок при работе
{
if ( (thisPort.isOpen()) && (error == QSerialPort::ResourceError)) {
error_(thisPort.errorString().toLocal8Bit());
DisconnectPort();
}
}//
void Port::DisconnectPort(){//Отключаем порт
if(thisPort.isOpen()){
thisPort.close();
error_(SettingsPort.name.toLocal8Bit() + " >> Закрыт!\r");
}
}
void Port :: WriteToPort(QByteArray data){//Запись данных в порт
if(thisPort.isOpen()){
thisPort.write(data);
}
}
//
void Port :: ReadInPort(){//Чтение данных из порта
QByteArray data;
data.append(thisPort.readAll());
outPort(data);
//((QString)(adr.toInt())).toLatin1().toHex()
}
Теперь необходимо правильно обьявить поток и подключить к нему наш новый класс (здесь находится вся магия):
QThread *thread_New = new QThread;//Создаем поток для порта платы
Port *PortNew = new Port();//Создаем обьект по классу
PortNew->moveToThread(thread_New);//помешаем класс в поток
PortNew->thisPort.moveToThread(thread_New);//Помещаем сам порт в поток
И соединяем все нужные нам сигналы и слоты:
connect(PortNew, SIGNAL(error_(QString)), this, SLOT(Print(QString)));//Лог ошибок
connect(thread_New, SIGNAL(started()), PortNew, SLOT(process_Port()));//Переназначения метода run
connect(PortNew, SIGNAL(finished_Port()), thread_New, SLOT(quit()));//Переназначение метода выход
connect(thread_New, SIGNAL(finished()), PortNew, SLOT(deleteLater()));//Удалить к чертям поток
connect(PortNew, SIGNAL(finished_Port()), thread_New, SLOT(deleteLater()));//Удалить к чертям поток
connect(this,SIGNAL(savesettings(QString,int,int,int,int,int)),PortNew,SLOT(Write_Settings_Port(QString,int,int,int,int,int)));//Слот - ввод настроек!
connect(ui->BtnConnect, SIGNAL(clicked()),PortNew,SLOT(ConnectPort()));//по нажатию кнопки подключить порт
connect(ui->BtnDisconect, SIGNAL(clicked()),PortNew,SLOT(DisconnectPort()));//по нажатию кнопки отключить порт
connect(PortNew, SIGNAL(outPort(QString)), this, SLOT(Print(QString)));//вывод в текстовое поле считанных данных
connect(this,SIGNAL(writeData(QByteArray)),PortNew,SLOT(WriteToPort(QByteArray)));//отправка в порт данных
И остается самое главное! Запустить поток:
thread_New->start();
В результате получаем вот такое окошко. Единственное что отправленные и принятые данные отображаются в ASCII кодах

Прилеплен архивчик с исходниками!
- +3
- 16 февраля 2014, 19:27
- kalik
- 1
Файлы в топике:
terminalka.zip
>>В такой задаче ИМХО целесообразно крутить работу с каждым портом в отдельном потоке. Вот на этом месте и начались все проблемы…
ИМХО тема не раскрыта — в чём проблемы-то были? О том, как создавать потоки в QT не писал только ленивый. В том числе, была куча дебатов, о том, как их писать правильно и неправильно — правда вот однозначных выводов из них сделано не было. У вас используется метод, продвигаемый в статье «QThread — You are doing it wrong», но почему именно так объяснений не дано. А вот я, решая аналогичную задачу и начитавшись ответной статьи, написал более привычным мне по wxWidgets методом — через наследование. А как НА САМОМ ДЕЛЕ правильно и не правильно — фиг разберёшься.
ИМХО тема не раскрыта — в чём проблемы-то были? О том, как создавать потоки в QT не писал только ленивый. В том числе, была куча дебатов, о том, как их писать правильно и неправильно — правда вот однозначных выводов из них сделано не было. У вас используется метод, продвигаемый в статье «QThread — You are doing it wrong», но почему именно так объяснений не дано. А вот я, решая аналогичную задачу и начитавшись ответной статьи, написал более привычным мне по wxWidgets методом — через наследование. А как НА САМОМ ДЕЛЕ правильно и не правильно — фиг разберёшься.
тема не раскрытаЯ как бы привел пример работоспособный, а не проводил анализ всех ошибок со способами их решения.
как создавать потоки в QTОтчасти вы правы=)) Вот только никто не писал про всовывание в поток QSerialPort.
QThread — You are doing it wrong
Именно он! обьяснения 2: 1-проблемы с наследованием всего что в классе; 2 — мне он больше понравился.
начитавшись ответной статьи
Первоначально я сделал тоже как в ответной статье, но куча проблем выскочила, опять с тем же наследованием.
p, li { white-space: pre-wrap; }
QObject: Cannot create children for a parent that is in a different thread. (Parent is QSerialPort(0x912dd50), parent's thread is QThread(0x8f75728), current thread is QThread(0x912dc28) QObject: Cannot create children for a parent that is in a different thread. (Parent is QSerialPort(0x912dd50), parent's thread is QThread(0x8f75728), current thread is QThread(0x912dc28)
Вот самая моя большая головная боль!
А как НА САМОМ ДЕЛЕ
Вот тут Вы правы, черт разберешь=)) Поэтому правильно в данном случае — так как удобно именно Вам и чтобы все работало!
>>Первоначально я сделал тоже как в ответной статье, но куча проблем выскочила, опять с тем же наследованием.
Вооо! Вот именно этой информации и не хватает в статье — было бы неплохо дополнить. У меня, кстати, такой проблемы не возникало, потому что я не через QSerialPort работал, а через простую с-шную обёртку вокруг нативного API.
>>Поэтому правильно в данном случае — так как удобно именно Вам и чтобы все работало!
Ещё бы быть уверенным, в том что оно действительно работает, а не только делает вид и любой чих его может сломать…
Вооо! Вот именно этой информации и не хватает в статье — было бы неплохо дополнить. У меня, кстати, такой проблемы не возникало, потому что я не через QSerialPort работал, а через простую с-шную обёртку вокруг нативного API.
>>Поэтому правильно в данном случае — так как удобно именно Вам и чтобы все работало!
Ещё бы быть уверенным, в том что оно действительно работает, а не только делает вид и любой чих его может сломать…
Вот именно этой информации и не хватает в статьеКак чуть расгребусь с делами насущными… постараюсь поправить.
что оно действительно работаетпроверяю на разных системах работоспособность кода, с разными устройствами и с использованием разных переходников USB <-> RS232, USB <-> RS485…
+ гонял несколько суток в очень тяжелых условиях (в цеху) со всеми положенными помехами: сварка, станки, лебедки, перепады напряжения и.т.д. вродибы не выскочило никаких критических ощибок…
А зачем вообще отдельные потоки? У QSerialPort есть неблокирующий API, которого должно быть вполне достаточно.
Его то достаточно, но куча сбоев при этом? + нужно какую-то службу писать или чегото там… чтобы все в свое нужное время и в нужной очередности по портам…
А если одновременно в 3 порта придут данные? Как там оно реализовано в QSerialPоrt мне не известно… и что в этот момент случится с программой спрогнозировать тоже тяжело… Так что лучше по отдельности.
Предложите Ваш вариант, как бы вы сделали все в одном потоке?
Предыдушее ПО было извояно (хорошо что не мной) на Delphi и все работало в общем потоке… Вот только как оно работало и можно ли назвать это работой или постоянным подвисанием… я хз.
А если одновременно в 3 порта придут данные? Как там оно реализовано в QSerialPоrt мне не известно… и что в этот момент случится с программой спрогнозировать тоже тяжело… Так что лучше по отдельности.
Предложите Ваш вариант, как бы вы сделали все в одном потоке?
Предыдушее ПО было извояно (хорошо что не мной) на Delphi и все работало в общем потоке… Вот только как оно работало и можно ли назвать это работой или постоянным подвисанием… я хз.
Объявление структуры настроек порта Settings, лучше перенести а public секцию самого класса Port, а объявление экземпляра структуры (Settings SettingsPort;) в pivate секцию.
Передавать настройки порту можно через указатель на структуру типа Port::SettingsPort.
Передавать настройки порту можно через указатель на структуру типа Port::SettingsPort.
Port::SettingsPort newSetting;
...
connect(this,SIGNAL(savesettings(Port::SettingsPort*)),PortNew,SLOT(Write_Settings_Port(Port::SettingsPort*)));//Слот - ввод настроек!вод настроек!
...
На десятке пишет Device is not open
Не могу понять в чем проблема…
Не могу понять в чем проблема…
- northcitizen
- 03 мая 2019, 12:49
- ↓
Комментарии (18)
RSS свернуть / развернуть