LabVIEWForum.de - Designproblem: RS232 Kommunikation

LabVIEWForum.de

Normale Version: Designproblem: RS232 Kommunikation
Du siehst gerade eine vereinfachte Darstellung unserer Inhalte. Normale Ansicht mit richtiger Formatierung.
Seiten: 1 2
Hallo,

ich habe ein Design-Problem für eine Kommunikation via RS232. Ich komme eigentlich aus der C++ - Ecke und hatte mit LabVIEW bisher nur am Rande zu tun. Jetzt will ich ein LabVIEW-Projekt, das bisher sehr mangelhaft umgesetzt war umschreiben und stoße dabei auf einige Probleme. Ich könnte mir aber vorstellen, dass das daran liegt, dass ich die Dinge aus dem falschen Blickwinkel betrachte, weshalb ich mir hier Hilfe erhoffe.

Das Szenario (vereinfacht):

Ein Gerät soll mit dem PC via RS232 kommunizieren. Das Gerät sendet nach einer Initialisierung permanent Datenpakete raus. Gleichzeitig kann man dem Gerät Befehle senden und bekommt Antwort-Pakete zurück. Wenn man einen Befehl gesendet hat, ist nicht bekannt, ob erst die Antwort zurückkommt, oder zunächst noch einige Daten-Pakete. Allerdings können die Pakete unterschieden werden.

Mindestens ein, idealerweise aber mehrere VIs sollen mit dem Gerät kommunizieren können. Über Buttons sollen Befehle gesendet werden können und die empfangenen Daten auf verschiedene Weise verarbeitet werden (Graph, abspeichern, etc.).

Lösungsansatz:

Mein erster Gedanke war, eine Klasse zu erstellen, die die Kommunikation handlet, d.h. den einkommenden Datenstrom permanent ausliest und nach Antwort- und Datenpaketen aufsplittet. Diese sollte den Datenstrom dann gleich noch parsen und die Daten daraus extrahieren (enthält verschiedene Messdaten). Diese Klasse hätte dann eine Funktion, um einen Befehl zu senden, die entweder einen Timeout oder die Antwort zurückliefern würde. Weiterhin hätte sie die Möglichkeit für jeden einkommenden Messwert-Datenstrom Listeners zu registrieren. Dafür hatte ich Queues angedacht, die man bei der Klasse registrieren und wieder abmelden kann. Das ganze habe ich versucht mit LVOOP umzusetzen.

Das Problem:

Das VI, das permanent samplet, muss auch permanent laufen. Das erste Problem ist also, wie ich das von außen wieder anhalten kann. Das habe ich mit Referenzen auf das VI gelöst, was ich irgendwie unschön finde. Das größere Problem ist, dass ich diesem Permanent laufenden VI ja nicht die Klasseninstanz übergeben kann, da ich sie ja dann nicht mehr zurückbekommen würde. Damit kann dieses VI keine wirkliche Objektmethode sein. Dadurch habe ich aber das Problem, dass ich Änderungen an der Klasse (Listener hinzugefügt, entfernt, etc.) in dem VI nicht mitbekomme. Natürlich könnte ich mir da jetzt irgendwie mit Queues behelfen, aber das wird dann gleich alles sehr komplex. Gibt es für so ein Szenario vielleicht irgendeine einfache und elegante Methode?

Vielen Dank,

Tobias
' schrieb:Mein erster Gedanke war, eine Klasse zu erstellen, die die Kommunikation handlet, d.h. den einkommenden Datenstrom permanent ausliest und nach Antwort- und Datenpaketen aufsplittet. Diese sollte den Datenstrom dann gleich noch parsen und die Daten daraus extrahieren (enthält verschiedene Messdaten). Diese Klasse hätte dann eine Funktion, um einen Befehl zu senden, die entweder einen Timeout oder die Antwort zurückliefern würde. Weiterhin hätte sie die Möglichkeit für jeden einkommenden Messwert-Datenstrom Listeners zu registrieren. Dafür hatte ich Queues angedacht, die man bei der Klasse registrieren und wieder abmelden kann.
Würde ich auch so machen.

Zitat:Das ganze habe ich versucht mit LVOOP umzusetzen.
Das nicht. Geht auch ohne.

