Forum: PC-Programmierung QT, serielle Ports, Signale und Slots


von Tobias P. (hubertus)


Lesenswert?

Hi,
ich möchte mit QT ein Programm schreiben, wo eine serielle Schnittstelle 
(FTDI) angesteuert wird.
An der Schnittstelle hängt ein kleiner Mikrocontroller.
Entweder sendet man ein Kommando, dann wird das ausgeführt. Das ist kein 
Problem.

Oder aber man sendet eine Anfrage, diese wird dann beantwortet. Leider 
weiss man im Voraus nicht, wie viele Bytes man bekommen wird, aber man 
bekommt immer eine Zeile, am Schluss ist ein CR LF.
Ich habe das jetzt so implementiert, dass meine eigene Klasse, welche 
die Funktionalität implementieren soll, einen Slot hat (rdData), und 
mittels connect() habe ich das readyRead()-Signal der QSerialPort-Klasse 
mit meiner eigenen Klasse verheiratet. Im Handler lese ich dann einfach 
alle verfügbaren Daten vom QSerialPort aus und hänge sie zu Testzwecken 
einfach man an einer Textbox an. Das funktioniert. Wie jedoch könnte ich 
eine kluge Synchronisation erzielen? wenn ich eine Anfrage absende, 
möchte ich eigentlich gern auf die Antwort warten, ohne jedoch, dass mir 
das GUI einfriert. Wie macht man das?

von Sven B. (scummos)


Lesenswert?

Es gibt im Grunde zwei Möglichkeiten; entweder du generierst ein Signal, 
wenn die Antwort da ist und machst dann weiter mit dem Programmablauf 
(in dem Slot, der von dem Signal aufgerufen wird), oder du lagerst die 
ganze Kommunikationslogik in einen Thread aus, der dann synchron einfach 
auf die Antwort warten kann. Was besser ist, hängt vom Anwendungsfall 
ab.

von Tobias P. (hubertus)


Lesenswert?

OK. Mir ist im Grunde nicht so wichtig, wie es genau implementiert wird. 
Ich glaube aber, die Lösung mit dem separaten Thread, welcher auf die 
Antwort warten kann, scheint mir gut.

Könntest du mir ein Beispiel dazu zeigen? ich bin da definitiv nicht fit 
genug in C++. Wie warte ich in dem Thread z.B. elegant auf das Ende der 
Antwort? Das Ende kann ja, wie gesagt, am CR LF erkannt werden.

von CuTee (Gast)


Lesenswert?

Das bekommst man ja "kostenlos" mit wenn man Signale & Slots dafür 
nutzt. Hier z.B. gibt deine Interface-Klasse ein Signal bei 
abgeschlossenem Lese-Vorgang aus, das du nutzen kannst.

Geht man gegen diese asynchrone Design-Philosophie kann man das ganze 
einfach in einen eigenen Thread packen. Die Kommunikations-Funktionen 
dürfen dann selbstverständlich nicht im GUI-Thread aufgerufen werden, 
sonst gewinnt man dadurch nichts.

von CuTee (Gast)


Lesenswert?

Hatte die vorausgehenden Antworten nicht gesehen...
Dein ready-read bzw. das synchrone equivalent gibt dir so viel gerade im 
Puffer liegt. Ich würde die Daten intern in einen Puffer schreiben und 
dort nach dem CRLF suchen, dann entsprechend parsen und das Signal 
senden oder welche andere Synchronisierungsstruktur auch immer genutzt 
wird anstoßen und die Daten aus dem internen Puffer löschen. Damit ist 
das Problem der in mehrere Blöcke zerlegten Transaktionen gelöst, bzw. 
auch dass mehrere Transaktionen in einem Block gelesen werden.

von Sven B. (scummos)


Lesenswert?

Die Frage ist halt was du mit der Antwort machen willst. In einem 
Fenster anzeigen? Dann reicht definitiv ein weiteres Signal, dass eine 
Antwort angekommen ist, was du z.B. gegebenerfalls aus der Funktion 
emittierst die nach readyRead() aufgerufen wird. Je nach Ergebnis der 
Abfrage einen weiteren Befehl schicken, und das mit komplexer Struktur? 
Dann kann ein separater Thread sinnvoll sein, weil die asynchrone Logik 
sonst leicht in Spaghetti-Code ausartet.

von Tobias P. (hubertus)


Lesenswert?

Die Antwort beinhaltet zB. ein paar Messwerte, welche in einem Diagramm 
angezeigt werden sollen.

Aber das hängt von der zuvor gesendeten Anfrage ab. Je nach dem, was 
halt angefragt wird, muss die Antwort anders interpretiert werden! :-/

