Diese Seite ist Teil des Projekts Automatische Gewächshauslüftung.
|
Der Zustandsautomat wird durch nachfolgenden Komponenten realisiert.
Der aktuelle Zustand der App wird in der globalen Variablen State gehalten. Die möglichen Ausprägungen sind:
Die Voreinstellung ist beliebig, da die erste Anweisung bei Programmstart den Zustand auf Disconnected einstellt.
Beim Start der App wird als erstes der Eventhandler Screen1.Initialize. aufgerufen. Hier wird der Zustandsautomat initialisiert und der Zustand Disconnected eingestellt.
Der Timer clkDoState wird genutzt, um die einzelnen Aktivitäten zu triggern, die zustandsspezifisch regelmäßig ausgeführt werden müssen (s. Zustandsaktivitäten). Er wird von den jeweiligen "enterState"-Prozeduren passend eingestellt (s. Zustandswechsel).
Im Zustand
Wenn die Bluetooth-Verbindung aus irgendwelchen Gründen getrennt wird, lösen die entsprechenden Übertragungsfunktionen eine Exception aus. Diese kann durch das Ereignis ErrorOccurred behandelt werden. Die Fehler mit den Fehlernummern 516, 515 und 507 treten auf, wenn die Verbindung nicht mehr besteht. In diesem Fall ist ein Zustandswechsel zum Zustand Disconnected notwendig.
Bei anderen Fehlern wird die Verarbeitung gestoppt und der Fehler angezeigt. Dieser Fall ist bisher noch nicht aufgetreten und wird auch nur sicherheitshalber behandelt. Sollte er doch häufiger auftreten wäre eine Rücksetzfunktion notwendig.
Wenn die Applikation deaktiviert wird, wird eine evtl. bestehende Verbindung abgebaut und die Applikation beendet.
Die Anweisungen zum Zustandswechsel sind in drei Prozeduren zusammengefasst, damit dieser Wechsel an beliebigen Programmstellen eingeleitet werden kann. Diese sind enterDisconnected, enterConnected und enterUpdatePending.
Zu Beginn eines jeden Zustandswechsels wird zunächst der Timer abgeschaltet. Da dieser asynchron läuft, könnte es sein, das darüber „Do“-Aktionen in einem undefinierten Zustand ausgelöst wird. Die Zustandsvariable wird auf den Wert des Zielzustands „0“ gesetzt. Danach wird die Verbindung aktiv unterbrochen. Dies ist eine reine Sicherheitsmaßnahme. enterDisconnected dürfte eigentlich nur dann betreten werden, wenn die Verbindung nicht mehr besteht.
In den nachfolgenden Anweisungen werden die Slider-Stellungen verändert. Damit diese Veränderung nicht ungewollt an die Steuerung übertragen wird, wird SlidersEnabled auf false gesetzt. Diese Variable wird an den entsprechenden Stellen abgefragt und unterdrückt dort die Reaktion auf die Änderung.
Es folgt das Zurücksetzen und die Deaktivierung der Bedienelemente. Ist dies geschehen, werden die Sperre für die Slider wieder zurückgesetzt und der Standard-Timer wieder gestartet. Innerhalb des Timer-Events wird dann versucht werden, (erneut) eine Verbindung zu der Lüftersteuerung aufzubauen.
Auch hier wird zunächst der Standard-Timer abgeschaltet, die Statusvariable auf den neuen Wert gesetzt und die Unterdrückung der Slider-Reaktionen aktiviert. Danach werden die Schaltflächen zur Modus-Steuerung aktiviert. Der Standard-Timer wird wieder aktiviert und nach Ablauf des Timer wird die „Do“-Aktivität dieses Zustands erstmalig angesteuert. Dort werden dann die Zustandsanzeigen der Lüftersteuerung aktualisiert.
Der Wechsel in den Zustand „UpdatePending“ erfolgt immer dann, wenn eine Slider-Position durch den Anwender geändert wurde. Dies ist ein mehr oder weniger kontinuierlicher Vorgang, d.h. bis zum Erreichen der beabsichtigten Endposition, wird das zugehörige Ereignis mehrfach ausgelöst. Es macht natürlich keinen Sinn, jedes Mal eine Datenübertragung durchzuführen. Aus diesem Grund werden die Daten gesammelt. Jede erneute Änderung einer Slider-Position führt zu einem Zurücksetzen des Timers (faktisch ruft handlePositionChanged jedes Mal enterUpdatePending auf). Eine Übertragung findet erst dann statt, wenn der Standard-Timer abgelaufen ist (z.Zt. 500 ms), d.h. für diesen Zeitraum keine Änderung stattgefunden hat. Nach Ablauf dieser Zeit kann man davon ausgehen, dass der Anwender den Slider auf die beabsichtigte Endposition manövriert hat.
Beim Wechsel in diesen Zustand wird nur die Zustandsvariable auf den korrekten Wert gesetzt und der Standard-Timer neu initialisiert. Nach Ablauf des Timers werden die Daten an die Lüftersteuerung übertragen.
Die einzelnen, regelmäßig auszuführenden Zustandsaktivitäten werden über den Timer clkDoState angesteuert. Der Timer wird beim Eintreten in einen Zustand passend eingestellt.
Der Verbindungsaufbau ist in der Prozedur tryConnect gekapselt.
Die MAC-Adresse des HC-05 ist in der globalen Variablen GreenhouseMac hinterlegt. App Inventor bietet eine lokale Datenbank, in der man diese Adresse dynamisch hinterlegen könnte, und auch die Möglichkeit, einen Selektionsdialog aufzubauen. Hierauf habe ich der Einfachheit halber verzichtet und die Adresse fest hinterlegt.
Die Routine ist einfach aufgebaut. Es wird versucht, eine Verbindung herzustellen. Ist dies möglich, wird sicherheitshalber der komplette Eingabepuffer geleert. Zuletzt wird in den Zustand "Connected" gewechselt.
Die Funktion updateState fragt zunächst den Gewächshaus-Zustand per Bluetooth ab und zeigt sie dann mit Hilfe der Funktion ShowState auf den Display an.
Zunächst werden evtl. Reste eine vorhergehenden, nicht ordnungsgemäß durchgeführten Übertragung vernichtet, d.h. in eine Dummy-Variable eingelesen. Durch das Senden eines "?" wird die Gewächshaus-Steuerung veranlasst, den aktuellen Zustand des Gewächshauses zu übertragen (s. Übertragungsprotokoll). Danach wird 100 ms gewartet, um eine abgeschlossene Übertragung sicher zu stellen. Zuletzt werden die Zustandsdaten eingelesen und an ShowState übergeben.
ShowState übernimmt die Übertragung der Zustandsdaten in die Anzeige-Elemente.
Zunächst werden die Slider-Aktivitäten gesperrt, in dem die Variable SlidersEnabled auf false gesetzt wird. Die nachfolgende Änderung der Slider-Werte löst ein PositionChanded-Ereignis aus. Leider kann man nicht erkennen, ob das Ereignis durch eine Aktion des Anwenders oder durch das Programm vorgenommen wurde. Eine Rückübertragung an die Gewächshaussteuerung ist aber nur dann sinnvoll, wenn die Slider-Position vom Anwender geändert wurde. über SlidersEnabled kann die Rückübertragung unterdrückt werden.
Die einzelnen Zustandsvariablen sind im dem Datenstring durch "," getrennt. Über die Funktion split wird dieser String in die einzelnen Elemente zerlegt und der lokalen Listen-Variablen State zugewiesen. State wird nur dann weiterverarbeitet, wenn die Anzahl der Listenelemente der Erwartung entspricht, d.h. neun Elemente vorliegen. Die Werte werden dann in die einzelnen Anzeige-Elemente übertragen (s.u.).
Zum Schluss werden die Slider wieder aktiviert, so dass sie auf Anwender-Aktionen reagieren können.
Bei Text-Anzeige-Elementen wird der Text-Eigenschaft einfach der Wert des entsprechenden Listenelements zugewiesen.
Die Zuweisung an Slider-Elemente erfolgt über dir Funktion SetSliderValue (s.u.). Zu beachten ist, dass das letzte Listenelement mit <CR><LF> abgeschlossen ist. Das eigentliche Nutzdatum ist das nur das erste Byte. Dies muss abgetrennt werden.
Die Slider-Position ist eine Fließkomma-Zahl, benutzt wird aber immer nur der gerundete Wert. Das einfache Überschreiben der Fließkomma-Position mit einer Ganzzahl führt i.d.R. zu einem unmotivierten Ruck des Sliders. Die Slider-Position wird deshalb nur dann neu eingestellt, wenn die gerundete Slider-Position nicht mit dem Zielwert übereinstimmt.
Die Anzeige-Texte werden in globalen Variablen abgelegt. Dies stellt die einheitliche Beschriftung sicher, wenn die Anzeige an unterschiedlichen Stellen angepasst wird.
Wenn die Einstellungen der Slider geändert wurde, beginnt eine Zeitschleife zu laufen. Wenn innerhalb der vorgegeben Zeit keine weitere Änderung der Slider-Positionen erfolgt ist, wird davon ausgegangen, das der Anwender seine Eingaben beendet hat. Nun wird der aktuelle Parametersatz an die Gewächshaussteuerung übertragen. Der Aufruf von updateState stellt sicher, dass der Gewächshauszustand und die Anzeige synchron sind. Abschließend erfolgt der Übergang in den Zustand Connected.
Die Zeitschleife wird durch den Vergleich der Systemzeit realisiert.