Zitat:Das VI, das permanent samplet, muss auch permanent laufen. Das erste Problem ist also, wie ich das von außen wieder anhalten kann. Das habe ich mit Referenzen auf das VI gelöst, was ich irgendwie unschön finde.
Der Klasse über die Steuerqueue den Befehl senden, sich selbst zu beenden.

Zitat:Das größere Problem ist, dass ich diesem Permanent laufenden VI ja nicht die Klasseninstanz übergeben kann, da ich sie ja dann nicht mehr zurückbekommen würde. Damit kann dieses VI keine wirkliche Objektmethode sein. Dadurch habe ich aber das Problem, dass ich Änderungen an der Klasse (Listener hinzugefügt, entfernt, etc.) in dem VI nicht mitbekomme. Natürlich könnte ich mir da jetzt irgendwie mit Queues behelfen, aber das wird dann gleich alles sehr komplex.
Das klingt, als wolltest du die Klasse, die die RS232 handlet, als Methode einer übergeordneten, applikationsspezifischen Klasse haben. Da sehe ich aber keine Notwendigkeit für. Für so kompliziert halte ich das mit den Queues nicht.
Den fett markierten Text verstehe ich nicht. Welches VI bekommt die Änderung an der Klasse nicht mit? Warum sollte das übergeordnete VI eine Änderung in der Klasse mitbekommen?

Ich hab mal folgendes überlegt:
Das mit dem Listener-Management könnte man über ein Array of Queue machen. Jeder der Daten haben will, muss der Klasse eine Queue (Referenz auf Queue) übergeben (das geht). Diese Queue wird dann nur von diesem einen Anmelder ausgelesen. Die RS232-Klasse merkt sich die Queues in einer Liste (also in einem Array). Das Management, welche empfangenen Daten über welche Queue weitergesendet werden müssen, liegt innerhalb der RS232-Klasse.

[*grübel*]

Man kann auch zusammen mit den Sendedaten eine Queue an die RS232-Klasse übergeben. Die Antwort auf diese Sendung geht dann an diese Queue.
AbsolutBig Grin

Ich würde es mit einer Klasse machen, wie oben beschrieben. Dazu 2 Queues, über eine werden die Befehle an die RS232-Klasse übergeben über die andere Daten zurück an die Main.
Wenn es mehrere Fenster parallel sein sollten, die die Daten anzeigen sollen, dann halt mehr Queues, pro paralleles VI eine.
Hi,

erstmal vielen Dank für die Antworten. Ich glaube, ich habe teilweise noch nicht so ganz verständlich machen können, was ich mir vorgestellt hatte.

' schrieb:Zu LVOOP: Das nicht. Geht auch ohne.
Hm, und ohne dass man globale Variablen oder Ähnliches verwendet? Unten redest Du auch immer von "Klassen". Ich dachte, das ist dann automatisch LVOOP?!

' schrieb:Der Klasse über die Steuerqueue den Befehl senden, sich selbst zu beenden.
Ja, das ist natürlich eine Möglichkeit, die auch ein Kollege vorgeschlagen hatte. Vielleicht ist das tatsächlich die beste Möglichkeit.


' schrieb:Das klingt, als wolltest du die Klasse, die die RS232 handlet, als Methode einer übergeordneten, applikationsspezifischen Klasse haben. Da sehe ich aber keine Notwendigkeit für. Für so kompliziert halte ich das mit den Queues nicht.
Den fett markierten Text verstehe ich nicht. Welches VI bekommt die Änderung an der Klasse nicht mit? Warum sollte das übergeordnete VI eine Änderung in der Klasse mitbekommen?


