Programowanie C++ Qt4 w systemie Gnu-Linux/Wątki-Dialog z urządzeniem po RS232
Wstęp
edytujMamy do oprogramowania taką funkcję: Funkcja wysyła polecenie zapisu danych do urządzenia następnie czeka na odpowiedź. A może wysłać polecenie odczytu danych Aby porównać czy zapisane dane są poprawne. Rozpisanie problemu w sposób klasyczny spowoduje utratę responsywnośći GUI. A kod asynchroniczny jest trudniejszy do napisania !!!
Uwaga!
|
W bibliotece QT za wątki odpowiada klasa QThread I podejścia do programowania wątków są dwa:
- napisanie klasy dziedziczącej po QThread i napisanie (reimplementacja) metody run()
- napisanie klasy dziedziczącej po QObject następnie w stosunku do obiektu klasy wykorzystanie metody moveToThread(QThread*). Ważne jest by funkcja odpalana w wątku była zdefiniowana jako slot!
Komunikacja z wątkiem jest możliwa Za pomocą sygnałów i slotów. Jeśli wątek ma na zwrócić wartość w postaci napisu to musimy zdefiniować sygnał i po prostu go odpalić. Kod odpalony w wątku nie będzie wstanie zmienić stanu widgetów okna głównego. lineEdit->setTekst("ala ma kota"); niestety nie zadziała. Widgety nie są przystosowane do takiej pracy!
Zwykła klasa
edytujNa początek posłużę się przykładem ze strony pomocy projektu QT http://doc.qt.io/qt-5/qthread.html Przykład pozwoliłem sobie opatrzyć komentarzami.
class Worker : public QObject
{
Q_OBJECT
public slots:
void doWork(const QString ¶meter) //funkcja odpalana w wątku może być z parametrem
{
QString result;
/* ... Operacje wymagające czasu do realizacji odpalimy w wątku ... */
emit resultReady(result); //tutaj wysyłamy wynik operacji
//emit koniecFunkcji(true); //Jeśli zależy nam tylko na informacji zwrotnej odnośnie sukcesu.
}
signals:
void resultReady(const QString &result);
void koniecFunkcji(bool);
};
class Controller : public QObject
{
Q_OBJECT
QThread workerThread;
public:
Controller() {
Worker *worker = new Worker; //obiekt naszej klasy
worker->moveToThread(&workerThread); //przeniesiony do wąktu
connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);
connect(this, SIGNAL(operate(QString)), worker, SLOT(doWork(QString))); // sygnał wysłany z klasy kontrolera uruchomi funkcję doWork
connect(worker, &Worker::resultReady, this, &Controller::handleResults); // po zakończeniu działania funkcji doWork zostanie wysłany sygnał z wynikiem.
workerThread.start();
}
~Controller() {
workerThread.quit();
workerThread.wait();
}
public slots:
void handleResults(const QString &);
signals:
void operate(const QString &);
};
Widzimy tutaj, iż za każdym wywołaniem sygnału w klasie kontrolera np:
emit operate("tekst")
zostanie wywołana funkcja doWork("tekst")
klasa dziedzicząca po QThread
edytujNiewątpliwą zaletą takiego podejścia jest to, iż potrzeba mniej kodu by powołać taki obiekt do życia. Tutaj do prezentacji posłużę się swoim kodem powstałym właśnie do obsługi urządzenia zapisującego numery do pastylek RFID.
Do oprogramowania będę miał kilka funkcji i wszystkie one powinny zostać odpalone w separowanym od głównego programu wątku. Właśnie po to by nie blokować responsywności GUI. Na Początek plik nagłówkowy, który już sam w sobie powinien sporo rozjaśnić
#ifndef RS232_INTERFEJS_H
#define RS232_INTERFEJS_H
#include <QThread>
#include<QSerialPort>
class RS232_Interfejs : public QThread
{
Q_OBJECT
int nr;
void (RS232_Interfejs::*f)();
/* Poniżej Właściwe funkcje odpalene w oddzielnym wądku do dialogu z urządzeniem
*
* _writeNumer() - wysyła polecenie zapisu numeru do pastylki RFID następnie ponownie go odczytuje w celu sprawdzenia poprawności zapisanego numeru
* _writeNumerAndConfig() - jak wyżej tyle że sapisuje również config pastylki
* _init() - otwarcie portu com do pracy i wysłanie ramki konfiguracyjnej !!!
* _getNumer()- Pobranie odczytanie numeru z pastylki RFID
*
*/
void _writeNumer();
void _writeNumerAndConfig();
void _init();
void _getNumer();
//koniec
inline int pomocniczaReadNumer();
QSerialPort portCom;
QString portName;
public:
explicit RS232_Interfejs(QObject *parent = 0);
/*Dialog z klasą główną funkcje publiczne
* f - funkcja odpalana w wątku Funkcja ta wywoływana jest w funkcji run() wątku
* Jeśli Z GUI wywołam objektRS.writeNumer(300) w oddzielnym wątku zostanie odpalona funkcja _writeNumer();
* Oczeniwanie na dane z portu nie zablokują responsywności GUI
* this->start() właściwie wykona funkcję run() A tym samym odpali funkcję na którą wskazuje wskaźnik "void(*f)()"
*
*
*
*/
void writeNumer(int numer)
{
nr=numer;
f= &RS232_Interfejs::_writeNumer; // Wskaźnik f wskazuje na funkcję _writeNumer()
this->start(); // odpalenie
}
void writeNumerAndConfig(int numer)
{
nr=numer;
f=&RS232_Interfejs::_writeNumerAndConfig;
this->start();
}
void init()
{
f=&RS232_Interfejs::_init;
this->start();
}
void getNumer()
{
f=&RS232_Interfejs::_getNumer;
this->start();
}
void setPortName(QString pnm){portName =pnm;}
void run()
/*
* Reimplementacja funkcji run
*/
{
(this->*f)(); //A właściwie odpalenie w wąktu funkcji tej, na którą wskazuje wskaźnik f
}
signals:
/*Sygnały potrzebne do komunikacji z otoczeniem
* initOk(bool) - wyślemy gdy inicjalizacja urządzenia przebiegnie pomyślnie
* writeOK(bool) - wyślemy gdy zapis numeru do pastylki RFID zostanie potwierdzony pozytwnie
* readnumer(int) - sygnał który nada odczytany z pastylki RFID Numer
*/
void initOk(bool);
void writeOK(bool);
void readnumer(int);
public slots:
};
#endif // RS232_INTERFEJS_H