Es gibt viele Möglichkeiten ein externes Programm aufzurufen. Ich habe Einiges ausprobiert und hier sind meine Erkenntnisse. Es kann sein, dass sich Dinge ändern oder ich etwas nicht richtig verstanden habe. Meine Ausführungen sind mit Sicherheit nicht vollständig!
Dieser Beitrag endet mit konkreten Beispielen, die kann man exakt so übernehmen. Dabei sind die Anführungszeichen in den Codefenstern richtig und wichtig!
Empfehlung: Komplizierte und lange Codezeilen immer in ein Script oder in eine Perl Sub schreiben! Mit Sonderzeichen, doppelten und einfachen Anführungszeichen kommt man schnell in sehr unübersichtliche, beinahe unlösbare Situationen. Immer die mögliche Blockade von FHEM im Hinterkopf haben!
Aufruf
Es gibt drei
FHEM Kommandotypen, sie unterscheiden sich in der Verwendung der "Klammer"
- FHEM Befehl - keine Klammer
- "Shell Befehl" - doppelte Anführungszeichen (blockiert FHEM nicht)
- {Perl Befehl} - geschweifte Klammer (kann FHEM blockieren)
Für externe Programmaufrufe eignen sich die beiden letzeren.
Perl kennt in sich wieder drei Funktionsaufrufe für externe (nicht Perl) Programme.
- system() - liefert in FHEM immer "-1" zurück, Ausgaben landen im Logfile
- qx() - liefert die Ausgabe zurück, bei Aufruf in der FHEM Oberfläche landet diese im Browserfenster
- exec() - in FHEM nicht sinnvoll! Beendet FHEM und das aufgerufene Programm wird ausgeführt.
Kurze Fingerübung
Der Code ist für die FHEM Kommandozeile!
"echo test"
Liefert nichts im Browserfenster, schreibt
test ins Logfile.
Innerhalb von Perlcode kann man das auch so verwenden:
{fhem("\"echo test\"")}
Perl system() liefert -1 im Browserfenster und schreibt
test ins Logfile.
{system("echo test")}
Variante von system() mit anderer Schreibweise: {system("programm","Arg1","Arg2")} und man bekommt das gleiche Ergebnis.
{system("echo","test")}
Perl qx() liefert
test im Browserfenster und schreibt nichts ins Logfile.
{qx(echo test)}
Tipp: Diese Version verwende ich gern, um in der FHEM Oberfläche mal schnell etwas im System abzufragen. Man muss dazu nicht immer ins Terminal wechseln.
Achtung: Die Pipeline (> |) der Standardausgabe funktioniert nur bei {system("echo system > test.txt")} und {qx(echo qx > test.txt)}, bei "echo kommando > test.txt" versagt sie aber!
Zwischenspiel
Wie bekomme ich Text in eine Datei? Am Besten mit der Perlfunktion
FileWrite
{FileWrite("test.txt","Inhalt")}
Das Ergebnis anschauen:
{qx(cat test.txt)}
Beispiel mit Wertübergabe von FHEM
Dieses mehrzeilige Beispiel ist für die Raw Definition!
Mit dem Code Block erzeugt man:
- ein Shellprogramm (echo $(($1*$2))) welches beide Übergabewerte multipliziert.
- ein Dummy mit dem Reading Wert,
- ein notify welches den Wert und state an das Shell Programm übergibt und die Rückgabe wieder den Wert schreibt.
{FileWrite("ext_Script.sh","echo \$((\$1*\$2))")}
define ext_dummy dummy
attr ext_dummy readingList Wert
attr ext_dummy room extern
attr ext_dummy setList 0 1 2 3 4 5 6 7 8 9
setreading ext_dummy Wert 1
define ext_notify notify ext_dummy:[0-9] {\
my $a = ReadingsVal($NAME,"Wert","2");;\
my $b = qx(bash ext_Script.sh $EVENT $a);;\
fhem("set $NAME Wert $b")\
}
attr ext_notify room extern
Diese Beispiel ist sicher etwas konstruiert, es zeigt aber verschiedenen Ebenen (FHEM, Perl, Shell) und auch die Übergabe von Werten über Variablen. Dabei gibt es einiges zu beachten:
Sonderzeichen in Textketten werden innerhalb einfacher und doppelter Anführungszeichen je nach Ebene unterschiedlich behandelt! Bei doppelten Anführungszeichen gilt:
- Text der mit $ oder @ beginnt wird als Perl Variable/Array aufgelöst/ersetzt. Man kann/muss die Auflösung mit dem vorangestellten \ verhindern/schützen: "\$a , name\@domain.de".
- Ist das Anführungszeichen selbst nur Text: \".
- Den \ muss man selbst schützen: \\.
- Mit \ werden auch Steuerzeichen eingeleitet. z.B. \n ist ein Zeilenumbruch.
Immer dran denken: Den Befehlstrenner ; müssen wir in der FHEM Eingabezeile auch verdoppeln/schützen!
Beispiele zur Verwendung von Variablen
Zwei Perlzeilen und zwei Ebenen. Die Ausgabe ist gleich, die Variablen unterschiedlich.
- $a ist eine Perl Variable und der Inhalt wird von Perl zur Shell übergeben.
- $a ist eine Shell Variable und wird erst in der Shell erzeugt. Es wird nichts übergeben.
{my $a = "test";;qx "echo $a"}
{qx 'a=test;;echo $a'}
Der Syntax von qx ist sehr variabel, alle Zeilen bewirken das Gleiche: den letzten Fehlercode der Shell ausgeben
{qx "echo \$?"}
{qx 'echo $?'}
{qx /echo \$?/}
{qx (echo \$?)}
{`echo \$?`}
Mit einer Ausnahme könnte auch eine Perl Variablen übergeben werden (anstatt \$? $a).
Achtung: Die Verwendung von qx("") führt zu einer Fehlermeldung, da ist offenbar ein String Terminator zu viel.
Damit es nicht zu leicht wird, kann man die String Terminatoren natürlich auch schachteln. Für die Auflösung von Perl Variablen ist dabei die äußere Klammer bestimmend!
Das erste Beispiel gibt Hallo Welt aus, das zweite nur Hallo. Die Variable $a wird im zweiten Fall nicht aufgelöst, sondern als Shell Variable interpretiert.
{my $a = "Welt";;qx "echo 'Hallo $a'"}
{my $a = "Welt";;qx 'a=FHEM;;echo "Hallo $a"'}
Ausnahmen? Verwirrung?
Im folgenden Beispiel muss der Name der Variable im Shellscript auch geschützt werden.
Im Zweifelsfall hilft nur probieren!
{system('echo "wget --quiet - \"http://192.168.178.65/esp8266?max=Aussentemperatur+\$1+C\"" > ext_Script2.sh')}
Empfehlung: Befehle kapseln
Die Verwendung von Trennzeichen im String und die bisher genannten Interpretationen von Variablen Namen in den Umgebungen führt mich zu den unbedingten Empfehlung Befehle zu kapseln! Entweder in Stringvariablen in Perl oder in Scriptdateien für andere Sprachen. Es spart jede Menge Zeit und Nerven!
Verkettung / concatenation
Perl kennt noch die String Verkettung mit Punkten.
Im ersten Fall ist die Ausgabe identisch zur Version oben, im zweiten Fall wird der Inhalt der Variablen noch in Anführungszeichen ausgegeben:
{my $a = "test";;qx "echo ".$a}
{my $a = "test";;qx "echo ".'"'.$a.'"'}
Welche Methode man verwendet ist häufig Geschmacksache, manchmal aber auch vom Inhalt der Strings und dem gewünschten Ergebnis abhängig und damit das Mittel der Wahl.
Achtung blockierend
Die Ausführung von externen Programmen
mit Perl Aufrufen wird FHEM für die Laufzeit dieses Programmes unterbrechen/anhalten. Das kann unvorhersehbare Folgen haben.
Der kurzer Perl Test erzeugt für 5 sec "Warten" und dann eine Ausschrift.
{sleep 5;; return "Das war eine Pause"}
{qx "echo Das war;;sleep 5;;echo eine Pause"}
Der zweite Test führt auf Shell Ebene ein sleep aus, beide echo Befehle werden aber erst am Ende über qx an den Browser zurück gegeben.
Für einen weiteren Test erzeugen wir uns wieder ein Shell Script.
{FileWrite("ext_Pause.sh","sleep \$1\necho \$1 sec Pause")}
Dies erwartet eine Zahl und macht dann eine entsprechende Pause. Mit einem qx() Aufruf können wir das Script testen.
{qx "echo Das war;;bash ext_Pause.sh 4"}
Nach meinen Informationen, lässt sich das Verhalten mit qx() nicht ändern. Die Befehle werden immer seriell ausgeführt, Perl wartet auf die Rückgabe!
Der system() Aufruf hat andere Möglichkeiten. Bei ihm kann man mit dem & Zeichen am Ende (für die Shell typisch) die Ausführung im Hintergrund starten. Die Ausgabe im Script landet damit nach der Pause im Logfile von FHEM.
{system ("bash ext_Pause.sh 5 &")}
Aufräumen
Um die hier beim Testen erzeugten Geräte und Dateien wieder zu entfernen, kann man die beiden Zeilen einzeln in der Kommandozeile oder zusammen in der Raw Definition ausführen.
delete ext_.*
{system ("rm ext_*.sh >/dev/null")}
Ganz zum Schluss
Mal noch als "Merker" der Tipp aus dem
Forum. Den baue ich bei Gelegenheit mal noch aus.
Code Block