Ich hab mal folgendes überlegt:
Das mit dem Listener-Management könnte man über ein Array of Queue machen. Jeder der Daten haben will, muss der Klasse eine Queue (Referenz auf Queue) übergeben (das geht). Diese Queue wird dann nur von diesem einen Anmelder ausgelesen. Die RS232-Klasse merkt sich die Queues in einer Liste (also in einem Array). Das Management, welche empfangenen Daten über welche Queue weitergesendet werden müssen, liegt innerhalb der RS232-Klasse.
Genau so habe ich es auch implementiert. Und genau da liegt der Punkt, wo ich ein Problem habe. Bei mir sind die Listener als ein Array von Queues innerhalb der Objektdaten implementiert. Am Anfang ist das leer. Jetzt wird das Kommunikations-VI gestartet, das dann durchlaufen soll (dazu, wie und wo das gestartet wird, unten nochmal mehr). Jetzt registriert sich ein anderes VI als Listener (gibt eine Referenz auf eine Queue an eine AddListener Methode der Klasse, die das in das Listener-Array hinzufügt). Allerdings hat das Kommunikations-VI ja jetzt keinen Zugriff auf dieses Array, da es in den privaten Daten der Klasse liegt. Entweder kann ich das als globale Variable machen (unschön wegen Datenkapselung, oder?), oder selber wieder in eine Queue legen (was dann zu einer Queue mit einem Array von Queues führt - nicht grade elegant), oder es geht irgendwie anders und ich sehe das nur nicht.

Warum ich das ganze als Klasse machen wollte, war, dass ich dachte, ich könnte es als Singleton implementieren. D.h., ein VI fordert die Kommunikation an und bekommt eine "Referenz" darauf und kann jetzt Queues als Listener registrieren und andere Kommunikation machen. Jetzt wird ein anderes VI gestartet, dass die Kommunikation auch anfordert. Da sie schon existiert, bekommt es nur eine Referenz auf das schon existierende Objekt zurück und kann damit ganz normal weitermachen (Ich hatte gedacht, das als Mischung zwischen den Beispielen Grundlagen->Objektorientiert->Design Pattern.lvproj und Grundlagen->Objektorientiert->ReferenceObject.lvproj zu implementieren).

' schrieb:Man kann auch zusammen mit den Sendedaten eine Queue an die RS232-Klasse übergeben. Die Antwort auf diese Sendung geht dann an diese Queue.
Die Antwort-Queue ist gar nicht das Problem, sondern die Queues für den kontinuierlichen Datenstrom.

Jetzt noch zum Starten des kontinuierlich laufenden VIs: Da ich es ja in verschiedenen VIs dynamisch verwenden wollte, kann ich es eigentlich nicht in einem VI ablegen, denn dann würde es ja beendet, wenn dieses VI beendet wird, obwohl vielleicht ein anderes VI noch darauf zugreift. Ich hatte deshalb gedacht, die Klasse bekommt eine StartCommunication-Methode, die dann über eine Referenz auf das RS232-VI dieses startet. Aber das ist auch nicht so richtig schön, finde ich.

Wenn ihr wollt, kann ich mal ein Beispiel-Projekt hochladen, wo ich die eigentliche Kommunikation rausnehme, damit ihr mal seht, wie das momentan aussieht.

Gruß,

Tobias
' schrieb:AbsolutBig Grin

Ich würde es mit einer Klasse machen, wie oben beschrieben. Dazu 2 Queues, über eine werden die Befehle an die RS232-Klasse übergeben über die andere Daten zurück an die Main.
Wenn es mehrere Fenster parallel sein sollten, die die Daten anzeigen sollen, dann halt mehr Queues, pro paralleles VI eine.
Das Ding ist, dass ich das dynamisch halten wollte, wieviele Queues und wo die hingehen. D.h. die Queues sollten nicht vom Kommunikations-VI angelegt werden, sondern von "außen" übergeben.

Gruß,

Tobias
' schrieb:Hm, und ohne dass man globale Variablen oder Ähnliches verwendet? Unten redest Du auch immer von "Klassen". Ich dachte, das ist dann automatisch LVOOP?!
Eine Klasse ist, wie du ja weist, eine Ansammlung von Methoden, Events, Propertys und Feldern, also gekapselten Daten. Genau dieses kannst du auch mit einem SubVI machen - und zwar so, dass kein Außenstehender was von den Methoden und Feldern sieht. Propertys kann man durch Enumeratoren und Variant-Ein/Ausgängen realsieren. "Globale Variablen" kann man auch mit einem SubVI machen: Schieberegister in While-Schleife.

Was ich als Klasse bezeichne, ist ein SubVI mit einer Statemachine (das sind die Methoden), einem Queue-Lesen-Element (das ist das SubVI-Aufrufen) und einer While-Schleife außenherum. Die While-Schleife enthält Schieberegister, die die Felder (gekapselten Daten) enthalten. Jetzt hast du auf höchster Ebene in dem SubVI (in der Klasse) alle benötigten Daten.

