REPL - Interaktives Experimentieren mit p44Script


Was ist ein REPL?

REPL ist eine Abkürzung und bedeutet "Read Evaluate Print Loop" - "Lesen-Auswerten-Ausgeben-Schleife" (Wikipedia). Die Script-Sprache wartet auf eine Eingabe, führt sie aus, gibt das Resultat aus - und wartet dann auf die nächste Eingabe. Andere Bezeichnungen für dasselbe sind Console (etwa bei JavaScript), oder shell (Kommandozeile).

Ein REPL ist nützlich, um einzelne Script-Befehle auszuprobieren, ggf. einfache Sequenzen von ein paar Zeilen, ohne ein "richtiges" Script zu schreiben, zu speichern und ausführen zu lassen. Auch zur Fehlersuche in bestehenden Scripten kann der REPL hilfreich sein, etwa um den aktuellen Inhalt von Variablen oder sonstige Systemzustände abzufragen oder zu verändern.

Bitte beachten!

Ganz allgemein gibt der REPL direkten Zugriff zu vielen Funktionen und interne Mechanismen. Damit lässt sich vieles erreichen, aber bei unvorsichtiger Anwendung auch der Normalbetrieb durcheinanderbringen, und im Extremfall das ganze Gerät zum Absturz bringen. Es ist deshalb zu empfehlen, den REPL nur auf einem Gerät zu verwenden, von dem vorgängig ein Konfigurations-Backup erstellt und gesichert wurde, und an welchem keine heikle Hardware angeschlossen ist.

REPL in P44-xx-Geräten

Die P44-xx-Geräte mit Weboberfläche bieten den REPL als ein einfaches Formular unter der URL http://ip-meines-p44-geraets/repl.html:

REPL web page

Beispiele

Aktuelles Datum und Zeit

Im obigen Screenshot wird gezeigt, wie man sich die aktuelle Zeit ausgeben lassen kann:

  • Eingabe:

    formattime()

  • Den Execute-Knopf drücken:

  • Ausgabe:

    2021-03-30 19:57:05 // String

Die Ausgabe zeigt das Resultat, und fügt dahinter einen Kommentar mit dem Datentyp des Resultats hinzu.

Zusammen mit der Ausgabe wird auch der Log-Ausschnitt unten neu geladen. Damit werden Log-Einträge, die durch den eingegebenen Script-Befehl verursacht wurden, sofort sichtbar.

Log-Einträge erzeugen

Die log()-Funktion erlaubt es, aus Scripts ins Geräte-Log zu schreiben:

  • Eingabe:

    log('Dies ist ein Logeintrag')

  • Ausgabe:

    Dies ist ein Logeintrag // string

  • Im Log erscheint:

    [2021-03-30 20:20:50.912 N] Script log: Dies ist ein Logeintrag

Die log()-Funktion mit einem Argument gibt dieses im Loglevel 5 aus ("Notify", s. N nach dem Datum). Mit zwei Argumenten kann aber der Loglevel auch explizite bestimmt werden:

  • Eingabe:

    log(4, 'Dies ist eine WARNUNG')

  • Ausgabe:

    Dies ist eine WARNUNG // string

  • Im Log erscheint (als "Warning", s. W nach dem Datum):

    [2021-03-30 20:25:40.729 W] Script log: Dies ist eine WARNUNG

Anmerkung

Was tatsächlich im Log erscheint, hängt vom global eingestellten Log-Level ab. Dieser ist standardmässig auf 5 ("Notify") eingestellt, kann aber auf der "Log"-Seite der normalen Weboberfläche und via loglevel() eingestellt werden.

Ausdrücke auswerten - "Rechnen"

Scriptbefehle können einfach Ausdrücke mit Zahlen sein:

  • Eingabe:

    7 * 6

  • Ausgabe:

    42 // numeric

Aber auch Ausdrücke mit Zeichenketten (Strings):

  • Eingabe:

    "Hallo Welt" + " um: " + formattime()

  • Ausgabe:

    Hallo Welt um: 2021-03-30 20:22:20 // string

Variablen

Die Eingaben im REPL werden im gleichen Script-Kontext ausgeführt wie das sogenannte Mainscript (s. Box), was bedeutet, dass die mit var definierten Variablen dieselben sind. D.h. dass man im REPL die Variablen des Mainscripts inspizieren und auch verändern kann.

Was ist das Mainscript?

Das Mainscript ist eine Art "Hauptprogramm" für ein Gerät, das beim Start des ganzen Geräts startet, und je nach Inhalt auch die ganze Zeit aktiv bleiben kann, vielleicht aber auch nur ein paar Dinge beim Start initialisiert. Standardmässig ist das Mainscript leer.

Achtung

