Der Zustandsautomat ist eine der grundlegenden Architekturen, die von LabVIEW-Entwicklern häufig zum schnellen Erstellen von Anwendungen verwendet wird. Die Architektur des Zustandsautomaten kann zur Implementierung komplexer Algorithmen zur Entscheidungsfindung verwendet werden, die durch Zustandsdiagramme oder Ablaufdiagramme dargestellt werden. Ein Zustandsautomat kann mithilfe von LabVIEW-Funktionen implementiert werden. Für die Architektur sind keine zusätzlichen Toolkits oder Module erforderlich.
In diesem Artikel wird beschrieben, was ein Zustandsautomat ist, er enthält ferner Anwendungsbeispiele, konzeptuelle Beispiele für einen Zustandsautomaten und Programmcodebeispiele für einen Zustandsautomaten.
Ein Zustandsautomat ist eine Programmierarchitektur, die einen dynamischen Fluss zu Zuständen in Abhängigkeit von Werten aus vorigen Zuständen oder Benutzereingängen ermöglicht.
Diese Architektur eignet sich für Anwendungen, die eine Kombination aus folgenden Komponenten darstellen:
Ein Zustand kann als der Status innerhalb des Programms definiert werden, während der gesamte Task des Programms ausgeführt wird. Beispiele für Zustände sind Initialisierung, Warten, Ausführung einer Berechnung, Statusprüfung usw.
Eine logische Anweisung hilft bei der Bestimmung, wann und zu welchem Zustand gewechselt werden soll. Mit Hilfe von Ereignissen können Sie den Übergang von einem Zustand zum nächsten auslösen. Dabei kann es sich um programmatische Ereignisse oder um benutzerdefinierte Ereignisse handeln, etwa das Drücken einer Schaltfläche.
Jeder Zustand eines Zustandsautomaten bewirkt etwas anderes und ruft andere Zustände auf. Die Zustandskommunikation hängt von einer bestimmten Bedingung oder Sequenz ab. Um das Zustandsdiagramm in eine LabVIEW-Programmierarchitektur umzuwandeln, benötigen Sie die folgende Infrastruktur:
Zustandsautomaten werden in Anwendungen mit unterscheidbaren Zuständen verwendet. Jeder Zustand kann zu einem oder mehreren Zuständen oder aber auch zum Ende des gesamten Prozessablaufs führen. Ein Zustandsautomat stützt sich auf Benutzereingaben oder zustandsinterne Berechnungen, um zu bestimmen, zu welchem Zustand als nächstes gewechselt werden soll. Viele Applikationen haben einen „Initialisierungszustand“. Diesem folgt in der Regel ein Standardzustand, in dem verschiedene Aktionen durchgeführt werden können. Die Aktionen können von vorherigen und aktuellen Eingaben sowie Zuständen abhängig sein. Der Zustand „Beenden“ kann dann für Bereinigungsvorgänge verwendet werden.
Neben ihrer nützlichen Fähigkeit, Entscheidungsfindungsalgorithmen zu implementieren, bieten Zustandsmaschinen auch eine zweckmäßige Art der Anwendungsplanung. Mit zunehmender Komplexität von Anwendungen wird auch der Bedarf an adäquaten Entwürfen erhöht. Zustandsdiagramme und Ablaufdiagramme sind nützlich und manchmal für den Entwurfsprozess unerlässlich. Zustandsautomaten sind nicht nur für die Anwendungsplanung von Vorteilen, sondern auch einfach zu erstellen.
Das Entwurfsmuster für Zustandsautomaten eignet sich beispielsweise für folgende Anwendungen:
Ein weiteres Entwurfsmuster zur Implementierung einer Benutzeroberfläche ist der Handler für Nachrichten-Queues. Ein Handler für Nachrichten-Queues ist eine anspruchsvollere Version des Zustandsautomaten und bietet zusätzliche Flexibilität, aber auch zusätzliche Komplexität.
Zum Erstellen eines effektiven Zustandsautomaten muss der Entwickler (1) eine Liste der möglichen Zustände erstellen. Mit dieser Liste kann der Entwickler (2) planen, wie jeder Zustand miteinander in Beziehung steht. Anschließend kann das Zustandsdiagramm (3) in die grafische Programmierarchitektur von LabVIEW umgewandelt werden.
In diesem Beispiel möchten wir eine Anwendung erzeugen, die kontinuierlich eine Kanone abfeuert, ohne dass diese gefährlich heiß wird.
(1) Mögliche Zustände auflisten
Zunächst erstellen wir eine Liste aller möglichen Zustände für unseren Task. Um die kontinuierliche Aktivierung der Kanone zu erledigen, müssen Sie:
(2) Beziehungen zwischen Zuständen in einem Zustandsdiagramm abbilden
Als Nächstes betrachten wir die Beziehung zwischen diesen Zuständen und erstellen ein Zustandsdiagramm. Überlegen Sie beim Erstellen des Zustandsdiagramms, was dazu führen würde, dass das Programm von einem Zustand zum nächsten wechselt – handelt es sich um einen automatischen Übergang? Gibt es einen externen Trigger vom Benutzer? Basiert der Übergang auf dem Ergebnis einer Berechnung?
Betrachten wir beispielsweise die Beziehung zwischen dem Initialisierungszustand und anderen Zuständen.
Beachten Sie, dass wir, während wir über die Beziehung zwischen den Zuständen nachzudenken begonnen haben, damit begonnen haben, die Logik für den Wechsel zwischen diesen Zuständen zu definieren. Die Programmierlogik, die wir im Programmcode verwenden, hängt von (1) der Anzahl der möglichen Übergangszustände und (2) der Anzahl der Eingänge ab, die in der Logik berücksichtigt werden. Die Optionen für den Code-Übergang werden im Abschnitt Beispiele für den Übergangscode beschrieben.
Arbeiten Sie weiter durch, wie sich die Zustände zueinander verhalten, bis Sie ein vollständiges Zustandsdiagramm haben. Das Zustandsdiagramm enthält alle Zustände und deren Beziehung (Abbildung 1). In diesem Diagramm beschreiben die Zustände (ovale Knoten) die Aktionen, die ausgeführt werden, wenn sich der Steuerungsprozess in diesem Zustand befindet, während die Übergänge (Pfeile) lediglich beschreiben, wann und wie der Prozess von einem Zustand zum nächsten wechseln kann.
Abbildung 1: Zustandsdiagramm des Abfeuerns einer Kanone
Die Beziehung zwischen den einzelnen Zuständen hilft Ihnen bei der Programmierung der Logik, die für den Übergang von einem Zustand zum nächsten erforderlich ist.
(3) Erstellen eines Zustandsautomaten in LabVIEW
Nachdem Sie die Zustände und ihre Beziehung in einem Zustandsdiagramm definiert haben, können Sie dieses auf die Codierungsarchitektur in LabVIEW übertragen (Abbildung 2). Der Fluss zwischen den Zuständen des Zustandsdiagramms (Abbildung 1) wird von der Schleife implementiert. Die einzelnen Zustände werden durch Cases in der Case-Struktur ersetzt. Jeder Zustand im abgebildeten Diagramm entspricht einem Unterdiagramm der Case-Struktur. Jeder Zustand:
Ein Schieberegister an der While-Schleife verfolgt den aktuellen Zustand, der an den Eingang der Case-Struktur übergeben wird.
Abbildung 2: Zustandsautomat
Ein Beispiel für die Anpassung dieser Vorlage an eine Messanwendung finden Sie im Beispielprojekt Einzelschussmessung, das im Dialogfeld Projekt erstellen verfügbar ist.
1. der Programmcode zum Beenden nur ausgeführt wird, wenn der Benutzer die Anwendung beenden möchte.
2. der Programmcode zum Beenden immer vollständig ausgeführt wird.
Weitere Informationen zu den ersten Schritten finden Sie in der Anleitung Anpassen der Vorlage für den einfachen Zustandsautomaten.
Bevor Sie die Vorlage bearbeiten, stellen Sie sich folgende Fragen:
Es gibt verschiedene Verfahren, um zu bestimmen, in welchen Zustand als nächstes übergegangen werden soll, die unten erörtert werden.
Beachten Sie, dass die Beispielabbildungen den Zustand „Init“ anzeigen, aber diese Übergangsmöglichkeiten auf jeden Zustand angewandt werden können.
Eins zu eins: Wenn Sie immer von Zustand A in Zustand B wechseln, müssen Sie keine Logik programmieren. Geben Sie einfach den Namen des nächsten Cases (Case B) an das Schieberegister aus.
Abbildung 3a: Nur ein möglicher Übergangszustand
Einer auf zwei: Wenn Sie von Zustand A auf Zustand B oder Zustand C wechseln könnten, können Sie eine Select-Funktion verwenden, um den Status eines Anzeigeelements zu beurteilen. Sie sollten etwas evaluieren, das bestimmt, zu welchem Zustand Sie wechseln möchten. In Abbildung 3b sehen wir beispielsweise, dass die Benutzereingabe der Stopp-Schaltfläche bestimmt, ob wir den Einschaltzustand verlassen oder zum Herunterfahren übergehen.
Abbildung 3b: Zwei mögliche Übergangszustände
Eins-zu-mehreren mithilfe von Arrays: Wenn Sie mehrere Zustände haben, in die Sie wechseln können, können Sie einen booleschen Bereich verwenden, der einer Enum-Konstante zugeordnet ist, um den Übergang zu programmieren. In Abbildung 3c wird beispielsweise ein Programmcode ausgeführt, bei dem das Ergebnis des Programmcode den Übergang bestimmt, bei dessen Ausgabe es sich um ein boolesches Array handelt. Das boolesche Array korreliert mit einer Enum-Konstante, die eine Liste der möglichen Zustände enthält, auf die Sie übergehen können. Mit der Funktion „Array indizieren“ wird der Index des ersten booleschen „True“-Werts im booleschen Array ausgegeben. Anschließend können Sie mit der Funktion „Teil-Array“ den entsprechenden Wert aus der Enum-Konstante entnehmen, mit der er korreliert.
Abbildung 3c
Tipp : Denken Sie daran, dass Arrays mit 0 und Enums mit 1 indiziert werden. Um das boolesche Array mit der Enum-Konstante zu korrelieren, korrigieren Sie diesen Offset mit der Funktion Inkrementieren.
Eins-zu-mehreren mithilfe der While-Schleife: Eine weitere Option, wenn Sie mehrere mögliche Übergangszustände haben, besteht darin, eine While-Schleife innerhalb eines Cases zu verwenden. Der Programmcode in der While-Schleife wird so lange fortgesetzt, bis ein boolescher Status auf TRUE gesetzt wird und die Stopp-Schaltfläche ausgelöst wird. Auf diese Weise kann Programmcode so lange ausgeführt werden, bis ein Trigger-Ereignis auftritt.
Abbildung 3d zeigt einen „Init“-Zustand, der mit einer inneren Schleife und einer Case-Struktur zum nächsten Zustand wechselt. Die innere Case-Struktur enthält ein Diagramm für jeden Übergang, der den aktuellen Zustand verlässt. Jeder Case in der inneren Case-Struktur hat zwei Ausgänge: einen booleschen Wert, mit dem festgelegt wird, ob der Übergang übernommen werden soll oder nicht, und eine Enum-Konstante, mit der der Zustand festgelegt wird, zu dem der Übergang führt. Wenn der Schleifenindex als Eingang für die Case-Struktur verwendet wird, durchläuft dieser Programmcode jeden Übergangs-Case nacheinander, bis ein Diagramm mit dem booleschen Ausgang „True“ gefunden wird. Nachdem der boolesche Ausgang „True“ gefunden wurde, gibt der Case den neuen Zustand aus, zu dem der Übergang wechselt. Obwohl diese Methode etwas komplizierter erscheinen kann als die vorherigen Methoden, bietet sie die Möglichkeit, den Übergängen Namen hinzuzufügen, indem die Ausgabe des Schleifenindex in einen Enum-Typ umgewandelt wird. Mit diesem Vorteil können Sie Ihrem Übergangscode „automatische Dokumentation“ hinzufügen.
Abbildung 3d
Programmcode-Redundanz
Problem: Der schwierigste Teil beim Erstellen eines Zustandsautomaten ist die Unterscheidung zwischen möglichen Zuständen im Zustandsdiagramm. Im Zustandsdiagramm der Cola-Maschine (Abbildung 4) könnten wir z. B. 0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50 Cent als Zustände haben, anstatt einen Zustand „Warten auf Antwort“, der je nach Art der Münze von einem Zustand zum nächsten wechselt. Dadurch würden elf verschiedene Zustände mit demselben Case-Diagramm erzeugt. Redundanter Programmcode kann in einer größeren Anwendung ein schwerwiegendes Problem darstellen.
Abhilfe: Wenn verschiedene Zustände das gleiche Case-Diagramm haben, versuchen Sie, diese in einem Zustand zu kombinieren. So wird z. B. der Zustand “Warten auf Antwort“ erstellt, um Code-Redundanz zu vermeiden.
Verwendung von Enums
Problem: Enums werden häufig als Case-Selektoren in Zustandsautomaten verwendet. Wenn der Benutzer versucht, dieser Enum einen Zustand hinzuzufügen oder zu löschen, werden die verbleibenden angeschlossenen Verbindungen mit den Kopien dieser Enum fehlerhaft. Dies ist das häufigste Problem bei der programmatischen Umsetzung von Zustandsautomaten mit Enums.
Lösung: Zwei mögliche Lösungen für dieses Problem sind:
1. Wenn alle Enums aus der geänderten Enum kopiert werden, verschwinden die Unterbrechungen.
2. Erstellen Sie ein neues Element mit der Enum und wählen Sie aus dem Untermenü die Option „Typdefinition“ aus. Bei Auswahl von Typdefinition werden alle Enum-Kopien automatisch aktualisiert, wenn der Benutzer einen Zustand hinzufügt oder entfernt.
Wenn Sie mehr über Zustandsautomaten und andere fortgeschrittene Architekturen in LabVIEW erfahren möchten, sollten Sie den Kundenschulungskurs LabVIEW-Grundlagen 2 absolvieren.