Zitat:Allerdings hat das Kommunikations-VI ja jetzt keinen Zugriff auf dieses Array, da es in den privaten Daten der Klasse liegt. Entweder kann ich das als globale Variable machen (unschön wegen Datenkapselung, oder?), oder selber wieder in eine Queue legen (was dann zu einer Queue mit einem Array von Queues führt - nicht grade elegant), oder es geht irgendwie anders und ich sehe das nur nicht.
Globale Variablen sind sehr schlecht. Dieses Problem wird durch das Schieberegister gelöst.

Zitat:Warum ich das ganze als Klasse machen wollte, war, dass ich dachte, ich könnte es als Singleton implementieren. D.h., ein VI fordert die Kommunikation an und bekommt eine "Referenz" darauf und kann jetzt Queues als Listener registrieren und andere Kommunikation machen. Jetzt wird ein anderes VI gestartet, dass die Kommunikation auch anfordert. Da sie schon existiert, bekommt es nur eine Referenz auf das schon existierende Objekt zurück und kann damit ganz normal weitermachen (Ich hatte gedacht, das als Mischung zwischen den Beispielen Grundlagen->Objektorientiert->Design Pattern.lvproj und Grundlagen->Objektorientiert->ReferenceObject.lvproj zu implementieren).
Dieses Denken ist oop-gerecht. Das geht in LV, das ein Denken nach dem Datenflußprinzip erfordert, aber eigentlich nicht.

Zitat:Jetzt noch zum Starten des kontinuierlich laufenden VIs: Da ich es ja in verschiedenen VIs dynamisch verwenden wollte, kann ich es eigentlich nicht in einem VI ablegen, denn dann würde es ja beendet, wenn dieses VI beendet wird, obwohl vielleicht ein anderes VI noch darauf zugreift. Ich hatte deshalb gedacht, die Klasse bekommt eine StartCommunication-Methode, die dann über eine Referenz auf das RS232-VI dieses startet. Aber das ist auch nicht so richtig schön, finde ich.
Deswegen legt du die Klasse einfach auf das MainVI als paralleles SubVI. Dann läuft es wie ein eigener Prozess innerhalb deiner LV-Applikation.
Gesteuert wird dieses parallele SubVI durch eine einzige Queue. Eine Queue macht nichts weiter als einen Datenfluss imaginär - also im BD unsichtbar - zu realisieren. Dieser Queue bedienen sich alle die VIs, die die RS232-Kommunikation nutzen wollen. Wie der Datentyp der Queue aussieht - da bist du völlig frei.


Zitat:Wenn ihr wollt, kann ich mal ein Beispiel-Projekt hochladen, wo ich die eigentliche Kommunikation rausnehme, damit ihr mal seht, wie das momentan aussieht.
Gute Idee.
Du kannst dir diese Klasse als Muster ohne Wert (am Ende des Beitrages) mal ansehen. Vielleicht nützt es ja was.
' schrieb:Eine Klasse ist, wie du ja weist, eine Ansammlung von Methoden, Events, Propertys und Feldern, also gekapselten Daten. Genau dieses kannst du auch mit einem SubVI machen - und zwar so, dass kein Außenstehender was von den Methoden und Feldern sieht. Propertys kann man durch Enumeratoren und Variant-Ein/Ausgängen realsieren. "Globale Variablen" kann man auch mit einem SubVI machen: Schieberegister in While-Schleife.

Ah, ok, jetzt verstehe ich, worauf Du raus wolltest. Ich wurde mittlerweile auf dem englischen Forum auch auf "Ben's Action Engine" hingewiesen und finde den Ansatz recht interessant, besonders, wenn man das ganze dann noch mit VIs kapselt (um die Queues von den Benutzer-VIs zu verstecken). Allerdings bin ich noch nicht so ganz sicher, ob mir das wirklich gefällt. Ich habe noch den Eindruck, dass das sehr viel Funktionalität an einem Ort vereint, was auch meiner Programmiererfahrung (ok, nicht LabVIEWSmile) meistens eher von Nachteil ist. Aber ich werde mich damit mal noch beschäftigen.