Der Zugriff auf Mainscript-Variablen kann z.B. bei der Fehlersuche sehr praktisch sein, aber birgt auch die Gefahr, versehentlich eine Variable des Mainscripts zu überschreiben und so den Verlauf des Scripts zu stören.

Variablen können einfach über ihren Namen angesprochen werden:

  • Eingabe:

    testvar

  • Ausgabe:

    // *** Error: 'testvar' unknown here (ScriptError:5)

Dies bedeutet, dass es eine Variable testvar bislang noch nicht gibt (weder im aktuellen Scriptcontext noch als globale, d.h. von allen Scripten aus zugängliche, Variable). Also definieren wir sie:

  • Eingabe:

    var testvar

  • Ausgabe:

    null // uninitialized variable

Jetzt existiert die Variable, aber sie hat noch keinen Wert, bzw. den "Wert" null (gleichbedeutend mit undefined). An der Stelle ist eine Besonderheit von p44script zu sehen: null-Werte können eine "Annotation", d.h. einen Kommentar haben. In diesem Fall die Information "uninitialized variable". Das hilft zum Verständnis, weshalb ein Wert null ist. Hier, weil noch kein Wert zugewiesen wurde.

  • Eingabe:

    testvar = 21 * 2

  • Ausgabe:

    42 // numeric

Ab jetzt ist testvar definiert:

  • Eingabe:

    testvar

  • Ausgabe:

    42 // numeric

Gerätezugriff (P44-DSB, P44-LC)

Geräte (Leuchten, Taster, Sensoren etc.) die auf der entsprechenden P44-DSB/P44-LC angeschlossen sind, können direkt angesprochen werden. Das geht entweder über die dSUID des Geräts (was für langlebige Scripte zu empfehlen ist, weil die dSUID eine stabile Identifikation eines Geräts ist), oder aber über den Namen (praktisch für Tests, nicht empfohlen für langlebige Scripte, da der Name vom User vielleicht irgendwann geändert wird).

Für die folgenden Beispiele wird angenommen, dass es eine Leuchte mit dem Namen "Lampe" im System gibt.

  • Eingabe:

    device('Lampe')

  • Ausgabe:

    0 // device

Da es sich bei dem Resultat um ein Gerät handelt, hat es keinen eigentlichen "Wert", es gibt einfach 0 zurück. Aber der Kommentar - "device" - weist darauf hin, dass es sich um ein Geräte-Objekt handelt. Ein Geräte-Objekt hat verschiedene Methoden und Eigenschaften, s. "Zugriff auf Geräte und API" in der Kurzreferenz. Zum Beispiel lässt sich die Helligkeit der "Lampe" wie folgt auslesen:

  • Eingabe:

    device('Lampe').output.channel('brightness')

  • Ausgabe:

    41.96078431372549 // value source

Das Resultat ist eine Zahl (helligkeit in %) aber der Kommentar sagt nun "value source" (bei älterer FW u.U. "input value"). Eine value source (Wertequelle) hat nicht nur eine numerischen Wert, sondern kann auch als Auslöser in verschiedenen Situationen verwendet werden. Zum Beispiel kann auf die nächste Änderung einer value source gewartet werden:

  • Eingabe:

    await(device('Lampe').output.channel('brightness'), 30)

  • Jetzt wird bis zu 30 Sekunden auf eine Änderung der Helligkeit gewartet

  • Ausgabe:

    ...running...

  • Wenn innerhalb der 30 Sekunden keine Helligkeitsänderung erfolgt, gibt es einen Timeout:

    null // await timeout

  • Wenn aber innerhalb der 30 Sekunden z.B. eine Szene aufgerufen wird, welche die Helligkeit ändert (hier: Ausschalten), kommt sofort:

    0 // value source

Value sources können z.B in Evaluator-Bedingungen oder mit dem on(...) {...}-Statement verwendet werden. In Evaluatoren gibt es eine bequemere Auswahl von möglichen Sensor-Wertequellen, aber mit der device(...).output.channel(...) Syntax können in Evaluatoren jetzt auch Ausgangswerte überwacht werden.

Auch Taster sind Value sources (Annahme: es existiert ein Taster namens "Taster" im System):

  • Eingabe:

    await(device('Taster').button(0), 30)

  • Ausgabe nach kurzem Druck auf den Taster (Aus-Seite, wenn es eine Wippe ist, die Ein-Seite wäre button(1)):

    1 // value source

Der Wert 1 bedeutet: Taste einmal kurz gedrückt. Wird der Taster aber gehalten, ist das Resultat 5 (Taste gehalten). Die anderen Werte sind 2,3,4 für Doppel- Drippel- und Vierfach-Click, sowie 0 für Loslassen nach längerem Halten.