Sollte man evtl. unterschiedliche Handler für das readyRead-Signal 
benutzen und diese jeweils mit connect() anders verbinden?

: Bearbeitet durch User
von Sven B. (scummos)


Lesenswert?

Tobias P. schrieb:
> Sollte man evtl. unterschiedliche Handler für das readyRead-Signal
> benutzen und diese jeweils mit connect() anders verbinden?

Ne, ich denke das gibt ein Chaos, weil du dann ständig 
connect/disconnect machen musst. Wenn du die Antworten anhand der 
Antwort unterscheiden kannst (z.B. Byte im Header was sagt auf welchen 
Befehl das eine Antwort ist), unterscheide einfach anhand dessen in dem 
Handler was passiert, andernfalls kannst du dir ja in einer 
Membervariablen merken, auf welchen Befehl du als nächstes eine Antwort 
erwartest.

von Rolf M. (rmagnus)


Lesenswert?

Tobias P. schrieb:
> OK. Mir ist im Grunde nicht so wichtig, wie es genau implementiert wird.
> Ich glaube aber, die Lösung mit dem separaten Thread, welcher auf die
> Antwort warten kann, scheint mir gut.

Ich sehe da wenig Sinn drin. Denn dann hast du das gleiche Problem 
wieder, nur nicht bei der Kommunikation zwischen seriellem Port und GUI, 
sondern zwischen deinem Empfänger-Thread und dem GUI-Thread.
Threads bringen hier eigentlich nur was, wenn die Verarbeitung sehr 
aufwändig ist und viel CPU-Zeit braucht. Das würde dann ohne Threads die 
GUI ausbremsen.

CuTee schrieb:
> Hatte die vorausgehenden Antworten nicht gesehen...
> Dein ready-read bzw. das synchrone equivalent gibt dir so viel gerade im
> Puffer liegt.

Nein, readyRead() gibt gar nichts. Es sagt nur, dass neue Daten 
angekommen sind.

> Ich würde die Daten intern in einen Puffer schreiben und
> dort nach dem CRLF suchen, dann entsprechend parsen

Oder man benutzt im mit readyRead() verbundenen Slot einfach die 
Funktion canReadLine(), um zu ermitteln, ob eine ganze Zeile vorhanden 
ist, und wenn ja, liest man sie mit readLine(). Das macht man dann in 
einer Schleife, für den Fall, dass inzwischen mehr als eine Zeile 
angekommen ist.

von N2 (Gast)


Lesenswert?

Hi,

Vielleicht habe ich was überlesen ?

Es es ist doch ganz einfach:
Im Slot readyRead die Funktion canReadLine checken.
Wenn wahr: readLine nutzen.

Keine Threads, keine Sync-Probleme...

cu
N2

von Tobias P. (hubertus)


Lesenswert?

Ja, klar geht das. Ich hab das jetzt ja auch so implementiert. ABER ich 
möchte doch folgendes tun: wenn auf einen Button geklickt wird, soll 
eine entsprechende Anfrage gesendet werden und die Antwort soll in einem 
Textfeld angezeigt werden. Pseudo-Beispiel:
1
void Button_click()
2
{
3
  QString response = serialport->send_query("version?");
4
  ui->textBox1->setText(response);
5
}

das geht natürlich mit dem komplett asynchronen Ansatz mit dem SIGNAL 
nicht wirklich. Die Frage war, ob man es irgendwie so implementieren 
kann, dass meine Klasse intern irgendwie das Signal verwendet, nach 
aussen hin aber so aussieht, als ob es nicht so wäre. (Mir fehlt da im 
Moment der richtige Fachbegriff für. Wie nennt man das? synchron vs. 
asynchron? bei Windows war das glaub ich irgendwie 'overlapped IO').

von Volker S. (vloki)


Lesenswert?

Tobias P. schrieb:
> ABER ich
> möchte doch folgendes tun: wenn auf einen Button geklickt wird, soll
> eine entsprechende Anfrage gesendet werden und die Antwort soll in einem
> Textfeld angezeigt werden.

Warum möchtest du das so machen? Also auf die Antwort warten?
Kannst du nicht einfach die Antwort plotten, wenn sie kommt?
Was machst du wenn gar keine Antwort kommt?

von Sven B. (scummos)


Lesenswert?

Eine Möglichkeit, Code ungefähr so zu schreiben, ist mit einem Callback, 
also du übergibst dem send_query eine Funktion als Argument, die 
aufgerufen wird wenn's fertig ist. Das kannst du noch als Lambda machen 
und dann hast du ungefähr das.
1
void send_query(const QByteArray& query, std::function<void(QByteArray)> onCompleted);
2
...
3
send_query("version?", [this](const QByteArray& reply) {
4
 ui->button->setText(QString::fromUtf8(reply));
5
});

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.