Allerdings würde ich eher nicht dazu tendieren, das eine "Klasse" zu nennen, denn (zumindest, wie ich das bisher verstehe) es fehlen hier elementare Dinge, die für mich eine Klasse ausmachen, wie Vererbung, Polymorphie, late binding, etc. Für mich ist das eher ein Modul, ein bisschen wie das, was man in C in einer Datei kapselt ohne dass man dafür OOP verwenden würde. Aber letztlich kommt das wahrscheinlich auf die Definition an.

Könnte man ein solches permanent laufendes VI jetzt eigentlich auch reentrant machen und damit z.B. gleichzeitig auf verschiedenen COM-Ports operieren lassen? Ich bin mir noch nicht sicher, ob ich das brauchen würde, aber es klingt verlockend.

Ich habe zu der Implementierung allerdings noch ein paar Fragen:

- Das Beispiel, dass Du gepostet hast führt ja - wenn ich das richtig verstehe - immer nur eine Funktion aus und wartet dann wieder, bis ein neuer Befehl reinkommt, bevor es wieder was tut. Mein Programm müsste ja permanent samplen. Trotzdem müsste das Sampling auf die Daten zugreifen, die auch von den anderen Methoden manipuliert würden. Ich könnte jetzt natürlich immer, wenn kein Befehl anliegt in den Sample-State gehen, aber das hätte den Nachteil, dass das Sampling unterbrochen würde wenn eine andere Funktion länger bräuchte, was auch nicht schön wäre. Kann man das irgendwie geschickt parallelisieren?

- Wo würde man die Parameter- und Result-Queues halten? Wenn ich Deinen Code richtig verstehe, holst Du sie Dir nach Namen, oder?

Hm... ich glaube, ich habe noch mehr, aber die fallen mir grade nicht mehr ein.

Ich werde damit jetzt mal noch ein bisschen rumprobieren, vielleicht aber auch nochmal eine LVOOP oder dqGOOP Version mit Referenzen ausprobieren (für mich ist das halt grundsätzlich der gewohntere Blickwinkel, aber ich lerne auch gerne dazu).

Im Moment habe ich noch nicht wirklich was, das ich mal sinnvoll hochladen könnte. Aber ich habe das noch vor.

Irgendwie ist LabVIEW schon eine recht spannende Sache. Gibt es zu der Sprache eigentlich schon irgendwelche akademischen Abhandlungen, die die Paradigmen mal im Lichte der Informatik betrachten? Mir waren aus dem Studium funktionale Sprachen bekannt (wenn auch mehr theoretisch als praktisch), aber mit Datenflusssprachen bin ich vor LabVIEW noch nie in Berührung bekommen.

Danke nochmal,

Tobias
' schrieb:Allerdings würde ich eher nicht dazu tendieren, das eine "Klasse" zu nennen, denn (zumindest, wie ich das bisher verstehe) es fehlen hier elementare Dinge, die für mich eine Klasse ausmachen, wie Vererbung, Polymorphie, late binding, etc. Für mich ist das eher ein Modul, ein bisschen wie das, was man in C in einer Datei kapselt ohne dass man dafür OOP verwenden würde. Aber letztlich kommt das wahrscheinlich auf die Definition an.
Dazu gibt es LVOOP, was du auch im Gemisch mit der o.g. Statemachine ruhig verwenden kannst.

' schrieb:Könnte man ein solches permanent laufendes VI jetzt eigentlich auch reentrant machen und damit z.B. gleichzeitig auf verschiedenen COM-Ports operieren lassen? Ich bin mir noch nicht sicher, ob ich das brauchen würde, aber es klingt verlockend.
Klar
' schrieb:Allerdings würde ich eher nicht dazu tendieren, das eine "Klasse" zu nennen, denn (zumindest, wie ich das bisher verstehe) es fehlen hier elementare Dinge, die für mich eine Klasse ausmachen, wie Vererbung, Polymorphie, late binding, etc. Für mich ist das eher ein Modul, ein bisschen wie das, was man in C in einer Datei kapselt ohne dass man dafür OOP verwenden würde. Aber letztlich kommt das wahrscheinlich auf die Definition an.

Nachtrag: Es fehlt auch Instanziierung, oder? Es sei denn man würde das VI reentrant machen (würde das gehen?)

Gruß,

Tobias
Seiten: 1 2
Referenz-URLs